diff options
author | bd <bdunahu@operationnull.com> | 2025-01-28 14:39:47 -0500 |
---|---|---|
committer | bd <bdunahu@operationnull.com> | 2025-01-28 14:39:47 -0500 |
commit | 9e09767e23a4edb6b31540195bfe885f83e080d7 (patch) | |
tree | 42454c51ea8e0c8cf90b7c9020dedf3a5627cea2 /ull | |
parent | c63a873fe7fbf7947e07acfaf2402fe85100deba (diff) |
[Ongoing] Rewrite frontend to use Flex/Bison
This is a merge of another experiment, so the changes are large:
- separated "modules" directory into frontend/backend
- adjusted module names and moved files for this to happen
- removed modules lexer & parser
- removed all the unit tests (most were outdated)
- added Bison, flex, and C development tools to manifest.scm
- added lexer.l, a source file used by the flex utility with a functioning lexing implementation
- added parser.y, a source file used by the bison utility with a functioning parser implementation
- added node.c and node.h, which parser.y uses to construct an AST of a C source file (up to binary ops)
- added driver.c, a Guile-C interface that provides a module to scheme programs
- added a Makefile to make all of this
- added stuff to .gitignore
Diffstat (limited to 'ull')
-rwxr-xr-x | ull | 127 |
1 files changed, 127 insertions, 0 deletions
@@ -0,0 +1,127 @@ +#!/run/current-system/profile/bin/guile \ +-L ./src -e main -s +!# + +(use-modules (ice-9 getopt-long) + (ice-9 popen) + (ice-9 pretty-print) + (frontend driver) + (backend tacky driver) + (backend generator driver) + (backend emitter driver)) + +(define version "v0.1.1") + +(define (error message) + (display (string-concatenate `(,message " +Usage: + ull [OPTIONS] file +Options: + --version, -v: print version information + --debug, -d: turn on verbose output + --lex, -l: run the lexer, but stop before assembly generation + --parse, -p: run the lexer and parser, but stop before assembly generation + --tacky, -t: run the tacky generation stage, but stop before assembly generation + --codegen, -c: perform lexing, parsing, tacky, and assembly generation, but stop before code emission\n"))) + (exit #f)) + +(define (c-extension? file) + (let ((extension (string-drop file (- (string-length file) 2)))) + (string=? extension ".c"))) + +(define (cleanup-file file) + (when (file-exists? file) + (delete-file file))) + +(define (preprocess src dst) + "Preprocesses SRC with gcc and write it to DST." + (system (string-concatenate `("gcc -E -P " ,src " -o " ,dst))) + (display (string-concatenate `("Preprocess reported success (wrote " ,dst ").\n")))) + +(define (frontend file parse?) + "Wrapper for lexing and parsing. Returns an AST representing FILE." + (let ((c-ast (file->ast file parse?))) + (or c-ast + (begin + (display (format #f "~a reported failure!\n" (or (and parse? "Parser") "Lexer"))) + (cleanup-file file) + (exit #f))))) + +(define (backend c-ast tack? generate? write?) + "Driver for tacky and assembly generation, code emission. Returns an assembly program representing C-AST." + (pretty-print c-ast) + (display "===^file->ast^===\n") + (when tack? + (let ((tacky-ast (ast->tacky c-ast))) + (pretty-print tacky-ast) + (display "===^ast->tacky^===\n") + (when generate? + (let ((assembly-ast (tacky->assembly tacky-ast))) + (pretty-print assembly-ast) + (display "===^tacky->assembly^===\n") + (when write? + (assembly->string assembly-ast))))))) + +(define (postprocess src dest) + "Assembles and links SRC, producing executable DEST. +Returns #f on a failure, #t on a success." + (zero? (system (string-concatenate `("gcc " ,src " -o " ,dest))))) + +(define (write str dst) + "Writes STR to the file at DST." + (cleanup-file dst) + (let ((port (open-output-file dst))) + (display str port) + (close-port port)) + (display (string-concatenate `("Assembly generation reported success (wrote " ,dst ").\n")))) + +(define (main args) + "Entry point for ull. Handles user args and performs initial validity check." + (let* ((option-spec + '((version (single-char #\v) (value #f)) + (debug (single-char #\d) (value #f)) + (lex (single-char #\l) (value #f)) + (parse (single-char #\p) (value #f)) + (tacky (single-char #\t) (value #f)) + (codegen (single-char #\c) (value #f)))) + (options (getopt-long args option-spec)) + (rest (option-ref options '() #f)) + (file-name (if (null? rest) #f (car rest))) ) + (cond + ;; display version number + ((option-ref options 'version #f) + (display (string-concatenate `("ull (" ,version ")\n")))) + + ;; complain about usage + ((not (equal? 1 (length rest))) (error "Wrong number of arguments.")) + ((or (not file-name) + (not (access? file-name R_OK)) + (not (equal? 'regular (stat:type (stat file-name)))) + (not (c-extension? file-name))) (error "The file could not be read, or it is not a C source code file.")) + + (#t + (let ((parse? (not (option-ref options 'lex #f))) + (tack? (not (option-ref options 'parse #f))) + (generate? (not (option-ref options 'tacky #f))) + (write? (not (option-ref options 'codegen #f))) + (preprocessed-file-name + (string-concatenate `(,(string-drop-right file-name 1) "i"))) + (assembly-file-name + (string-concatenate `(,(string-drop-right file-name 1) "s"))) + (executable-file-name + (string-drop-right file-name 2))) + + ;; preprocess the file + (preprocess file-name preprocessed-file-name) + ;; call the frontend + (let ((c-ast (frontend preprocessed-file-name parse?))) + (cleanup-file preprocessed-file-name) + (if (and parse? c-ast) + ;; call the backend + (begin (display "Parser reported success\n") + (let ((program (backend c-ast tack? generate? write?))) + (when write? + (write program assembly-file-name) + ;; call postprocessing + (postprocess assembly-file-name executable-file-name)))) + (display "Tokenizer successful. ")))))))) |