Skip to content

Commit fbb9d33

Browse files
slice 1 v1
- parser complete
1 parent af92673 commit fbb9d33

File tree

7 files changed

+262
-34
lines changed

7 files changed

+262
-34
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ jobs:
2727
with:
2828
deno-version: v2.x
2929

30-
- name: deno install
31-
continue-on-error: false
32-
run: |
33-
cd bs
34-
deno fmt --check
35-
3630
- name: deno fmt
3731
continue-on-error: false
3832
run: |

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ to build a compiler that is sufficiently powerful compiles itself.
4040

4141
## specs
4242

43-
- refer [.\cmin.ebnf]
43+
- refer [C= definition](.\def\cmin.ebnf)
4444

4545
## References
4646

4747
- [curated list of awesome resources](https://github.com/aalhour/awesome-compilers)
4848
- [LLVM](https://mukulrathi.com/create-your-own-programming-language/llvm-ir-cpp-api-tutorial/)
4949
- [jack crenshaw: let's build a compiler](https://xmonader.github.io/letsbuildacompiler-pretty/)
50-
- [compiler explorer](https://godbolt.org/)
50+
- [compiler explorer](https://godbolt.org/)
51+
- [flex / bison tutorial](https://www.capsl.udel.edu/courses/cpeg421/2012/slides/Tutorial-Flex_Bison.pdf)

bs/main.js

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,10 @@ function isKeyword(str) {
3838
}
3939

4040
function scan(text, state) {
41-
// EOF token.
42-
if (getCurrChar(text, state) === null) {
43-
return { token: { id: TOK_EOF, value: null, location: state }, state }
44-
}
41+
state = { ...state }
4542

4643
// WHITESPACE token.
47-
if (isWhitespace(getCurrChar(text, state))) {
48-
const location = { ...state }
44+
while (isWhitespace(getCurrChar(text, state))) {
4945
while (true) {
5046
const currChar = getCurrChar(text, state)
5147
const nextChar = getNextChar(text, state)
@@ -72,8 +68,11 @@ function scan(text, state) {
7268

7369
break
7470
}
71+
}
7572

76-
return { token: { id: TOK_WHITESPACE, value: null, location }, state }
73+
// EOF token.
74+
if (getCurrChar(text, state) === null) {
75+
return { token: { id: TOK_EOF, value: null, location: state }, state }
7776
}
7877

7978
// KEYWORD or IDENTIFIER token.
@@ -114,7 +113,51 @@ export function scanAll(text) {
114113
return tokens
115114
}
116115

116+
export const AST_NODE_PROGRAM = 0
117+
export const AST_NODE_FUNCTION = 0
118+
119+
function parseFunction(tokens, state) {
120+
state = { ...state }
121+
122+
state.pos += 9
123+
return { ast: { id: AST_NODE_FUNCTION, name: 'main', statements: [] }, state, errors: [] }
124+
}
125+
126+
function parseProgram(tokens, state) {
127+
state = { ...state }
128+
const errors = []
129+
const ast = { id: AST_NODE_PROGRAM, functions: [] }
130+
131+
if (tokens[state.pos].id === TOK_ERROR) {
132+
errors.push({ location: tokens[state.pos].location, message: 'scanner error token' })
133+
state.pos += 2
134+
return { ast, state, errors }
135+
}
136+
137+
while (state.pos < tokens.length && tokens[state.pos].id === TOK_KEYWORD && tokens[state.pos].value === 'fn') {
138+
const fnRet = parseFunction(tokens, state)
139+
ast.functions.push(fnRet.ast)
140+
errors.push(...fnRet.errors)
141+
state = fnRet.state
142+
}
143+
144+
state.pos++
145+
return { ast, state, errors }
146+
}
147+
148+
export function parse(text) {
149+
const tokens = scanAll(text)
150+
if (tokens.length === 0) {
151+
return null
152+
}
153+
154+
const state = { pos: 0 }
155+
return parseProgram(tokens, state)
156+
}
157+
117158
// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
118159
if (import.meta.main) {
119-
console.log('This module is not meant to be run directly.')
160+
const text = 'fn main() {\n}'
161+
const ret = parse(text)
162+
console.log(ret)
120163
}

bs/main_test.js

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,159 @@ import { assertEquals } from '@std/assert'
22
import * as main from './main.js'
33

44
Deno.test({
5-
name: 'empty file',
5+
name: 'scan: empty file',
66
fn() {
77
const text = ''
88
const tokens = main.scanAll(text)
99

10-
// This will never be reached
11-
assertEquals([
10+
assertEquals(tokens, [
1211
{
1312
id: main.TOK_EOF,
1413
value: null,
1514
location: { pos: 0, line: 1, column: 1 },
1615
},
17-
], tokens)
16+
])
1817
},
1918
})
2019

2120
Deno.test({
22-
name: 'random error token',
21+
name: 'scan: random error token',
2322
fn() {
2423
const text = ' \r# \r\n \n '
2524
const tokens = main.scanAll(text)
2625

27-
// This will never be reached
2826
assertEquals(
27+
tokens,
2928
[
30-
{ id: main.TOK_WHITESPACE, value: null, location: { pos: 0, line: 1, column: 1 } },
3129
{ id: main.TOK_ERROR, value: null, location: { pos: 4, line: 2, column: 1 } },
3230
],
33-
tokens,
3431
)
3532
},
3633
})
3734

3835
Deno.test({
39-
name: 'empty file with whitespace',
36+
name: 'scan: empty file with whitespace',
4037
fn() {
4138
const text = ' \r\t \r\n \n '
4239
const tokens = main.scanAll(text)
4340

44-
// This will never be reached
4541
assertEquals(
42+
tokens,
4643
[
47-
{ id: main.TOK_WHITESPACE, value: null, location: { pos: 0, line: 1, column: 1 } },
4844
{ id: main.TOK_EOF, value: null, location: { pos: 14, line: 4, column: 3 } },
4945
],
50-
tokens,
5146
)
5247
},
5348
})
5449

5550
Deno.test({
56-
name: 'trivial main function',
51+
name: 'scan: trivial main function',
5752
fn() {
5853
const text = 'fn main() {\n}'
5954
const tokens = main.scanAll(text)
6055

61-
// This will never be reached
6256
assertEquals(
57+
tokens,
6358
[
6459
{ id: main.TOK_KEYWORD, value: 'fn', location: { pos: 0, line: 1, column: 1 } },
65-
{ id: main.TOK_WHITESPACE, value: null, location: { pos: 2, line: 1, column: 3 } },
6660
{ id: main.TOK_IDENTIFIER, value: 'main', location: { pos: 3, line: 1, column: 4 } },
6761
{ id: main.TOK_SYMBOL, value: '(', location: { pos: 7, line: 1, column: 8 } },
6862
{ id: main.TOK_SYMBOL, value: ')', location: { pos: 8, line: 1, column: 9 } },
69-
{ id: main.TOK_WHITESPACE, value: null, location: { pos: 9, line: 1, column: 10 } },
7063
{ id: main.TOK_SYMBOL, value: '{', location: { pos: 10, line: 1, column: 11 } },
71-
{ id: main.TOK_WHITESPACE, value: null, location: { pos: 11, line: 1, column: 12 } },
7264
{ id: main.TOK_SYMBOL, value: '}', location: { pos: 12, line: 2, column: 1 } },
7365
{ id: main.TOK_EOF, value: null, location: { pos: 13, line: 2, column: 2 } },
7466
],
75-
tokens,
67+
)
68+
},
69+
})
70+
71+
Deno.test({
72+
name: 'parse: empty file',
73+
fn() {
74+
const text = ''
75+
const ret = main.parse(text)
76+
77+
assertEquals(
78+
ret,
79+
{
80+
ast: {
81+
id: main.AST_NODE_PROGRAM,
82+
functions: [],
83+
},
84+
state: { pos: 1 },
85+
errors: [],
86+
},
87+
)
88+
},
89+
})
90+
91+
Deno.test({
92+
name: 'parse: empty file with whitespace',
93+
fn() {
94+
const text = ' \r\t \r\n \n '
95+
const ret = main.parse(text)
96+
97+
assertEquals(
98+
ret,
99+
{
100+
ast: {
101+
id: main.AST_NODE_PROGRAM,
102+
functions: [],
103+
},
104+
state: { pos: 1 },
105+
errors: [],
106+
},
107+
)
108+
},
109+
})
110+
111+
Deno.test({
112+
name: 'parse: random scanner error',
113+
fn() {
114+
const text = ' \r# \r\n \n '
115+
const ret = main.parse(text)
116+
117+
assertEquals(
118+
ret,
119+
{
120+
ast: {
121+
id: main.AST_NODE_PROGRAM,
122+
functions: [],
123+
},
124+
state: { pos: 2 },
125+
errors: [{
126+
location: {
127+
column: 1,
128+
line: 2,
129+
pos: 4,
130+
},
131+
message: 'scanner error token',
132+
}],
133+
},
134+
)
135+
},
136+
})
137+
138+
Deno.test({
139+
name: 'parse: trivial main function',
140+
fn() {
141+
const text = 'fn main() {\n}'
142+
const ret = main.parse(text)
143+
144+
assertEquals(
145+
ret,
146+
{
147+
ast: {
148+
id: main.AST_NODE_PROGRAM,
149+
functions: [{
150+
id: main.AST_NODE_FUNCTION,
151+
name: 'main',
152+
statements: [],
153+
}],
154+
},
155+
state: { pos: 10 },
156+
errors: [],
157+
},
76158
)
77159
},
78160
})
File renamed without changes.

def/cmin.l

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
%option noyywrap
2+
3+
%{
4+
#include <stdio.h>
5+
6+
#define YY_DECL int yylex()
7+
8+
#include "calc.tab.h"
9+
10+
%}
11+
12+
%%
13+
14+
[ \t] ; // ignore all whitespace
15+
[0-9]+\.[0-9]+ {yylval.fval = atof(yytext); return T_FLOAT;}
16+
[0-9]+ {yylval.ival = atoi(yytext); return T_INT;}
17+
\n {return T_NEWLINE;}
18+
"+" {return T_PLUS;}
19+
"-" {return T_MINUS;}
20+
"*" {return T_MULTIPLY;}
21+
"/" {return T_DIVIDE;}
22+
"(" {return T_LEFT;}
23+
")" {return T_RIGHT;}
24+
"exit" {return T_QUIT;}
25+
"quit" {return T_QUIT;}
26+
27+
%%

0 commit comments

Comments
 (0)