Skip to content

Commit f4328c7

Browse files
Merge pull request #2 from patrickroberts/feature/compiler
Feature/compiler
2 parents 7595fae + bc75804 commit f4328c7

File tree

13 files changed

+241
-35
lines changed

13 files changed

+241
-35
lines changed

src/compiler/compile.ts

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/* eslint-disable prefer-object-spread, prefer-spread */
2+
import Complex from '../complex';
3+
import { E, I, PI } from '../constants';
4+
import { BinaryExpression, Expression, UnaryExpression } from '../expressions';
5+
import parse from '../parser';
6+
import { acos, acosh, add, asin, asinh, atan, atanh, cbrt, conj, cos, cosh, div, exp, from, log, mod, mul, pow, proj, sin, sinh, sqrt, sub, tan, tanh, trunc } from '../static';
7+
8+
export type Value = Complex | ((...args: Complex[]) => Complex);
9+
10+
export type Bindings = Record<string, Value>;
11+
12+
export const bindings: Bindings = {
13+
acos,
14+
acosh,
15+
asin,
16+
asinh,
17+
atan,
18+
atanh,
19+
cbrt,
20+
conj,
21+
cos,
22+
cosh,
23+
e: E,
24+
exp,
25+
i: I,
26+
log,
27+
mod,
28+
pi: PI,
29+
pow,
30+
proj,
31+
sin,
32+
sinh,
33+
sqrt,
34+
tan,
35+
tanh,
36+
trunc,
37+
};
38+
39+
const unexpectedExpressionType = (type: never) => new TypeError(`Unexpected expression type ${type}`);
40+
41+
const binary: Record<BinaryExpression['operator'], (left: Complex, right: Complex) => Complex> = {
42+
'+': add,
43+
'-': sub,
44+
'*': mul,
45+
'/': div,
46+
'**': pow,
47+
'%': mod,
48+
};
49+
50+
const unary: Record<UnaryExpression['operator'], (arg: Complex) => Complex> = {
51+
'+': (arg) => arg,
52+
'-': (arg) => new Complex(0 - arg._real, 0 - arg._imag, arg._abs, 0 - arg._arg, arg._has),
53+
};
54+
55+
const asComplexOrThrow = (value: Value) => {
56+
if (typeof value !== 'object') {
57+
throw new TypeError(`${typeof value} is not an object`);
58+
}
59+
60+
return value;
61+
};
62+
63+
const asFunctionOrThrow = (value: Value) => {
64+
if (typeof value !== 'function') {
65+
throw new TypeError(`${typeof value} is not a function`);
66+
}
67+
68+
return value;
69+
};
70+
71+
const hasOwnProperty = (object: unknown, propertyKey: PropertyKey) => (
72+
Object.prototype.hasOwnProperty.call(object, propertyKey)
73+
);
74+
75+
const generate = (expression: Expression<Complex>): (variables: Bindings) => Value => {
76+
const { type } = expression;
77+
78+
switch (type) {
79+
case 'BinaryExpression': {
80+
const operator = binary[expression.operator];
81+
const left = generate(expression.left);
82+
const right = generate(expression.right);
83+
return (variables) => operator(
84+
asComplexOrThrow(left(variables)),
85+
asComplexOrThrow(right(variables)),
86+
);
87+
}
88+
case 'CallExpression': {
89+
const callee = generate(expression.callee);
90+
const args = expression.arguments.map(generate);
91+
return (variables) => asFunctionOrThrow(callee(variables)).apply(
92+
undefined,
93+
args.map((argument) => asComplexOrThrow(argument(variables))),
94+
);
95+
}
96+
case 'Identifier': {
97+
const { name } = expression;
98+
return (variables) => {
99+
if (!hasOwnProperty(variables, name)) {
100+
throw new ReferenceError(`${name} is not defined`);
101+
}
102+
103+
return variables[name];
104+
};
105+
}
106+
case 'Literal': {
107+
const { value } = expression;
108+
return () => value;
109+
}
110+
case 'UnaryExpression': {
111+
const operator = unary[expression.operator];
112+
const argument = generate(expression.argument);
113+
return (variables) => operator(asComplexOrThrow(argument(variables)));
114+
}
115+
default:
116+
throw unexpectedExpressionType(type);
117+
}
118+
};
119+
120+
const isConstant = (expression: Expression<unknown>, constants: Bindings): boolean => {
121+
const { type } = expression;
122+
123+
switch (type) {
124+
case 'BinaryExpression':
125+
return isConstant(expression.left, constants) && isConstant(expression.right, constants);
126+
case 'CallExpression':
127+
return isConstant(expression.callee, constants)
128+
&& expression.arguments.every((argument) => isConstant(argument, constants));
129+
case 'Identifier':
130+
return hasOwnProperty(constants, expression.name);
131+
case 'Literal':
132+
return true;
133+
case 'UnaryExpression':
134+
return isConstant(expression.argument, constants);
135+
default:
136+
throw unexpectedExpressionType(type);
137+
}
138+
};
139+
140+
const asLiteralIfConstant = (
141+
expression: Expression<Complex>, constants: Bindings,
142+
): Expression<Complex> => {
143+
if (!isConstant(expression, constants)) {
144+
return expression;
145+
}
146+
147+
const evaluate = generate(expression);
148+
return { type: 'Literal', value: asComplexOrThrow(evaluate(constants)) };
149+
};
150+
151+
const transform = (expression: Expression<number>, constants: Bindings): Expression<Complex> => {
152+
const { type } = expression;
153+
154+
switch (type) {
155+
case 'BinaryExpression':
156+
return asLiteralIfConstant({
157+
type,
158+
operator: expression.operator,
159+
left: transform(expression.left, constants),
160+
right: transform(expression.right, constants),
161+
}, constants);
162+
case 'CallExpression':
163+
return asLiteralIfConstant({
164+
type,
165+
callee: expression.callee,
166+
arguments: expression.arguments.map((argument) => transform(argument, constants)),
167+
}, constants);
168+
case 'Identifier':
169+
return expression;
170+
case 'Literal':
171+
return {
172+
type,
173+
value: from(expression.value),
174+
};
175+
case 'UnaryExpression':
176+
return asLiteralIfConstant({
177+
type,
178+
operator: expression.operator,
179+
argument: transform(expression.argument, constants),
180+
}, constants);
181+
default:
182+
throw unexpectedExpressionType(type);
183+
}
184+
};
185+
186+
const compile = (expression: string, constants: Bindings = {}): (variables: Bindings) => Value => {
187+
const record = Object.assign({}, bindings, constants);
188+
const evaluate = generate(transform(parse(expression), record));
189+
return (variables) => evaluate(Object.assign({}, record, variables));
190+
};
191+
192+
export default compile;

src/compiler/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Bindings, Value, bindings, default } from './compile';

src/expressions/binary.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Expression } from '.';
2+
3+
export default interface BinaryExpression<T = number> {
4+
type: 'BinaryExpression';
5+
operator: '+' | '-' | '*' | '/' | '%' | '**';
6+
left: Expression<T>;
7+
right: Expression<T>;
8+
}

src/expressions/call.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Expression, Identifier } from '.';
2+
3+
export default interface CallExpression<T = number> {
4+
type: 'CallExpression';
5+
callee: Identifier;
6+
arguments: Expression<T>[];
7+
}

src/expressions/expression.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { BinaryExpression, CallExpression, Identifier, Literal, UnaryExpression } from '.';
2+
3+
type Expression<T = number> =
4+
BinaryExpression<T> | CallExpression<T> | UnaryExpression<T> | Identifier | Literal<T>;
5+
6+
export default Expression;

src/expressions/identifier.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default interface Identifier {
2+
type: 'Identifier';
3+
name: string;
4+
}

src/expressions/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export { default as BinaryExpression } from './binary';
2+
export { default as CallExpression } from './call';
3+
export { default as Expression } from './expression';
4+
export { default as Identifier } from './identifier';
5+
export { default as Literal } from './literal';
6+
export { default as UnaryExpression } from './unary';

src/expressions/literal.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default interface Literal<T = number> {
2+
type: 'Literal';
3+
value: T;
4+
}

src/expressions/unary.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Expression } from '.';
2+
3+
export default interface UnaryExpression<T = number> {
4+
type: 'UnaryExpression';
5+
operator: '+' | '-';
6+
argument: Expression<T>;
7+
}

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
export { default as Complex } from './complex';
22
export { E, I, LN10, LN2, LOG10E, LOG2E, PI, SQRT1_2, SQRT2 } from './constants';
3+
export { BinaryExpression, CallExpression, Expression, Identifier, Literal, UnaryExpression } from './expressions';
34
export {
45
acos, acosh, add, asin, asinh, atan, atanh, cartesian, cbrt, conj, cos, cosh, div, exp, from, log,
56
mod, mul, polar, pow, proj, sin, sinh, sqrt, sub, tan, tanh, trunc,
67
} from './static';
7-
export { BinaryExpression, CallExpression, Expression, Identifier, Literal, UnaryExpression, parse } from './parser';
8+
export { default as parse } from './parser';
9+
export { Bindings, Value, bindings, default as compile } from './compiler';

0 commit comments

Comments
 (0)