summaryrefslogtreecommitdiff
path: root/ull
blob: 004d7e50bacb408a346b421d6c78be2a6aba20e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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. "))))))))