-
-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathcpp.os
More file actions
605 lines (455 loc) · 13.6 KB
/
Copy pathcpp.os
File metadata and controls
605 lines (455 loc) · 13.6 KB
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
; The MIT License (MIT)
;
; Copyright (c) 2023-2025 Fraser Heavy Software
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in all
; copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE.
; This is the first stage preprocessor. It is written in compound assembly.
;
; It simply strips all preprocessor directives and comments.
;
; Both C-style comments and C++-style comments are supported. A preprocessor
; directive is treated the same as a comment.
; TODO we're linking against libo now so we should use its fatal() and
; file/line mechanism to give better error messages.
@dash_o
"-o" '00
@r
"r" '00
@w
"w" '00
@error_usage
"ERROR: Invalid arguments." '0A
"Usage: <cpp> <input> -o <output>" '0A '00
@error_input
"ERROR: Failed to read from input file." '0A '00
@error_output
"ERROR: Failed to read from output file." '0A '00
@error_unclosed_comment
"ERROR: Unclosed C-style comment." '0A '00
@error_unclosed_string
"ERROR: Unclosed string or character literal." '0A '00
@input_file
0
@output_file
0
@current_char
0
; ==========================================================
; int main(int argc, char** argv);
; ==========================================================
; The entry point of the preprocessor.
;
; vars:
; - argv: rfp-4
; ==========================================================
=main
; preserve argv
enter
push r1
; make sure we have exactly three arguments (plus the program name)
cmpu r0 r0 4
jnz r0 &main_usage
; check if "-o" is the first argument
ldw r1 r1 4
imw r0 ^dash_o
add r0 r0 rpp
call ^strcmp
jnz r0 &main_not_first
; it is; output comes first
ldw r9 rfp -4
ldw r1 r9 8
ldw r0 r9 12
jmp &main_ok
:main_not_first
; check if "-o" is the second argument
ldw r9 rfp -4
ldw r1 r9 8
imw r0 ^dash_o
add r0 r0 rpp
call ^strcmp
jnz r0 &main_usage
; it is; output comes last
ldw r9 rfp -4
ldw r0 r9 4
ldw r1 r9 12
:main_ok
push r0
call ^open_files
pop r0
call ^print_debug_line
call ^run
call ^close_files
; done
zero r0
leave
ret
:main_usage
imw r0 ^error_usage
add r0 r0 rpp
call ^__fatal
; ==========================================================
; void open_files(const char* input_filename, const char* output_filename);
; ==========================================================
=open_files
; preserve output filename
enter
push r1
; open input file
imw r1 ^r
add r1 r1 rpp
call ^fopen
jz r0 &open_files_input_failed
imw r1 ^input_file
stw r0 rpp r1
; open output file
pop r0
imw r1 ^w
add r1 r1 rpp
call ^fopen
jz r0 &open_files_output_failed
imw r1 ^output_file
stw r0 rpp r1
; prime the current char
call ^next_char
; done
leave
ret
:open_files_input_failed
imw r0 ^error_input
add r0 r0 rpp
call ^__fatal
:open_files_output_failed
imw r0 ^error_output
add r0 r0 rpp
call ^__fatal
; ==========================================================
; void close_files(void);
; ==========================================================
=close_files
imw r0 ^output_file
ldw r0 rpp r0
call ^fclose
imw r0 ^input_file
ldw r0 rpp r0
call ^fclose
ret
;==========================================
; void next_char(void);
;==========================================
; Reads a character, or -1 if the end of the file has been reached, into
; current_char.
;==========================================
=next_char
; don't bother to set up a stack frame
; call fgetc(input_file)
imw r0 ^input_file
ldw r0 r0 rpp
call ^fgetc
; store it in current_char
imw r1 ^current_char
stw r0 rpp r1
ret
; ==========================================================
; void print_debug_line(const char* input_filename);
; ==========================================================
; Prints a #line directive containing the source filename.
;
; We only print a single #line directive at the very start of the file. All
; line endings are forwarded from the input to the output so line numbers are
; automatically correct.
; ==========================================================
=print_debug_line
; TODO
ret
; ==========================================================
; void run(void);
; ==========================================================
=run
; no stack frame
:run_loop
; check if current_char is -1
imw r0 ^current_char
ldw r0 rpp r0
cmpu r0 r0 -1
jz r0 &run_eof
; check for a string
call ^try_parse_string
jnz r0 &run_loop
; check for a preprocessor directive
call ^try_parse_preproc
jnz r0 &run_loop
; check for a comment
call ^try_parse_comment
jnz r0 &run_loop
; otherwise forward the current char to the output
imw r0 ^current_char
ldb r0 rpp r0
call ^emit_byte
call ^next_char
jmp &run_loop
:run_eof
ret
; ==========================================================
; void emit_byte(char c);
; ==========================================================
; Writes the given byte to the output file.
; ==========================================================
=emit_byte
; no stack frame
; push the char to the stack to get a pointer to it it
push r0
; call fwrite
mov r0 rsp
mov r1 1
mov r2 1
imw r3 ^output_file
ldw r3 rpp r3
call ^fwrite
; TODO check for error
; done
popd
ret
; ==========================================================
; void try_parse_preproc(void);
; ==========================================================
; ==========================================================
=try_parse_preproc
; check if current_char is #
imw r0 ^current_char
ldw r0 rpp r0
cmpu r0 r0 "#"
jnz r0 &try_parse_preproc_not_found
; TODO rename parse_comment_cxx() to consume_line_comment() and call it
:try_parse_preproc_loop
; consume the current char
call ^next_char
; stop consuming when we reach a line feed, carriage return or eof
imw r1 ^current_char
ldw r1 rpp r1
cmpu r0 r1 '0A ; line feed
jz r0 &try_parse_preproc_done
cmpu r0 r1 '0D ; carriage return
jz r0 &try_parse_preproc_done
cmpu r0 r1 -1 ; end-of-file
jz r0 &try_parse_preproc_done
; keep going
jmp &try_parse_preproc_loop
:try_parse_preproc_done
mov r0 1
ret
:try_parse_preproc_not_found
zero r0
ret
; ==========================================================
; void try_parse_string(void);
; ==========================================================
; Checks for a string or character literal. If found, it is forwarded to the
; output. This prevents `#` inside literals being treated as preprocs.
;
; vars:
; - r0: current char
; - rfp-4: starting quote
; ==========================================================
=try_parse_string
enter
; check if current_char is ' or "
imw r0 ^current_char
ldw r0 rpp r0
sub r1 r0 "'"
jz r1 &try_parse_string_found
sub r1 r0 '22 ; '"'
jz r1 &try_parse_string_found
jmp &try_parse_string_not_found
:try_parse_string_found
; save the opening quote to match it later
sub rsp rsp 4
stw r0 rfp -4
:try_parse_string_loop
; emit and consume the current char
call ^emit_byte
call ^next_char
imw r0 ^current_char
ldw r0 rpp r0
; check for end of file
sub r2 r0 -1
jz r2 &try_parse_string_eof
; check for end of the string (matching quote)
ldw r1 rfp -4
sub r2 r0 r1
jz r2 &try_parse_string_done
; check for a backslash
sub r1 r0 '5C ; '\\'
jnz r1 &try_parse_string_loop
; we have a backslash. send two characters to the output (one here and one
; at the start of the loop.) we don't care if the escape sequence is longer
; than two characters; those will get captured in the loop as well.
call ^emit_byte
call ^next_char
imw r0 ^current_char
ldw r0 rpp r0
jmp &try_parse_string_loop
:try_parse_string_done
; emit the closing quote
call ^emit_byte
call ^next_char
; return true
mov r0 1
leave
ret
:try_parse_string_not_found
; return false
zero r0
leave
ret
:try_parse_string_eof
; error, unclosed string or character literal
imw r0 ^error_unclosed_string
add r0 r0 rpp
call ^__fatal
; ==========================================================
; void try_parse_comment(void);
; ==========================================================
; Checks for a C-style or C++-style comment.
;
; If a comment is found, it forwards to parse_comment_c() or
; parse_comment_cxx().
; ==========================================================
=try_parse_comment
; check if current_char is /
imw r1 ^current_char
ldw r1 rpp r1
cmpu r0 r1 "/"
jnz r0 &try_parse_comment_not_found
; consume it
call ^next_char
; check if current char is * or / for a C-style or C++-style comment
; respectively
imw r1 ^current_char
ldw r1 rpp r1
cmpu r0 r1 "*"
jz r0 &try_parse_comment_c
cmpu r0 r1 "/"
jz r0 &try_parse_comment_cxx
; not a comment. emit the / we consumed and fall through.
mov r0 "/"
call ^emit_byte
:try_parse_comment_not_found
zero r0
ret
:try_parse_comment_c
jmp ^parse_comment_c
:try_parse_comment_cxx
jmp ^parse_comment_cxx
; ==========================================================
; void parse_comment_c(void);
; ==========================================================
=parse_comment_c
; no stack frame
:parse_comment_c_loop
; consume the current char
call ^next_char
:parse_comment_c_test
; check if the current char is EOF. if so it's a fatal error
imw r1 ^current_char
ldw r1 rpp r1
cmpu r0 r1 -1
jz r0 &parse_comment_c_unclosed
; check if we have a *
imw r1 ^current_char
ldw r1 rpp r1
cmpu r0 r1 "*"
jnz r0 &parse_comment_c_loop
call ^next_char
; check if it's followed by /
imw r1 ^current_char
ldw r1 rpp r1
cmpu r0 r1 "/"
jz r0 &parse_comment_c_done
jmp &parse_comment_c_test
:parse_comment_c_done
call ^next_char
mov r0 1
ret
:parse_comment_c_unclosed
; unclosed C-style comment, fatal error
imw r0 ^error_unclosed_comment
add r0 r0 rpp
call ^__fatal
; ==========================================================
; void parse_comment_cxx(void);
; ==========================================================
; Parses a C++-style comment.
;
; A C++ style comment ends in a carriage return and line feed, or a carriage
; return alone, or a line feed alone, as long as these are not escaped. The
; comment can also end by the end of the file (for files that end in a
; C++-style comment without a final line ending.)
;
; A backslash at the end of a line escapes the line ending. It does not matter
; if this backslash is itself preceded by another backslash.
; ==========================================================
=parse_comment_cxx
; no stack frame
:parse_comment_cxx_consume
; consume the current char
call ^next_char
:parse_comment_cxx_test
; get the current char
imw r1 ^current_char
ldw r1 rpp r1
; check if it's a backslash. if so we need to check whether we're escaping
; a line ending
cmpu r0 r1 '5C ; backslash
jz r0 &parse_comment_cxx_backslash
; stop consuming when we reach a line feed, carriage return or eof
cmpu r0 r1 '0A ; line feed
jz r0 &parse_comment_cxx_done
cmpu r0 r1 '0D ; carriage return
jz r0 &parse_comment_cxx_done
cmpu r0 r1 -1 ; end-of-file
jz r0 &parse_comment_cxx_done
; keep going
jmp &parse_comment_cxx_consume
:parse_comment_cxx_backslash
; consume the backslash
call ^next_char
; get the current char
imw r1 ^current_char
ldw r1 rpp r1
; see if the next character is a carriage return, if so handle it specially
cmpu r0 r1 '0D ; carriage return
jz r0 &parse_comment_cxx_escaped_carriage_return
; see if it's a line feed, if so consume it and continue
cmpu r0 r1 '0A ; line feed
jz r0 &parse_comment_cxx_consume
; otherwise we ignore the escape sequence and keep going. we don't consume
; the escaped character. (a double \\ at the end of a line still escapes
; the line ending.)
jmp &parse_comment_cxx_test
:parse_comment_cxx_escaped_carriage_return
; consume the carriage return
call ^next_char
; check if we have a line feed, if so consume it and keep going
cmpu r0 r1 '0A ; line feed
jz r0 &parse_comment_cxx_consume
; otherwise test and keep going
jmp &parse_comment_cxx_test
:parse_comment_cxx_done
mov r0 1
ret