/
calculator.ts
158 lines (129 loc) · 4.45 KB
/
calculator.ts
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
/**
* Represents a slice of a text file.
*
* This object uses byte offsets to point to the right part of the text file.
* If you want to move the range you will have to modify both the start and the
* end offset.
*/
export class SourceTextSpan {
constructor(
public startOffset: number,
public endOffset: number
) {
}
/**
* Count how many characters there are in this slice.
*/
public get size() {
return this.endOffset - this.startOffset;
}
}
// This acts as a global counter that is incremented each time we create a new
// node. That way, we can hash nodes based on their ID and be sure there are no
// collisions.
let nextNodeId = 0;
// This is a forward-declaration of a function that will be generated by
// tsastgen. tsastgen does not explicitly need these declarations. It is only
// useful if you're going to use a generated function in this file.
export declare function isDefinition(value: any): value is Definition;
// This is our root node. You specify it on the command-line with --root-node.
// If you forget to add this flag, tsastgen will by default search for a root
// node named 'Syntax'.
// Every node that should be part of the AST should in some way inherit from
// the root node. If you forget to do this, tsastgen will just emit the
// class/interface without any transformations.
export class CalcNode {
public readonly id: number;
constructor(
public span: SourceTextSpan | null = null,
public parentNode: CalcNode | null = null
) {
this.id = nextNodeId++;
}
}
/**
* A sheet is just a text file with a named list of simple arithmetic expressions.
*
* Something like this:
*
* ```
* foo = (x + 2) / (y - 1)
*
* bar = 42;
* ```
*/
export class Sheet extends CalcNode {
// This map is used to efficiently look up a definition given only is name.
private definitionsByName = Object.create(null);
constructor(
public elements: SheetElement[],
span: SourceTextSpan | null = null,
parentNode: CalcNode | null = null,
) {
super(span, parentNode);
for (const element of elements) {
if (isDefinition(element)) {
this.definitionsByName[element.name.text] = element;
}
}
}
/**
* Get a definition using its name and return nothing if it was not found.
*
* @param name The name of the definition to search for.
*/
public getDefinition(name: string): Definition | null {
return this.definitionsByName[name] ?? null;
}
}
// All things that can be written inside a sheet should inherit from this node.
// In this example, this is just a Definition.
export interface SheetElement extends CalcNode {}
// This could be a token generated by a scanner/lexer.
export interface Identifier extends CalcNode {
text: string;
}
export interface Definition extends SheetElement {
name: Identifier;
expression: Expression;
}
export class Expression extends CalcNode {
constructor(
public tag: string | null = null,
span: SourceTextSpan | null = null,
parentNode: CalcNode | null = null
) {
super(span, parentNode);
}
}
// Because a constant expression only contains one field, you will very
// frequently be able to swap createConstantExpression(x) with just x. tsastgen
// applies some magic to make this work with all factory functions.
export interface ConstantExpression extends Expression {
value: number;
}
// Because this interfaces's string field does not overlap with
// ConstantExpression's value field, you will be able to use just strings or
// numbers when constructing a new Expression.
export interface ReferenceExpression extends Expression {
name: string;
}
// You can view this as a kind of 'mixin' that will be pushed inside every node
// that inherits from it. Note that this interface does not extend CalcNode,
// though in theory it could.
export interface BinaryExpression {
left: Expression;
right: Expression;
}
// Finally, our nodes that are able to calculate something!
export interface SubtractExpression extends BinaryExpression, Expression {}
export interface AddExpression extends BinaryExpression, Expression {}
export interface MultiplyExpression extends BinaryExpression, Expression {}
export interface DivideExpression extends BinaryExpression, Expression {}
// This type alias only consists of references to other AST node types, so a
// predicate isCommutativeExpression will be generated after you have run
// tsastgen.
export type CommutativeExpression
= AddExpression
| SubtractExpression
| MultiplyExpression;