Fix code in chapters
This commit is contained in:
parent
79f862f941
commit
112b3b19c8
@ -23,7 +23,7 @@ type Stmt = {
|
|||||||
type StmtKind =
|
type StmtKind =
|
||||||
| { type: "error" }
|
| { type: "error" }
|
||||||
// ...
|
// ...
|
||||||
| { type: "let", ident: string, value: Expr }
|
| { type: "return", expr?: Expr }
|
||||||
// ...
|
// ...
|
||||||
;
|
;
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class Parser {
|
|||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
private step() { this.currentToken = this.lexer.next() }
|
private step() { this.currentToken = this.lexer.next() }
|
||||||
private done(): bool { return this.currentToken == null; }
|
private done(): boolean { return this.currentToken == null; }
|
||||||
private current(): Token { return this.currentToken!; }
|
private current(): Token { return this.currentToken!; }
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ Also like the lexer, we'll have a `.test()` method in the parser, which will tes
|
|||||||
```ts
|
```ts
|
||||||
class Parser {
|
class Parser {
|
||||||
// ...
|
// ...
|
||||||
private test(type: string): bool {
|
private test(type: string): boolean {
|
||||||
return !this.done() && this.current().type === type;
|
return !this.done() && this.current().type === type;
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
@ -189,17 +189,17 @@ class Parser {
|
|||||||
public parseOperand(): Expr {
|
public parseOperand(): Expr {
|
||||||
// ...
|
// ...
|
||||||
if (this.test("ident")) {
|
if (this.test("ident")) {
|
||||||
const value = this.current().identValue;
|
const value = this.current().identValue!;
|
||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "ident", value }, pos);
|
return this.expr({ type: "ident", value }, pos);
|
||||||
}
|
}
|
||||||
if (this.test("int")) {
|
if (this.test("int")) {
|
||||||
const value = this.current().intValue;
|
const value = this.current().intValue!;
|
||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "int", value }, pos);
|
return this.expr({ type: "int", value }, pos);
|
||||||
}
|
}
|
||||||
if (this.test("string")) {
|
if (this.test("string")) {
|
||||||
const value = this.current().stringValue;
|
const value = this.current().stringValue!;
|
||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "string", value }, pos);
|
return this.expr({ type: "string", value }, pos);
|
||||||
}
|
}
|
||||||
@ -327,7 +327,7 @@ class Parser {
|
|||||||
this.report("expected ident");
|
this.report("expected ident");
|
||||||
return this.expr({ type: "error" }, pos);
|
return this.expr({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
const value = this.current().identValue;
|
const value = this.current().identValue!;
|
||||||
this.step();
|
this.step();
|
||||||
subject = this.expr({ type: "field", subject, value }, pos);
|
subject = this.expr({ type: "field", subject, value }, pos);
|
||||||
continue;
|
continue;
|
||||||
@ -715,21 +715,19 @@ class Parser {
|
|||||||
this.report("expected ident");
|
this.report("expected ident");
|
||||||
return this.stmt({ type: "error" }, pos);
|
return this.stmt({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
const ident = this.current().identValue;
|
const ident = this.current().identValue!;
|
||||||
this.step();
|
this.step();
|
||||||
if (!this.test("(")) {
|
if (!this.test("(")) {
|
||||||
this.report("expected '('");
|
this.report("expected '('");
|
||||||
return this.stmt({ type: "error" }, pos);
|
return this.stmt({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
const params = this.parseFnParams();
|
const params = this.parseFnParams();
|
||||||
if (!params.ok)
|
|
||||||
return this.stmt({ type: "error" }, pos);
|
|
||||||
if (!this.test("{")) {
|
if (!this.test("{")) {
|
||||||
this.report("expected block");
|
this.report("expected block");
|
||||||
return this.stmt({ type: "error" }, pos);
|
return this.stmt({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
const body = this.parseBlock();
|
const body = this.parseBlock();
|
||||||
return this.stmt({ type: "fn", ident, params: params.value, body }, pos);
|
return this.stmt({ type: "fn", ident, params, body }, pos);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -783,7 +781,7 @@ class Parser {
|
|||||||
public parseParam(): { ok: true, value: Param } | { ok: false } {
|
public parseParam(): { ok: true, value: Param } | { ok: false } {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
if (this.test("ident")) {
|
if (this.test("ident")) {
|
||||||
const ident = self.current().value;
|
const ident = this.current().value;
|
||||||
this.step();
|
this.step();
|
||||||
return { ok: true, value: { ident, pos } };
|
return { ok: true, value: { ident, pos } };
|
||||||
}
|
}
|
||||||
@ -966,6 +964,7 @@ class Parser {
|
|||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
private parseSingleLineBlockStmt(): Stmt {
|
private parseSingleLineBlockStmt(): Stmt {
|
||||||
|
const pos = this.pos();
|
||||||
if (this.test("let"))
|
if (this.test("let"))
|
||||||
return this.parseLet();
|
return this.parseLet();
|
||||||
if (this.test("return"))
|
if (this.test("return"))
|
||||||
@ -1011,6 +1010,7 @@ class Parser {
|
|||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
private parseMultiLineBlockExpr(): Expr {
|
private parseMultiLineBlockExpr(): Expr {
|
||||||
|
const pos = this.pos();
|
||||||
if (this.test("{"))
|
if (this.test("{"))
|
||||||
return this.parseBlock();
|
return this.parseBlock();
|
||||||
if (this.test("if"))
|
if (this.test("if"))
|
||||||
|
@ -85,12 +85,12 @@ function valueToString(value: Value): string {
|
|||||||
return value.value ? "true" : "false";
|
return value.value ? "true" : "false";
|
||||||
}
|
}
|
||||||
if (value.type === "array") {
|
if (value.type === "array") {
|
||||||
const valueStrings = result.values
|
const valueStrings = value.values
|
||||||
.map(value => value.toString());
|
.map(value => value.toString());
|
||||||
return `[${valueStrings.join(", ")}]`;
|
return `[${valueStrings.join(", ")}]`;
|
||||||
}
|
}
|
||||||
if (value.type === "struct") {
|
if (value.type === "struct") {
|
||||||
const fieldStrings = Object.entries(result.fields)
|
const fieldStrings = Object.entries(value.fields)
|
||||||
.map(([key, value]) => `${key}: ${valueToString(value)}`);
|
.map(([key, value]) => `${key}: ${valueToString(value)}`);
|
||||||
return `struct { ${fieldStrings.join(", ")} }`;
|
return `struct { ${fieldStrings.join(", ")} }`;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ type SymMap = { [ident: string]: Sym }
|
|||||||
class Syms {
|
class Syms {
|
||||||
private syms: SymMap = {};
|
private syms: SymMap = {};
|
||||||
|
|
||||||
public constructor(private parent?: SymMap) {}
|
public constructor(private parent?: Syms) {}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -255,8 +255,6 @@ We'll want a *root* symbol table, which stores all the predefined symbols. We al
|
|||||||
class Evaluator {
|
class Evaluator {
|
||||||
private root = new Syms();
|
private root = new Syms();
|
||||||
// ...
|
// ...
|
||||||
public defineBuiltins() { /*...*/ }
|
|
||||||
// ...
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -292,12 +290,12 @@ Let's make a function `evalExpr` for evaluating expressions.
|
|||||||
```ts
|
```ts
|
||||||
class Evaluator {
|
class Evaluator {
|
||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr): Flow {
|
public evalExpr(expr: Expr, syms: Expr): Flow {
|
||||||
if (expr.type === "error") {
|
if (expr.kind.type === "error") {
|
||||||
throw new Error("error in AST");
|
throw new Error("error in AST");
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
throw new Error(`unknown expr type "${expr.type}"`);
|
throw new Error(`unknown expr type "${expr.kind.type}"`);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -312,10 +310,10 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "ident") {
|
if (expr.kind.type === "ident") {
|
||||||
const result = syms.get(expr.value);
|
const result = syms.get(expr.kind.value);
|
||||||
if (!result.ok)
|
if (!result.ok)
|
||||||
throw new Error(`undefined symbol "${expr.value}"`);
|
throw new Error(`undefined symbol "${expr.kind.value}"`);
|
||||||
return flowValue(result.sym.value);
|
return flowValue(result.sym.value);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
@ -331,17 +329,17 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "null") {
|
if (expr.kind.type === "null") {
|
||||||
return flowValue({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
if (expr.type === "int") {
|
if (expr.kind.type === "int") {
|
||||||
return flowValue({ type: "int", value: expr.value });
|
return flowValue({ type: "int", value: expr.kind.value });
|
||||||
}
|
}
|
||||||
if (expr.type === "string") {
|
if (expr.kind.type === "string") {
|
||||||
return flowValue({ type: "string", value: expr.value });
|
return flowValue({ type: "string", value: expr.kind.value });
|
||||||
}
|
}
|
||||||
if (expr.type === "bool") {
|
if (expr.kind.type === "bool") {
|
||||||
return flowValue({ type: "int", value: expr.value });
|
return flowValue({ type: "bool", value: expr.kind.value });
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -358,8 +356,8 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "group") {
|
if (expr.kind.type === "group") {
|
||||||
return this.evalExpr(expr.expr, syms);
|
return this.evalExpr(expr.kind.expr, syms);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -376,15 +374,15 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "field") {
|
if (expr.kind.type === "field") {
|
||||||
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.subject, syms));
|
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.kind.subject, syms));
|
||||||
if (!subject)
|
if (!subject)
|
||||||
return subjectFlow;
|
return subjectFlow;
|
||||||
if (subject.type !== "struct")
|
if (subject.type !== "struct")
|
||||||
throw new Error(`cannot use field operator on ${subject.type} value`);
|
throw new Error(`cannot use field operator on ${subject.type} value`);
|
||||||
if (!(expr.value in subject.fields))
|
if (!(expr.kind.value in subject.fields))
|
||||||
throw new Error(`field ${expr.value} does not exist on struct`);
|
throw new Error(`field ${expr.kind.value} does not exist on struct`);
|
||||||
return subject.fields[expr.value];
|
return flowValue(subject.fields[expr.kind.value]);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -401,11 +399,11 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "index") {
|
if (expr.kind.type === "index") {
|
||||||
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.subject, syms));
|
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.kind.subject, syms));
|
||||||
if (!subject)
|
if (!subject)
|
||||||
return subjectFlow;
|
return subjectFlow;
|
||||||
const [value, valueFlow] = expectValue(this.evalExpr(expr.value, syms));
|
const [value, valueFlow] = expectValue(this.evalExpr(expr.kind.value, syms));
|
||||||
if (!value)
|
if (!value)
|
||||||
return valueFlow;
|
return valueFlow;
|
||||||
if (subject.type === "struct") {
|
if (subject.type === "struct") {
|
||||||
@ -431,11 +429,11 @@ class Evaluator {
|
|||||||
if (subject.type === "string") {
|
if (subject.type === "string") {
|
||||||
if (value.type !== "int")
|
if (value.type !== "int")
|
||||||
throw new Error(`cannot index into string with ${value.type} value`);
|
throw new Error(`cannot index into string with ${value.type} value`);
|
||||||
if (value.value >= subject.values.length)
|
if (value.value >= subject.value.length)
|
||||||
throw new Error("index out of range");
|
throw new Error("index out of range");
|
||||||
if (value.value < 0) {
|
if (value.value < 0) {
|
||||||
const negativeIndex = subject.values.length + value.value;
|
const negativeIndex = subject.value.length + value.value;
|
||||||
if (negativeIndex < 0 || negativeIndex >= subject.values.length)
|
if (negativeIndex < 0 || negativeIndex >= subject.value.length)
|
||||||
throw new Error("index out of range");
|
throw new Error("index out of range");
|
||||||
return flowValue({ type: "int", value: subject.value.charCodeAt(negativeIndex) });
|
return flowValue({ type: "int", value: subject.value.charCodeAt(negativeIndex) });
|
||||||
}
|
}
|
||||||
@ -460,18 +458,18 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "call") {
|
if (expr.kind.type === "call") {
|
||||||
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.subject, syms));
|
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.kind.subject, syms));
|
||||||
if (!subject)
|
if (!subject)
|
||||||
return subjectFlow;
|
return subjectFlow;
|
||||||
const args: Value[] = [];
|
const args: Value[] = [];
|
||||||
for (const arg of expr.args) {
|
for (const arg of expr.kind.args) {
|
||||||
const [value, valueFlow] = expectValue(this.evalExpr(expr.value, syms));
|
const [value, valueFlow] = expectValue(this.evalExpr(arg, syms));
|
||||||
if (!value)
|
if (!value)
|
||||||
return valueFlow;
|
return valueFlow;
|
||||||
args.push(value);
|
args.push(value);
|
||||||
}
|
}
|
||||||
if (subject.type === "builtin") {
|
if (subject.type === "builtin_fn") {
|
||||||
return this.executeBuiltin(subject.name, args, syms);
|
return this.executeBuiltin(subject.name, args, syms);
|
||||||
}
|
}
|
||||||
if (subject.type !== "fn")
|
if (subject.type !== "fn")
|
||||||
@ -482,7 +480,7 @@ class Evaluator {
|
|||||||
if (fnDef.params.length !== args.length)
|
if (fnDef.params.length !== args.length)
|
||||||
throw new Error("incorrect amount of arguments in call to function");
|
throw new Error("incorrect amount of arguments in call to function");
|
||||||
let fnScopeSyms = new Syms(this.root);
|
let fnScopeSyms = new Syms(this.root);
|
||||||
for (const [i, param] in fnDef.params.entries()) {
|
for (const [i, param] of fnDef.params.entries()) {
|
||||||
fnScopeSyms.define(param.ident, { value: args[i], pos: param.pos });
|
fnScopeSyms.define(param.ident, { value: args[i], pos: param.pos });
|
||||||
}
|
}
|
||||||
const flow = this.evalExpr(fnDef.body, fnScopeSyms);
|
const flow = this.evalExpr(fnDef.body, fnScopeSyms);
|
||||||
@ -509,9 +507,9 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "unary") {
|
if (expr.kind.type === "unary") {
|
||||||
if (expr.unaryType === "not") {
|
if (expr.kind.unaryType === "not") {
|
||||||
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.subject, syms));
|
const [subject, subjectFlow] = expectValue(this.evalExpr(expr.kind.subject, syms));
|
||||||
if (!subject)
|
if (!subject)
|
||||||
return subjectFlow;
|
return subjectFlow;
|
||||||
if (subject.type === "bool") {
|
if (subject.type === "bool") {
|
||||||
@ -519,7 +517,7 @@ class Evaluator {
|
|||||||
}
|
}
|
||||||
throw new Error(`cannot apply not operator on type ${subject.type}`);
|
throw new Error(`cannot apply not operator on type ${subject.type}`);
|
||||||
}
|
}
|
||||||
throw new Error(`unhandled unary operation ${expr.unaryType}`);
|
throw new Error(`unhandled unary operation ${expr.kind.unaryType}`);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -538,23 +536,23 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "binary") {
|
if (expr.kind.type === "binary") {
|
||||||
const [left, leftFlow] = expectValue(this.evalExpr(expr.left, syms));
|
const [left, leftFlow] = expectValue(this.evalExpr(expr.kind.left, syms));
|
||||||
if (!left)
|
if (!left)
|
||||||
return leftFlow;
|
return leftFlow;
|
||||||
const [right, rightFlow] = expectValue(this.evalExpr(expr.right, syms));
|
const [right, rightFlow] = expectValue(this.evalExpr(expr.kind.right, syms));
|
||||||
if (!right)
|
if (!right)
|
||||||
return rightFlow;
|
return rightFlow;
|
||||||
if (expr.binaryType === "+") {
|
if (expr.kind.binaryType === "+") {
|
||||||
if (left.type === "int" && right.type === "int") {
|
if (left.type === "int" && right.type === "int") {
|
||||||
return flowValue({ type: "int", value: left.value + right.value });
|
return flowValue({ type: "int", value: left.value + right.value });
|
||||||
}
|
}
|
||||||
if (left.type === "string" && right.type === "string") {
|
if (left.type === "string" && right.type === "string") {
|
||||||
return flowValue({ type: "string", value: left.value + right.value });
|
return flowValue({ type: "string", value: left.value + right.value });
|
||||||
}
|
}
|
||||||
throw new Error(`cannot apply ${expr.binaryType} operator on types ${left.type} and ${right.type}`);
|
throw new Error(`cannot apply ${expr.kind.binaryType} operator on types ${left.type} and ${right.type}`);
|
||||||
}
|
}
|
||||||
if (expr.binaryType === "==") {
|
if (expr.kind.binaryType === "==") {
|
||||||
if (left.type === "null" && right.type === "null") {
|
if (left.type === "null" && right.type === "null") {
|
||||||
return flowValue({ type: "bool", value: true });
|
return flowValue({ type: "bool", value: true });
|
||||||
}
|
}
|
||||||
@ -564,12 +562,13 @@ class Evaluator {
|
|||||||
if (left.type !== "null" && right.type === "null") {
|
if (left.type !== "null" && right.type === "null") {
|
||||||
return flowValue({ type: "bool", value: false });
|
return flowValue({ type: "bool", value: false });
|
||||||
}
|
}
|
||||||
if (["int", "string", "bool"].includes(left.type) && left.type === right.type) {
|
if ((left.type === "int" || left.type === "string" || left.type === "bool")
|
||||||
|
&& left.type === right.type) {
|
||||||
return flowValue({ type: "bool", value: left.value === right.value });
|
return flowValue({ type: "bool", value: left.value === right.value });
|
||||||
}
|
}
|
||||||
throw new Error(`cannot apply ${expr.binaryType} operator on types ${left.type} and ${right.type}`);
|
throw new Error(`cannot apply ${expr.kind.binaryType} operator on types ${left.type} and ${right.type}`);
|
||||||
}
|
}
|
||||||
throw new Error(`unhandled binary operation ${expr.unaryType}`);
|
throw new Error(`unhandled binary operation ${expr.kind.binaryType}`);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -594,16 +593,16 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "if") {
|
if (expr.kind.type === "if") {
|
||||||
const [condition, conditionFlow] = expectValue(this.evalExpr(expr.condition, syms));
|
const [cond, condFlow] = expectValue(this.evalExpr(expr.kind.cond, syms));
|
||||||
if (!condition)
|
if (!cond)
|
||||||
return conditionFlow;
|
return condFlow;
|
||||||
if (condition.type !== "bool")
|
if (cond.type !== "bool")
|
||||||
throw new Error(`cannot use value of type ${subject.type} as condition`);
|
throw new Error(`cannot use value of type ${cond.type} as condition`);
|
||||||
if (condition.value)
|
if (cond.value)
|
||||||
return this.evalExpr(expr.truthy, syms);
|
return this.evalExpr(expr.kind.truthy, syms);
|
||||||
if (expr.falsy)
|
if (expr.kind.falsy)
|
||||||
return this.evalExpr(exor.falsy, syms);
|
return this.evalExpr(expr.kind.falsy, syms);
|
||||||
return flowValue({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
@ -623,9 +622,9 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "loop") {
|
if (expr.kind.type === "loop") {
|
||||||
while (true) {
|
while (true) {
|
||||||
const flow = this.evaluate(expr.body, syms);
|
const flow = this.evalExpr(expr.kind.body, syms);
|
||||||
if (flow.type === "break")
|
if (flow.type === "break")
|
||||||
return flowValue(flow.value);
|
return flowValue(flow.value);
|
||||||
if (flow.type !== "value")
|
if (flow.type !== "value")
|
||||||
@ -650,15 +649,15 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalExpr(expr: Expr, syms: Syms): Flow {
|
public evalExpr(expr: Expr, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (expr.type === "block") {
|
if (expr.kind.type === "block") {
|
||||||
let scopeSyms = new Syms(syms);
|
let scopeSyms = new Syms(syms);
|
||||||
for (const stmt of block.stmts) {
|
for (const stmt of expr.kind.stmts) {
|
||||||
const flow = this.evalStmt(stmt, scopeSyms);
|
const flow = this.evalStmt(stmt, scopeSyms);
|
||||||
if (flow.type !== "value")
|
if (flow.type !== "value")
|
||||||
return flow;
|
return flow;
|
||||||
}
|
}
|
||||||
if (expr.expr)
|
if (expr.kind.expr)
|
||||||
return this.evalExpr(expr.expr, scopeSyms);
|
return this.evalExpr(expr.kind.expr, scopeSyms);
|
||||||
return flowValue({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
@ -682,11 +681,11 @@ For evaluating statements, we'll make a function called `evalStmt` .
|
|||||||
class Evaluator {
|
class Evaluator {
|
||||||
// ...
|
// ...
|
||||||
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
||||||
if (stmt.type === "error") {
|
if (stmt.kind.type === "error") {
|
||||||
throw new Error("error in AST");
|
throw new Error("error in AST");
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
throw new Error(`unknown stmt type "${expr.type}"`);
|
throw new Error(`unknown stmt type "${stmt.kind.type}"`);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -703,10 +702,10 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (stmt.type === "break") {
|
if (stmt.kind.type === "break") {
|
||||||
if (!stmt.expr)
|
if (!stmt.kind.expr)
|
||||||
return { type: "break" };
|
return { type: "break", value: { type: "null" } };
|
||||||
const [value, valueFlow] = expectValue(this.evalExpr(stmt.expr));
|
const [value, valueFlow] = expectValue(this.evalExpr(stmt.kind.expr, syms));
|
||||||
if (!value)
|
if (!value)
|
||||||
return valueFlow;
|
return valueFlow;
|
||||||
return { type: "break", value };
|
return { type: "break", value };
|
||||||
@ -728,10 +727,10 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (stmt.type === "return") {
|
if (stmt.kind.type === "return") {
|
||||||
if (!stmt.expr)
|
if (!stmt.kind.expr)
|
||||||
return { type: "return" };
|
return { type: "return", value: { type: "null" } };
|
||||||
const [value, valueFlow] = expectValue(this.evalExpr(stmt.expr));
|
const [value, valueFlow] = expectValue(this.evalExpr(stmt.kind.expr, syms));
|
||||||
if (!value)
|
if (!value)
|
||||||
return valueFlow;
|
return valueFlow;
|
||||||
return { type: "return", value };
|
return { type: "return", value };
|
||||||
@ -753,8 +752,8 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (stmt.type === "expr") {
|
if (stmt.kind.type === "expr") {
|
||||||
return this.evalExpr(stmt.expr);
|
return this.evalExpr(stmt.kind.expr, syms);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -771,41 +770,42 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (stmt.type === "assign") {
|
if (stmt.kind.type === "assign") {
|
||||||
const [value, valueFlow] = expectValue(this.evalExpr(stmt.value, syms));
|
const [value, valueFlow] = expectValue(this.evalExpr(stmt.kind.value, syms));
|
||||||
if (!value)
|
if (!value)
|
||||||
return valueFlow;
|
return valueFlow;
|
||||||
if (stmt.subject.type === "ident") {
|
if (stmt.kind.subject.kind.type === "ident") {
|
||||||
const ident = stmt.subject.value;
|
const ident = stmt.kind.subject.kind.value;
|
||||||
const { ok: found, sym } = syms.get(ident);
|
const getResult = syms.get(ident);
|
||||||
if (!found)
|
if (!getResult.ok)
|
||||||
throw new Error(`cannot assign to undefined symbol "${ident}"`);
|
throw new Error(`cannot assign to undefined symbol "${ident}"`);
|
||||||
|
const { sym } = getResult;
|
||||||
if (sym.value.type !== value.type)
|
if (sym.value.type !== value.type)
|
||||||
throw new Error(`cannot assign value of type ${value.type} to symbol originally declared ${sym.value.type}`);
|
throw new Error(`cannot assign value of type ${value.type} to symbol originally declared ${sym.value.type}`);
|
||||||
sym.value = value;
|
sym.value = value;
|
||||||
return valueFlow({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
if (stmt.subject.type === "field") {
|
if (stmt.kind.subject.type === "field") {
|
||||||
const [subject, subjectFlow] = expectValue(this.evalExpr(stmt.subject.subject, syms));
|
const [subject, subjectFlow] = expectValue(this.evalExpr(stmt.kind.subject.kind.subject, syms));
|
||||||
if (!subject)
|
if (!subject)
|
||||||
return subjectFlow;
|
return subjectFlow;
|
||||||
if (subject.type !== "struct")
|
if (subject.type !== "struct")
|
||||||
throw new Error(`cannot use field operator on ${subject.type} value`);
|
throw new Error(`cannot use field operator on ${subject.type} value`);
|
||||||
subject.fields[stmt.subject.value] = value;
|
subject.fields[stmt.kind.subject.kind.value] = value;
|
||||||
return valueFlow({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
if (stmt.subject.type === "index") {
|
if (stmt.kind.subject.kind.type === "index") {
|
||||||
const [subject, subjectFlow] = expectValue(this.evalExpr(stmt.subject.subject, syms));
|
const [subject, subjectFlow] = expectValue(this.evalExpr(stmt.kind.subject.kind.subject, syms));
|
||||||
if (!subject)
|
if (!subject)
|
||||||
return subjectFlow;
|
return subjectFlow;
|
||||||
const [index, indexFlow] = expectValue(this.evalExpr(stmt.subject.value, syms));
|
const [index, indexFlow] = expectValue(this.evalExpr(stmt.kind.subject.kind.value, syms));
|
||||||
if (!index)
|
if (!index)
|
||||||
return valueFlow;
|
return indexFlow;
|
||||||
if (subject.type === "struct") {
|
if (subject.type === "struct") {
|
||||||
if (index.type !== "string")
|
if (index.type !== "string")
|
||||||
throw new Error(`cannot index into struct with ${index.type} value`);
|
throw new Error(`cannot index into struct with ${index.type} value`);
|
||||||
subject.fields[index.value] = value;
|
subject.fields[index.value] = value;
|
||||||
return valueFlow({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
if (subject.type === "array") {
|
if (subject.type === "array") {
|
||||||
if (index.type !== "int")
|
if (index.type !== "int")
|
||||||
@ -813,19 +813,19 @@ class Evaluator {
|
|||||||
if (index.value >= subject.values.length)
|
if (index.value >= subject.values.length)
|
||||||
throw new Error("index out of range");
|
throw new Error("index out of range");
|
||||||
if (index.value >= 0) {
|
if (index.value >= 0) {
|
||||||
subject.value[index.value] = value;
|
subject.values[index.value] = value;
|
||||||
} else {
|
} else {
|
||||||
const negativeIndex = subject.values.length + index.value;
|
const negativeIndex = subject.values.length + index.value;
|
||||||
if (negativeIndex < 0 || negativeIndex >= subject.values.length)
|
if (negativeIndex < 0 || negativeIndex >= subject.values.length)
|
||||||
throw new Error("index out of range");
|
throw new Error("index out of range");
|
||||||
subject.value[negativeIndex] = value;
|
subject.values[negativeIndex] = value;
|
||||||
return valueFlow({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
return valueFlow({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
throw new Error(`cannot use field operator on ${subject.type} value`);
|
throw new Error(`cannot use field operator on ${subject.type} value`);
|
||||||
}
|
}
|
||||||
throw new Error(`cannot assign to ${stmt.subject.type} expression`);
|
throw new Error(`cannot assign to ${stmt.kind.subject.kind.type} expression`);
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -850,14 +850,14 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (stmt.type === "let") {
|
if (stmt.kind.type === "let") {
|
||||||
if (syms.definedLocally(stmt.param.ident))
|
if (syms.definedLocally(stmt.kind.param.ident))
|
||||||
throw new Error(`cannot redeclare symbol "${stmt.param.ident}"`);
|
throw new Error(`cannot redeclare symbol "${stmt.kind.param.ident}"`);
|
||||||
const [value, valueFlow] = expectValue(this.evalExpr(stmt.value, syms));
|
const [value, valueFlow] = expectValue(this.evalExpr(stmt.kind.value, syms));
|
||||||
if (!value)
|
if (!value)
|
||||||
return valueFlow;
|
return valueFlow;
|
||||||
syms.define(stmt.param.ident, value);
|
syms.define(stmt.kind.param.ident, { value: value, pos: stmt.pos });
|
||||||
return valueFlow({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -874,10 +874,10 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
public evalStmt(stmt: Stmt, syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (stmt.type === "fn") {
|
if (stmt.kind.type === "fn") {
|
||||||
if (syms.definedLocally(stmt.ident))
|
if (syms.definedLocally(stmt.kind.ident))
|
||||||
throw new Error(`cannot redeclare function "${stmt.ident}"`);
|
throw new Error(`cannot redeclare function "${stmt.kind.ident}"`);
|
||||||
const { params, body } = stmt;
|
const { params, body } = stmt.kind;
|
||||||
|
|
||||||
let paramNames: string[] = [];
|
let paramNames: string[] = [];
|
||||||
for (const param of params) {
|
for (const param of params) {
|
||||||
@ -888,8 +888,8 @@ class Evaluator {
|
|||||||
|
|
||||||
const id = this.fnDefs.length;
|
const id = this.fnDefs.length;
|
||||||
this.fnDefs.push({ params, body, id });
|
this.fnDefs.push({ params, body, id });
|
||||||
this.syms.define(stmt.ident, { type: "fn", fnDefId: id });
|
syms.define(stmt.kind.ident, { value: { type: "fn", fnDefId: id }, pos: stmt.pos });
|
||||||
return flowValue({ type: "none" });
|
return flowValue({ type: "null" });
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -908,7 +908,7 @@ class Evaluator {
|
|||||||
// ...
|
// ...
|
||||||
public evalStmts(stmts: Stmt[], syms: Syms) {
|
public evalStmts(stmts: Stmt[], syms: Syms) {
|
||||||
let scopeSyms = new Syms(syms);
|
let scopeSyms = new Syms(syms);
|
||||||
for (const stmt of block.stmts) {
|
for (const stmt of stmts) {
|
||||||
const flow = this.evalStmt(stmt, scopeSyms);
|
const flow = this.evalStmt(stmt, scopeSyms);
|
||||||
if (flow.type !== "value")
|
if (flow.type !== "value")
|
||||||
throw new Error(`${flow.type} on the loose!`);
|
throw new Error(`${flow.type} on the loose!`);
|
||||||
@ -1063,9 +1063,9 @@ class Evaluator {
|
|||||||
private executeBuiltin(name: string, args: Value[], syms: Syms): Flow {
|
private executeBuiltin(name: string, args: Value[], syms: Syms): Flow {
|
||||||
// ...
|
// ...
|
||||||
if (name === "println") {
|
if (name === "println") {
|
||||||
if (args.length < 1)
|
if (args.length < 1 || args[0].type !== "string")
|
||||||
throw new Error("incorrect arguments");
|
throw new Error("incorrect arguments");
|
||||||
let msg = args[0];
|
let msg = args[0].value;
|
||||||
for (const arg of args.slice(1)) {
|
for (const arg of args.slice(1)) {
|
||||||
if (!msg.includes("{}"))
|
if (!msg.includes("{}"))
|
||||||
throw new Error("incorrect arguments");
|
throw new Error("incorrect arguments");
|
||||||
@ -1151,7 +1151,7 @@ class Evaluator {
|
|||||||
if (args.length !== 1 || args[0].type !== "string")
|
if (args.length !== 1 || args[0].type !== "string")
|
||||||
throw new Error("incorrect arguments");
|
throw new Error("incorrect arguments");
|
||||||
const value = parseInt(args[0].value);
|
const value = parseInt(args[0].value);
|
||||||
if (value === NaN)
|
if (isNaN(value))
|
||||||
return flowValue({ type: "null" });
|
return flowValue({ type: "null" });
|
||||||
return flowValue({ type: "int", value });
|
return flowValue({ type: "int", value });
|
||||||
}
|
}
|
||||||
@ -1171,14 +1171,18 @@ Finally, we need a way to define the builtin functions in the symbol table.
|
|||||||
class Evaluator {
|
class Evaluator {
|
||||||
// ...
|
// ...
|
||||||
public defineBuiltins() {
|
public defineBuiltins() {
|
||||||
this.root.define("array", { type: "builtin_fn", name: "array" });
|
function defineBuiltin(syms: Syms, name: string) {
|
||||||
this.root.define("struct", { type: "builtin_fn", name: "struct" });
|
syms.define(name, { value: { type: "builtin_fn", name } });
|
||||||
this.root.define("array_push", { type: "builtin_fn", name: "array_push" });
|
}
|
||||||
this.root.define("array_len", { type: "builtin_fn", name: "array_len" });
|
|
||||||
this.root.define("string_concat", { type: "builtin_fn", name: "string_concat" });
|
defineBuiltin(this.root, "array");
|
||||||
this.root.define("string_len", { type: "builtin_fn", name: "string_len" });
|
defineBuiltin(this.root, "struct");
|
||||||
this.root.define("println", { type: "builtin_fn", name: "println" });
|
defineBuiltin(this.root, "array_push");
|
||||||
this.root.define("exit", { type: "builtin_fn", name: "exit" });
|
defineBuiltin(this.root, "array_len");
|
||||||
|
defineBuiltin(this.root, "string_concat");
|
||||||
|
defineBuiltin(this.root, "string_len");
|
||||||
|
defineBuiltin(this.root, "println");
|
||||||
|
defineBuiltin(this.root, "exit");
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user