-
Notifications
You must be signed in to change notification settings - Fork 0
/
analyzer.js
98 lines (92 loc) · 3.26 KB
/
analyzer.js
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
import fs from "fs"
import ohm from "ohm-js"
import * as core from "./core.js"
const astroGrammar = ohm.grammar(fs.readFileSync("src/astro.ohm"))
// Throw an error message that takes advantage of Ohm's messaging.
// If you supply an Ohm tree node as the second parameter, this will
// use Ohm's cool reporting mechanism.
function error(message, node) {
if (node) {
throw new Error(`${node.source.getLineAndColumnMessage()}${message}`)
}
throw new Error(message)
}
function check(condition, message, node) {
if (!condition) error(message, node)
}
export default function analyze(sourceCode) {
const context = new Map()
const analyzer = astroGrammar.createSemantics().addOperation("rep", {
Program(statements) {
return new core.Program(statements.rep())
},
Statement_assignment(id, _eq, e, _semicolon) {
const initializer = e.rep()
let entity = context.get(id.sourceString)
if (!entity) {
entity = new core.Variable(id.sourceString, "NUM", "RW")
context.set(id.sourceString, entity)
} else {
check(entity?.type === "NUM", "Cannot assign", id)
check(entity?.access === "RW", `${id.sourceString} not writable`, id)
return new core.Assignment(variable, initializer)
}
},
Statement_call(id, args, _semicolon) {
const [entity, argList] = [context.get(id.sourceString), args.rep()]
check(entity !== undefined, `${id.sourceString} not defined`, id)
check(entity?.constructor === core.Procedure, "Procedure expected", id)
check(
argList.length === entity?.paramCount,
"Wrong number of arguments",
args
)
return new core.ProcedureCall(entity, argList)
},
Args(_leftParen, expressions, _rightParen) {
return expressions.asIteration().rep()
},
Exp_binary(left, op, right) {
return new core.BinaryExpression(op.rep(), left.rep(), right.rep())
},
Term_binary(left, op, right) {
return new core.BinaryExpression(op.rep(), left.rep(), right.rep())
},
Factor_binary(left, _op, right) {
return new core.BinaryExpression(op.rep(), left.rep(), right.rep())
},
Primary_parens(_leftParen, e, _rightParen) {
return e.rep()
},
Primary_num(num) {
return Number(num.sourceString)
},
Primary_id(id) {
// In Astro, functions and procedures never stand alone
const entity = context.get(id.sourceString)
check(entity !== undefined, `${id.sourceString} not defined`, id)
check(entity?.type === "NUM", `Expected type number`, id)
return entity
},
Primary_call(id, args) {
const [entity, argList] = [context.get(id.sourceString), args.rep()]
check(entity !== undefined, `${id.sourceString} not defined`, id)
check(entity?.type === "FUNC", "Function expected", id)
check(
argList.length === entity?.paramCount,
"Wrong number of arguments",
args
)
return core.FunctionCall(entity, argList)
},
_iter(...nodes) {
return nodes.map(node => node.rep())
},
})
for (const [name, entity] of Object.entries(core.standardLibrary)) {
context.set(name, entity)
}
const match = astroGrammar.match(sourceCode)
if (!match.succeeded()) error(match.message)
return analyzer(match).rep()
}