-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode.mx
More file actions
286 lines (256 loc) · 12.2 KB
/
Copy pathcode.mx
File metadata and controls
286 lines (256 loc) · 12.2 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
; code.mx -- the same Pascal as examples/pascal.mx, through the other kind of
; template.
;
; diff examples/pascal.out examples/code.out
;
; is the whole argument for this file existing. The body below is character for
; character the body of pascal.mx. Every rule is the same rule. The only
; difference is that a template here is `=> { … }` rather than `=> "…"`, and
; two things pascal.out is recorded as getting wrong come out right:
;
; the parentheses pascal.out writes `(((((i % mod) == 0)) && ((i != 9))))`,
; because a string template can only bracket every operand
; unconditionally. Here a rule asks an operand what level it
; was parsed at and brackets it only when it must.
;
; the literal pascal.out writes `puts('it''s middling')` into C, because
; a `string` hole splices the source text it matched. Here
; the rule translates it.
;
; The language is Metaxis's own, so it lives outside the strings; the foreign
; text it emits lives inside them. That is the same rule the pattern side
; follows, which is why `{` after the `=>` was enough to tell the two forms
; apart and nothing had to be reserved.
@comment "{" "}"
@comment "(*" "*)"
@token number "[0-9]+"
@token name "[A-Za-z_][A-Za-z0-9_]*"
@token string "'([^']|'')*'"
@separator ";" => ";\n"
; A group is an atom whatever is inside it, so nothing below ever needs to
; bracket it again.
@syntax "(" e ")" => { emit "(" + e + ")" }
@syntax a "(" [ x ]* sep "," join ", " ")" 95 => { emit a + "(" + x + ")" }
; `group(x, n)` is the operand `x`, bracketed when the rule that produced it
; binds looser than `n`. The right operand asks for one more than the left, so
; `a - b - c` keeps its grouping and `a - (b - c)` keeps its brackets.
@syntax a ":=" b 10 => { emit a + " = " + b }
@syntax a "=" b 40 => { emit group(a, 40) + " == " + group(b, 41) }
@syntax a "<>" b 40 => { emit group(a, 40) + " != " + group(b, 41) }
@syntax a "<" b 40 => { emit group(a, 40) + " < " + group(b, 41) }
@syntax a ">" b 40 => { emit group(a, 40) + " > " + group(b, 41) }
@syntax a "+" b 60 => { emit group(a, 60) + " + " + group(b, 61) }
@syntax a "-" b 60 => { emit group(a, 60) + " - " + group(b, 61) }
@syntax a "*" b 70 => { emit group(a, 70) + " * " + group(b, 71) }
@syntax a "div" b 70 => { emit group(a, 70) + " / " + group(b, 71) }
@syntax a "mod" b 70 => { emit group(a, 70) + " % " + group(b, 71) }
@syntax "not" a 80 => { emit "!" + group(a, 80) }
@syntax a "and" b 30 => { emit group(a, 30) + " && " + group(b, 31) }
@syntax a "or" b 25 => { emit group(a, 25) + " || " + group(b, 26) }
; The include is not written here. `writeln` contributes it to a collection
; called `head` -- once, however many times it fires -- and `program` says
; where the aggregate goes with `splice("head")`. A program that never prints
; gets no include, which is the difference between a head that is the body's
; aggregate and one that was guessed. This was the first customer for the
; mechanism, named in docs/prior-art.md the day before it was built.
;
; Declarations. Nothing here is better in this form than in a string, and both
; files say it the same way -- a type is a quoted word because `integer` has to
; come out as `int`, and a hole would splice the token it matched.
@syntax "program" n:name => { emit "/* " + n + " */\n" + splice("head") } terminated
@syntax a "," b 20 => { emit a + ", " + b }
; A type is a rule of its own, a word alone, so that it can be *read* as well
; as matched. That is what lets a parameter list hold a hole where the type goes
; and translate each one on its own -- see the fragment below.
@syntax "integer" => { emit "int" }
@syntax "boolean" => { emit "int" }
@syntax "real" => { emit "double" }
; The declaration still names the type as a quoted word, one rule per type, and
; cannot use the hole. `a ":" t` would read every `case` arm as a declaration:
; `1: writeln(11)` and `mod: integer` are both `expr ":" expr` and nothing here
; tells them apart. That is the context wall again -- the same one `writeln` and
; a parameterless call sit against -- reached this time from the type side.
@syntax a ":" "integer" 15 => { emit "int " + a }
@syntax a ":" "boolean" 15 => { emit "int " + a }
@syntax a ":" "real" 15 => { emit "double " + a }
@syntax "var" d => { emit d }
; A block's last statement needs its own semicolon: @separator puts one
; *between* two statements and never after the last, so the `}` would otherwise
; close over an unterminated one. `terminated(body)` is the same question asked
; of a run of statements, where it means *the last one*.
@syntax "begin" body:stmts "end"
=> {
emit "{\n" + indent(body, 4)
if not terminated(body) { emit ";" }
emit "\n}"
} terminated
; `group(c, 80)` again: C's `!` binds tighter than any comparison, so the
; condition needs brackets unless it is an atom. examples/pascal.mx writes them
; unconditionally, and `while (!(1))` is what that costs.
@syntax "repeat" b:stmts "until" c
=> {
emit "do {\n" + indent(b, 4)
if not terminated(b) { emit ";" }
emit "\n} while (!" + group(c, 80) + ")"
}
; Pascal's arms do not fall through and C's do, so every one ends in a `break`
; the source never wrote.
;
; The arm is `[ v ":" s ]` -- **two** holes in one repeated group, which is two
; parallel lists. `for i, x in v` walks the labels with their position and
; `at(s, i)` takes the matching body. examples/pascal.mx cannot do this: a
; string template splices each list joined and has no way to interleave them,
; so it declares an infix `a ":" s` rule to fold the pair into one value before
; the group sees it, and that rule then means *case arm* everywhere a colon is
; not already claimed. This is the difference the two files exist to show, and
; it is the sharpest one on the page.
@syntax "case" e "of" [ v ":" s ]* sep ";" "end"
=> {
emit "switch (" + e + ") {\n"
for i, x in v sep "\n" { emit indent("case " + x + ": " + at(s, i) + "; break;", 4) }
emit "\n}"
} terminated
@syntax "case" e "of" [ v ":" s ]* sep ";" "else" d "end"
=> {
emit "switch (" + e + ") {\n"
for i, x in v sep "\n" { emit indent("case " + x + ": " + at(s, i) + "; break;", 4) }
emit "\n" + indent("default: " + d + "; break;", 4) + "\n}"
} terminated
@syntax "begin" body:stmts "end" "."
=> { emit "int main(void) {\n" + indent(body + ";\nreturn 0;", 4) + "\n}" } terminated
; `terminated(h)` is `level(h)`'s other half. C's `if (c) x = 1; else` wants a
; semicolon that C's `if (c) { … } else` must not have, and which of the two a
; branch is depends on the rule that filled the hole -- exactly what a rule
; says when it declares itself `terminated`, and exactly what a hole now
; remembers. examples/pascal.mx cannot ask, so it braces every branch
; unconditionally and the recorded diff shows what that costs.
; A rule punctuates *inside* itself and never at its end: the semicolon before
; an `else` is C's and has to be written here, and the one that ends the whole
; statement is @separator's, the way it is for every other statement. Emitting
; both is how the first draft of this got `x = 1;;`.
@syntax "if" c "then" t
=> { emit "if (" + c + ") " + t }
@syntax "if" c "then" t "else" f
=> {
emit "if (" + c + ") " + t
if not terminated(t) { emit ";" }
emit " else " + f
}
@syntax "while" c "do" b
=> { emit "while (" + c + ") " + b }
; Pascal's `'it''s'` becomes C's "it's": drop the quote off each end, undouble
; what is left, and put C's quotes back. `drop` and `replace` are the two
; smallest things a template needs to translate a literal rather than move it.
@syntax "writeln" "(" x:string ")"
=> {
contribute("head", "#include <stdio.h>")
emit "puts(\"" + replace(drop(x, 1, 1), "''", "'") + "\")"
}
@syntax "writeln" "(" x ")"
=> {
contribute("head", "#include <stdio.h>")
emit "printf(\"%d\\n\", " + x + ")"
}
@syntax "for" i:name ":=" a "to" b "do" s
=> {
emit "for (int " + i + " = " + a + "; "
emit i + " <= " + b + "; " + i + "++) "
emit s
}
; A parameter list, and the place the two forms finally part company.
; `p` is a hole inside a repeated group, so it is a **list**; `t` is a second
; one holding the type of each turn, and the loop below walks them in step with
; `for i, x in p` and `at(t, i)`. That is what lets `Scale(n: integer; k: real)`
; come out as `void Scale(int n, double k)`.
;
; examples/pascal.mx cannot. `join ", int "` writes one word in front of every
; turn and cannot vary it, and a string template splices each list joined with
; no way to interleave two -- so that file writes `int k` for a `real` and its
; recorded output carries the wrong type on purpose. This used to be the one
; place in these two files where the code template bought nothing. It is now
; the clearest place it buys something, and the argument is the diff between
; the two recorded outputs rather than this paragraph.
;
; The list itself is written once. `@fragment` names a piece of *pattern* and
; `@params` splices it where the list goes; the holes come with it, which is why
; the bodies below still say `p` without declaring it. It is not a template and
; takes no arguments -- a template is called at expansion and this is spliced at
; declaration, so by the time either rule is matched there is nothing left to
; say a fragment was ever involved.
@fragment params = "(" [ p:name ":" t ]* sep ";" ")"
; And the body once, which is where the two mechanics meet: the pattern is
; shared by `@params` and the template by `subprogram`, and the only thing left
; that differs between a procedure and a function is C's return type. A list
; goes through a template parameter unchanged -- `p` arrives as a list and the
; loop below walks it -- which nothing had asked for until this call site.
@template subprogram(ret, f, p, t, b) {
emit ret + " " + f + "("
if count(p) == 0 { emit "void" }
for i, x in p sep ", " { emit at(t, i) + " " + x }
emit ") " + b
}
@syntax "procedure" f:name @params ";" b
=> { subprogram("void", f, p, t, b) } terminated
@syntax "function" f:name @params ":" rt ";" b
=> { subprogram(rt, f, p, t, b) } terminated
; Free Pascal's `Result`, for the reason examples/pascal.mx gives.
@syntax "Result" ":=" e => { emit "return " + e }
@end
{ Everything below is Pascal. Nothing below is Metaxis's.
It is the body of examples/pascal.mx, unchanged. }
program Fizz;
var
total, mod: integer;
i, n: integer;
{ A procedure and a function. Pascal separates parameter groups with `;` and
C gives every parameter its own type, so one Pascal group becomes several
C ones. }
procedure Show(n: integer);
begin
writeln(n)
end;
procedure Scale(n: integer; k: real);
begin
writeln(n)
end;
procedure Pair(a: integer; b: integer);
begin
writeln(a + b)
end;
function Double(n: integer): integer;
begin
Result := n * 2
end;
begin
total := 0;
mod := 3;
for i := 1 to 20 do
if (i mod mod = 0) and (i <> 9) then
total := total + i
else
total := total - 1;
if not (total > 100) then writeln('it''s middling') else writeln('big');
if total > 30 then
begin
total := total + 1;
writeln(total)
end
else
writeln(total);
{ `until` says when to stop; C's `while` says when to go on. }
n := 0;
repeat
n := n + 1
until n > 3;
writeln(n);
{ Pascal's arms do not fall through. C's do, so each one gains a `break`. }
case n of
1: writeln(11);
4: writeln(44)
else
writeln(0)
end;
Show(Double(total));
Scale(7, 2);
Pair(total, 2)
end.