mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-18 09:46:30 +00:00
struct and array literal syntax
This commit is contained in:
parent
d981e60f8f
commit
f56df189c4
@ -53,7 +53,9 @@ export const Builtins = {
|
|||||||
ArrayPush: 0x22,
|
ArrayPush: 0x22,
|
||||||
ArrayAt: 0x23,
|
ArrayAt: 0x23,
|
||||||
ArrayLength: 0x24,
|
ArrayLength: 0x24,
|
||||||
StructSet: 0x30,
|
StructNew: 0x30,
|
||||||
|
StructSet: 0x31,
|
||||||
|
StructAt: 0x32,
|
||||||
Print: 0x40,
|
Print: 0x40,
|
||||||
FileOpen: 0x41,
|
FileOpen: 0x41,
|
||||||
FileClose: 0x42,
|
FileClose: 0x42,
|
||||||
|
@ -59,6 +59,8 @@ export type ExprKind =
|
|||||||
sym: Sym;
|
sym: Sym;
|
||||||
}
|
}
|
||||||
| { type: "group"; expr: Expr }
|
| { type: "group"; expr: Expr }
|
||||||
|
| { type: "array"; exprs: Expr[] }
|
||||||
|
| { type: "struct"; fields: Field[] }
|
||||||
| { type: "field"; subject: Expr; ident: string }
|
| { type: "field"; subject: Expr; ident: string }
|
||||||
| { type: "index"; subject: Expr; value: Expr }
|
| { type: "index"; subject: Expr; value: Expr }
|
||||||
| {
|
| {
|
||||||
@ -101,6 +103,12 @@ export type BinaryType =
|
|||||||
| "or"
|
| "or"
|
||||||
| "and";
|
| "and";
|
||||||
|
|
||||||
|
export type Field = {
|
||||||
|
ident: string;
|
||||||
|
expr: Expr;
|
||||||
|
pos: Pos;
|
||||||
|
};
|
||||||
|
|
||||||
export type Param = {
|
export type Param = {
|
||||||
ident: string;
|
ident: string;
|
||||||
etype?: EType;
|
etype?: EType;
|
||||||
@ -141,7 +149,8 @@ export type ETypeKind =
|
|||||||
sym: Sym;
|
sym: Sym;
|
||||||
}
|
}
|
||||||
| { type: "array"; inner: EType }
|
| { type: "array"; inner: EType }
|
||||||
| { type: "struct"; fields: Param[] };
|
| { type: "struct"; fields: Param[] }
|
||||||
|
| { type: "type_of"; expr: Expr };
|
||||||
|
|
||||||
export type GenericParam = {
|
export type GenericParam = {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { EType, Expr, Param, Stmt } from "./ast.ts";
|
import { EType, Expr, Field, Param, Stmt } from "./ast.ts";
|
||||||
|
|
||||||
export type VisitRes = "stop" | void;
|
export type VisitRes = "stop" | void;
|
||||||
|
|
||||||
@ -21,6 +21,8 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
|||||||
visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitGroupExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitGroupExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
|
visitArrayExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
|
visitStructExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitFieldExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitFieldExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitCallExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitCallExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
@ -38,6 +40,7 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
|||||||
visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitParam?(param: Param, ...args: Args): VisitRes;
|
visitParam?(param: Param, ...args: Args): VisitRes;
|
||||||
|
visitField?(field: Field, ...args: Args): VisitRes;
|
||||||
visitEType?(etype: EType, ...args: Args): VisitRes;
|
visitEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitErrorEType?(etype: EType, ...args: Args): VisitRes;
|
visitErrorEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitNullEType?(etype: EType, ...args: Args): VisitRes;
|
visitNullEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
@ -48,6 +51,7 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
|||||||
visitSymEType?(etype: EType, ...args: Args): VisitRes;
|
visitSymEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitArrayEType?(etype: EType, ...args: Args): VisitRes;
|
visitArrayEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitStructEType?(etype: EType, ...args: Args): VisitRes;
|
visitStructEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
visitTypeOfEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitAnno?(etype: EType, ...args: Args): VisitRes;
|
visitAnno?(etype: EType, ...args: Args): VisitRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,6 +179,14 @@ export function visitExpr<Args extends unknown[] = []>(
|
|||||||
visitExpr(expr.kind.left, v, ...args);
|
visitExpr(expr.kind.left, v, ...args);
|
||||||
visitExpr(expr.kind.right, v, ...args);
|
visitExpr(expr.kind.right, v, ...args);
|
||||||
break;
|
break;
|
||||||
|
case "array":
|
||||||
|
if (v.visitArrayExpr?.(expr, ...args) == "stop") return;
|
||||||
|
expr.kind.exprs.map((expr) => visitExpr(expr, v, ...args));
|
||||||
|
break;
|
||||||
|
case "struct":
|
||||||
|
if (v.visitStructExpr?.(expr, ...args) == "stop") return;
|
||||||
|
expr.kind.fields.map((field) => visitField(field, v, ...args));
|
||||||
|
break;
|
||||||
case "if":
|
case "if":
|
||||||
if (v.visitIfExpr?.(expr, ...args) == "stop") return;
|
if (v.visitIfExpr?.(expr, ...args) == "stop") return;
|
||||||
visitExpr(expr.kind.cond, v, ...args);
|
visitExpr(expr.kind.cond, v, ...args);
|
||||||
@ -235,6 +247,15 @@ export function visitParam<Args extends unknown[] = []>(
|
|||||||
if (param.etype) visitEType(param.etype, v, ...args);
|
if (param.etype) visitEType(param.etype, v, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function visitField<Args extends unknown[] = []>(
|
||||||
|
field: Field,
|
||||||
|
v: AstVisitor<Args>,
|
||||||
|
...args: Args
|
||||||
|
) {
|
||||||
|
if (v.visitField?.(field, ...args) == "stop") return;
|
||||||
|
visitExpr(field.expr, v, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
export function visitEType<Args extends unknown[] = []>(
|
export function visitEType<Args extends unknown[] = []>(
|
||||||
etype: EType,
|
etype: EType,
|
||||||
v: AstVisitor<Args>,
|
v: AstVisitor<Args>,
|
||||||
@ -271,6 +292,10 @@ export function visitEType<Args extends unknown[] = []>(
|
|||||||
if (v.visitStructEType?.(etype, ...args) == "stop") return;
|
if (v.visitStructEType?.(etype, ...args) == "stop") return;
|
||||||
etype.kind.fields.map((field) => visitParam(field, v, ...args));
|
etype.kind.fields.map((field) => visitParam(field, v, ...args));
|
||||||
break;
|
break;
|
||||||
|
case "type_of":
|
||||||
|
if (v.visitTypeOfEType?.(etype, ...args) == "stop") return;
|
||||||
|
visitExpr(etype.kind.expr, v, ...args);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`etype '${
|
`etype '${
|
||||||
|
@ -326,6 +326,10 @@ export class Checker {
|
|||||||
return { type: "string" };
|
return { type: "string" };
|
||||||
case "group":
|
case "group":
|
||||||
return this.checkExpr(expr.kind.expr);
|
return this.checkExpr(expr.kind.expr);
|
||||||
|
case "array":
|
||||||
|
throw new Error("should have been desugared");
|
||||||
|
case "struct":
|
||||||
|
return this.checkStructExpr(expr);
|
||||||
case "field":
|
case "field":
|
||||||
return this.checkFieldExpr(expr);
|
return this.checkFieldExpr(expr);
|
||||||
case "index":
|
case "index":
|
||||||
@ -393,6 +397,15 @@ export class Checker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public checkStructExpr(expr: Expr): VType {
|
||||||
|
if (expr.kind.type !== "struct") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const fields: VTypeParam[] = expr.kind.fields
|
||||||
|
.map(({ ident, expr }) => ({ ident, vtype: this.checkExpr(expr) }));
|
||||||
|
return { type: "struct", fields };
|
||||||
|
}
|
||||||
|
|
||||||
public checkFieldExpr(expr: Expr): VType {
|
public checkFieldExpr(expr: Expr): VType {
|
||||||
if (expr.kind.type !== "field") {
|
if (expr.kind.type !== "field") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -446,8 +459,8 @@ export class Checker {
|
|||||||
if (subject.type === "fn") {
|
if (subject.type === "fn") {
|
||||||
if (expr.kind.args.length !== subject.params.length) {
|
if (expr.kind.args.length !== subject.params.length) {
|
||||||
this.report(
|
this.report(
|
||||||
`incorrect number of arguments` +
|
`expected ${subject.params.length} arguments` +
|
||||||
`, expected ${subject.params.length}`,
|
`, got ${expr.kind.args.length}`,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -657,8 +670,8 @@ export class Checker {
|
|||||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
||||||
if (args.length !== params.length) {
|
if (args.length !== params.length) {
|
||||||
this.report(
|
this.report(
|
||||||
`incorrect number of arguments` +
|
`expected ${params.length} arguments` +
|
||||||
`, expected ${params.length}`,
|
`, got ${args.length}`,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -989,7 +1002,11 @@ export class Checker {
|
|||||||
}));
|
}));
|
||||||
return { type: "struct", fields };
|
return { type: "struct", fields };
|
||||||
}
|
}
|
||||||
throw new Error(`unknown explicit type ${etype.kind.type}`);
|
if (etype.kind.type === "type_of") {
|
||||||
|
const exprVType = this.checkExpr(etype.kind.expr);
|
||||||
|
return exprVType;
|
||||||
|
}
|
||||||
|
throw new Error(`unknown explicit type '${etype.kind.type}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private report(msg: string, pos: Pos) {
|
private report(msg: string, pos: Pos) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { AstCreator, Mod, Stmt } from "./ast.ts";
|
import { AstCreator, Mod, Stmt } from "./ast.ts";
|
||||||
import { Checker } from "./checker.ts";
|
import { Checker } from "./checker.ts";
|
||||||
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
|
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
|
||||||
|
import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
|
||||||
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
|
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
|
||||||
import { Reporter } from "./info.ts";
|
import { Reporter } from "./info.ts";
|
||||||
import { Lexer } from "./lexer.ts";
|
import { Lexer } from "./lexer.ts";
|
||||||
@ -12,6 +13,7 @@ import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts";
|
|||||||
|
|
||||||
import * as path from "jsr:@std/path";
|
import * as path from "jsr:@std/path";
|
||||||
import { Pos } from "./token.ts";
|
import { Pos } from "./token.ts";
|
||||||
|
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
|
||||||
|
|
||||||
export type CompileResult = {
|
export type CompileResult = {
|
||||||
program: number[];
|
program: number[];
|
||||||
@ -34,6 +36,8 @@ export class Compiler {
|
|||||||
).resolve();
|
).resolve();
|
||||||
|
|
||||||
new SpecialLoopDesugarer(this.astCreator).desugar(mod.ast);
|
new SpecialLoopDesugarer(this.astCreator).desugar(mod.ast);
|
||||||
|
new ArrayLiteralDesugarer(this.astCreator).desugar(mod.ast);
|
||||||
|
new StructLiteralDesugarer(this.astCreator).desugar(mod.ast);
|
||||||
|
|
||||||
new Resolver(this.reporter).resolve(mod.ast);
|
new Resolver(this.reporter).resolve(mod.ast);
|
||||||
|
|
||||||
|
93
compiler/desugar/array_literal.ts
Normal file
93
compiler/desugar/array_literal.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import {
|
||||||
|
AstCreator,
|
||||||
|
ETypeKind,
|
||||||
|
Expr,
|
||||||
|
ExprKind,
|
||||||
|
Stmt,
|
||||||
|
StmtKind,
|
||||||
|
} from "../ast.ts";
|
||||||
|
import { AstVisitor, visitExpr, VisitRes, visitStmts } from "../ast_visitor.ts";
|
||||||
|
import { Pos } from "../token.ts";
|
||||||
|
|
||||||
|
export class ArrayLiteralDesugarer implements AstVisitor {
|
||||||
|
public constructor(
|
||||||
|
private astCreator: AstCreator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public desugar(stmts: Stmt[]) {
|
||||||
|
visitStmts(stmts, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitArrayExpr(expr: Expr): VisitRes {
|
||||||
|
if (expr.kind.type !== "array") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const npos: Pos = { index: 0, line: 1, col: 1 };
|
||||||
|
const Expr = (kind: ExprKind, pos = npos) =>
|
||||||
|
this.astCreator.expr(kind, pos);
|
||||||
|
const Stmt = (kind: StmtKind, pos = npos) =>
|
||||||
|
this.astCreator.stmt(kind, pos);
|
||||||
|
const EType = (kind: ETypeKind, pos = npos) =>
|
||||||
|
this.astCreator.etype(kind, pos);
|
||||||
|
|
||||||
|
const std = (ident: string): Expr =>
|
||||||
|
Expr({
|
||||||
|
type: "path",
|
||||||
|
subject: Expr({
|
||||||
|
type: "ident",
|
||||||
|
ident: "std",
|
||||||
|
}),
|
||||||
|
ident,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (expr.kind.exprs.length < 1) {
|
||||||
|
throw new Error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
expr.kind = {
|
||||||
|
type: "block",
|
||||||
|
stmts: [
|
||||||
|
Stmt({
|
||||||
|
type: "let",
|
||||||
|
param: {
|
||||||
|
ident: "::value",
|
||||||
|
pos: npos,
|
||||||
|
},
|
||||||
|
value: Expr({
|
||||||
|
type: "call",
|
||||||
|
subject: Expr({
|
||||||
|
type: "etype_args",
|
||||||
|
subject: std("array_new"),
|
||||||
|
etypeArgs: [
|
||||||
|
EType({
|
||||||
|
type: "type_of",
|
||||||
|
expr: expr.kind.exprs[0],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
args: [],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
...expr.kind.exprs
|
||||||
|
.map((expr) =>
|
||||||
|
Stmt({
|
||||||
|
type: "expr",
|
||||||
|
expr: Expr({
|
||||||
|
type: "call",
|
||||||
|
subject: std("array_push"),
|
||||||
|
args: [
|
||||||
|
Expr({ type: "ident", ident: "::value" }),
|
||||||
|
expr,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
],
|
||||||
|
expr: Expr({ type: "ident", ident: "::value" }),
|
||||||
|
};
|
||||||
|
|
||||||
|
visitExpr(expr, this);
|
||||||
|
|
||||||
|
return "stop";
|
||||||
|
}
|
||||||
|
}
|
102
compiler/desugar/struct_literal.ts
Normal file
102
compiler/desugar/struct_literal.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import {
|
||||||
|
AstCreator,
|
||||||
|
ETypeKind,
|
||||||
|
Expr,
|
||||||
|
ExprKind,
|
||||||
|
Stmt,
|
||||||
|
StmtKind,
|
||||||
|
} from "../ast.ts";
|
||||||
|
import {
|
||||||
|
AstVisitor,
|
||||||
|
visitExpr,
|
||||||
|
visitField,
|
||||||
|
VisitRes,
|
||||||
|
visitStmts,
|
||||||
|
} from "../ast_visitor.ts";
|
||||||
|
import { Pos } from "../token.ts";
|
||||||
|
|
||||||
|
export class StructLiteralDesugarer implements AstVisitor {
|
||||||
|
public constructor(
|
||||||
|
private astCreator: AstCreator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public desugar(stmts: Stmt[]) {
|
||||||
|
visitStmts(stmts, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitStructExpr(expr: Expr): VisitRes {
|
||||||
|
if (expr.kind.type !== "struct") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const npos: Pos = { index: 0, line: 1, col: 1 };
|
||||||
|
const Expr = (kind: ExprKind, pos = npos) =>
|
||||||
|
this.astCreator.expr(kind, pos);
|
||||||
|
const Stmt = (kind: StmtKind, pos = npos) =>
|
||||||
|
this.astCreator.stmt(kind, pos);
|
||||||
|
const EType = (kind: ETypeKind, pos = npos) =>
|
||||||
|
this.astCreator.etype(kind, pos);
|
||||||
|
|
||||||
|
const std = (ident: string): Expr =>
|
||||||
|
Expr({
|
||||||
|
type: "path",
|
||||||
|
subject: Expr({
|
||||||
|
type: "ident",
|
||||||
|
ident: "std",
|
||||||
|
}),
|
||||||
|
ident,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Yes, I know this isn't a deep clone,
|
||||||
|
// but I don't really need it to be.
|
||||||
|
const oldExpr = { ...expr };
|
||||||
|
|
||||||
|
const fields = expr.kind.fields;
|
||||||
|
|
||||||
|
expr.kind = {
|
||||||
|
type: "block",
|
||||||
|
stmts: [
|
||||||
|
Stmt({
|
||||||
|
type: "let",
|
||||||
|
param: {
|
||||||
|
ident: "::value",
|
||||||
|
pos: npos,
|
||||||
|
},
|
||||||
|
value: Expr({
|
||||||
|
type: "call",
|
||||||
|
subject: Expr({
|
||||||
|
type: "etype_args",
|
||||||
|
subject: std("struct_new"),
|
||||||
|
etypeArgs: [
|
||||||
|
EType({ type: "type_of", expr: oldExpr }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
args: [],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
...expr.kind.fields
|
||||||
|
.map((field) =>
|
||||||
|
Stmt({
|
||||||
|
type: "assign",
|
||||||
|
assignType: "=",
|
||||||
|
subject: Expr({
|
||||||
|
type: "field",
|
||||||
|
subject: Expr({
|
||||||
|
type: "ident",
|
||||||
|
ident: "::value",
|
||||||
|
}),
|
||||||
|
ident: field.ident,
|
||||||
|
}),
|
||||||
|
value: field.expr,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
],
|
||||||
|
expr: Expr({ type: "ident", ident: "::value" }),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
visitField(field, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "stop";
|
||||||
|
}
|
||||||
|
}
|
@ -277,7 +277,7 @@ class MonoFnLowerer {
|
|||||||
case "group":
|
case "group":
|
||||||
return void this.lowerExpr(expr.kind.expr);
|
return void this.lowerExpr(expr.kind.expr);
|
||||||
case "field":
|
case "field":
|
||||||
break;
|
return this.lowerFieldExpr(expr);
|
||||||
case "index":
|
case "index":
|
||||||
return this.lowerIndexExpr(expr);
|
return this.lowerIndexExpr(expr);
|
||||||
case "call":
|
case "call":
|
||||||
@ -298,6 +298,20 @@ class MonoFnLowerer {
|
|||||||
throw new Error(`unhandled expr '${expr.kind.type}'`);
|
throw new Error(`unhandled expr '${expr.kind.type}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lowerFieldExpr(expr: Expr) {
|
||||||
|
if (expr.kind.type !== "field") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
this.lowerExpr(expr.kind.subject);
|
||||||
|
this.program.add(Ops.PushString, expr.kind.ident);
|
||||||
|
|
||||||
|
if (expr.kind.subject.vtype?.type == "struct") {
|
||||||
|
this.program.add(Ops.Builtin, Builtins.StructAt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(`unhandled field subject type '${expr.kind.subject}'`);
|
||||||
|
}
|
||||||
|
|
||||||
private lowerIndexExpr(expr: Expr) {
|
private lowerIndexExpr(expr: Expr) {
|
||||||
if (expr.kind.type !== "index") {
|
if (expr.kind.type !== "index") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
ETypeKind,
|
ETypeKind,
|
||||||
Expr,
|
Expr,
|
||||||
ExprKind,
|
ExprKind,
|
||||||
|
Field,
|
||||||
GenericParam,
|
GenericParam,
|
||||||
Param,
|
Param,
|
||||||
Stmt,
|
Stmt,
|
||||||
@ -17,7 +18,7 @@ import { printStackTrace, Reporter } from "./info.ts";
|
|||||||
import { Lexer } from "./lexer.ts";
|
import { Lexer } from "./lexer.ts";
|
||||||
import { Pos, Token } from "./token.ts";
|
import { Pos, Token } from "./token.ts";
|
||||||
|
|
||||||
type Res<T> = { ok: true; value: T } | { ok: false };
|
type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };
|
||||||
|
|
||||||
export class Parser {
|
export class Parser {
|
||||||
private currentToken: Token | null;
|
private currentToken: Token | null;
|
||||||
@ -203,6 +204,9 @@ export class Parser {
|
|||||||
args.push(this.parseExpr());
|
args.push(this.parseExpr());
|
||||||
while (this.test(",")) {
|
while (this.test(",")) {
|
||||||
this.step();
|
this.step();
|
||||||
|
if (this.done() || this.test(")")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
args.push(this.parseExpr());
|
args.push(this.parseExpr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -536,6 +540,80 @@ export class Parser {
|
|||||||
return this.expr({ type: "for", decl, cond, incr, body }, pos);
|
return this.expr({ type: "for", decl, cond, incr, body }, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseArray(): Expr {
|
||||||
|
const pos = this.pos();
|
||||||
|
this.step();
|
||||||
|
const exprs: Expr[] = [];
|
||||||
|
if (!this.test("]")) {
|
||||||
|
exprs.push(this.parseExpr());
|
||||||
|
while (this.test(",")) {
|
||||||
|
this.step();
|
||||||
|
if (this.done() || this.test("]")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
exprs.push(this.parseExpr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.test("]")) {
|
||||||
|
this.report("expected ']'");
|
||||||
|
return this.expr({ type: "error" }, pos);
|
||||||
|
}
|
||||||
|
this.step();
|
||||||
|
return this.expr({ type: "array", exprs }, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseStruct(): Expr {
|
||||||
|
const pos = this.pos();
|
||||||
|
this.step();
|
||||||
|
if (!this.test("{")) {
|
||||||
|
this.report("expected '{'");
|
||||||
|
return this.expr({ type: "error" }, pos);
|
||||||
|
}
|
||||||
|
this.step();
|
||||||
|
const fields: Field[] = [];
|
||||||
|
if (!this.test("}")) {
|
||||||
|
const res = this.parseStructField();
|
||||||
|
if (!res.ok) {
|
||||||
|
return this.expr({ type: "error" }, res.pos!);
|
||||||
|
}
|
||||||
|
fields.push(res.value);
|
||||||
|
while (this.test(",")) {
|
||||||
|
this.step();
|
||||||
|
if (this.done() || this.test("}")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const res = this.parseStructField();
|
||||||
|
if (!res.ok) {
|
||||||
|
return this.expr({ type: "error" }, res.pos!);
|
||||||
|
}
|
||||||
|
fields.push(res.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.test("}")) {
|
||||||
|
this.report("expected '}'");
|
||||||
|
return this.expr({ type: "error" }, pos);
|
||||||
|
}
|
||||||
|
this.step();
|
||||||
|
return this.expr({ type: "struct", fields }, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseStructField(): Res<Field> {
|
||||||
|
const pos = this.pos();
|
||||||
|
if (!this.test("ident")) {
|
||||||
|
this.report("expected 'ident'");
|
||||||
|
return { ok: false, pos };
|
||||||
|
}
|
||||||
|
const ident = this.current().identValue!;
|
||||||
|
this.step();
|
||||||
|
if (!this.test(":")) {
|
||||||
|
this.report("expected ':'");
|
||||||
|
return { ok: false, pos };
|
||||||
|
}
|
||||||
|
this.step();
|
||||||
|
const expr = this.parseExpr();
|
||||||
|
return { ok: true, value: { ident, expr, pos } };
|
||||||
|
}
|
||||||
|
|
||||||
private parseIf(): Expr {
|
private parseIf(): Expr {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
this.step();
|
this.step();
|
||||||
@ -815,6 +893,12 @@ export class Parser {
|
|||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "group", expr }, pos);
|
return this.expr({ type: "group", expr }, pos);
|
||||||
}
|
}
|
||||||
|
if (this.test("[")) {
|
||||||
|
return this.parseArray();
|
||||||
|
}
|
||||||
|
if (this.test("struct")) {
|
||||||
|
return this.parseStruct();
|
||||||
|
}
|
||||||
if (this.test("{")) {
|
if (this.test("{")) {
|
||||||
return this.parseBlock();
|
return this.parseBlock();
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,24 @@ export function vtypesEqual(
|
|||||||
if (a.type === "array" && b.type === "array") {
|
if (a.type === "array" && b.type === "array") {
|
||||||
return vtypesEqual(a.inner, b.inner, generics);
|
return vtypesEqual(a.inner, b.inner, generics);
|
||||||
}
|
}
|
||||||
|
if (a.type === "struct" && b.type === "struct") {
|
||||||
|
if (a.fields.length !== b.fields.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const match = a.fields
|
||||||
|
.map((af) => ({
|
||||||
|
ident: af.ident,
|
||||||
|
af,
|
||||||
|
bf: b.fields.find((bf) => bf.ident === af.ident),
|
||||||
|
}));
|
||||||
|
if (match.some((m) => m.bf === undefined)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (match.some((m) => !vtypesEqual(m.af.vtype, m.bf!.vtype))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (a.type === "fn" && b.type === "fn") {
|
if (a.type === "fn" && b.type === "fn") {
|
||||||
if (a.params.length !== b.params.length) {
|
if (a.params.length !== b.params.length) {
|
||||||
return false;
|
return false;
|
||||||
@ -101,6 +119,12 @@ export function vtypeToString(vtype: VType): string {
|
|||||||
if (vtype.type === "array") {
|
if (vtype.type === "array") {
|
||||||
return `[${vtypeToString(vtype.inner)}]`;
|
return `[${vtypeToString(vtype.inner)}]`;
|
||||||
}
|
}
|
||||||
|
if (vtype.type === "struct") {
|
||||||
|
const fields = vtype.fields
|
||||||
|
.map((field) => `${field.ident}: ${vtypeToString(field.vtype)}`)
|
||||||
|
.join(", ");
|
||||||
|
return `struct { ${fields} }`;
|
||||||
|
}
|
||||||
if (vtype.type === "fn") {
|
if (vtype.type === "fn") {
|
||||||
const paramString = vtype.params.map((param) =>
|
const paramString = vtype.params.map((param) =>
|
||||||
`${param.ident}: ${vtypeToString(param.vtype)}`
|
`${param.ident}: ${vtypeToString(param.vtype)}`
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "alloc.hpp"
|
#include "alloc.hpp"
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
using namespace sliger::heap;
|
using namespace sliger::heap;
|
||||||
|
|
||||||
@ -14,3 +15,18 @@ auto Array::at(int32_t index) & -> Value&
|
|||||||
}
|
}
|
||||||
return values.at(static_cast<size_t>(index));
|
return values.at(static_cast<size_t>(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Struct::at(const std::string& field) & -> Value&
|
||||||
|
{
|
||||||
|
if (this->fields.find(field) == this->fields.end()) {
|
||||||
|
std::cout << std::format(
|
||||||
|
"field name not in struct, got: \"{}\"\n", field);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return this->fields.at(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Struct::assign(const std::string& field, Value&& value)
|
||||||
|
{
|
||||||
|
this->fields.insert_or_assign(field, value);
|
||||||
|
}
|
||||||
|
@ -19,6 +19,9 @@ struct Array {
|
|||||||
|
|
||||||
struct Struct {
|
struct Struct {
|
||||||
std::unordered_map<std::string, Value> fields;
|
std::unordered_map<std::string, Value> fields;
|
||||||
|
|
||||||
|
auto at(const std::string&) & -> Value&;
|
||||||
|
void assign(const std::string&, Value&& value);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AllocType {
|
enum class AllocType {
|
||||||
|
@ -54,7 +54,9 @@ enum class Builtin : uint32_t {
|
|||||||
ArrayPush = 0x22,
|
ArrayPush = 0x22,
|
||||||
ArrayAt = 0x23,
|
ArrayAt = 0x23,
|
||||||
ArrayLength = 0x24,
|
ArrayLength = 0x24,
|
||||||
StructSet = 0x30,
|
StructNew = 0x30,
|
||||||
|
StructSet = 0x31,
|
||||||
|
StructAt = 0x32,
|
||||||
Print = 0x40,
|
Print = 0x40,
|
||||||
FileOpen = 0x41,
|
FileOpen = 0x41,
|
||||||
FileClose = 0x42,
|
FileClose = 0x42,
|
||||||
|
@ -289,6 +289,11 @@ void VM::run_instruction()
|
|||||||
this->current_pos = { index, line, col };
|
this->current_pos = { index, line, col };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
std::cerr << std::format("unrecognized instruction '{}', pc = {}",
|
||||||
|
std::to_underlying(op), this->pc);
|
||||||
|
std::exit(1);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this->instruction_counter += 1;
|
this->instruction_counter += 1;
|
||||||
}
|
}
|
||||||
@ -331,12 +336,11 @@ void VM::run_builtin(Builtin builtin_id)
|
|||||||
run_array_builtin(builtin_id);
|
run_array_builtin(builtin_id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Builtin::StructSet: {
|
case Builtin::StructNew:
|
||||||
assert_stack_has(2);
|
case Builtin::StructSet:
|
||||||
std::cerr << std::format("not implemented\n");
|
case Builtin::StructAt:
|
||||||
std::exit(1);
|
run_struct_builtin(builtin_id);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case Builtin::Print:
|
case Builtin::Print:
|
||||||
case Builtin::FileOpen:
|
case Builtin::FileOpen:
|
||||||
@ -407,6 +411,7 @@ void VM::run_string_builtin(Builtin builtin_id)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VM::run_array_builtin(Builtin builtin_id)
|
void VM::run_array_builtin(Builtin builtin_id)
|
||||||
{
|
{
|
||||||
switch (builtin_id) {
|
switch (builtin_id) {
|
||||||
@ -456,6 +461,40 @@ void VM::run_array_builtin(Builtin builtin_id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VM::run_struct_builtin(Builtin builtin_id)
|
||||||
|
{
|
||||||
|
switch (builtin_id) {
|
||||||
|
case Builtin::StructNew: {
|
||||||
|
auto alloc_res = this->heap.alloc<heap::AllocType::Struct>();
|
||||||
|
stack_push(Ptr(alloc_res.val()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Builtin::StructSet: {
|
||||||
|
assert_stack_has(2);
|
||||||
|
auto field = stack_pop().as_string().value;
|
||||||
|
auto struct_ptr = stack_pop().as_ptr().value;
|
||||||
|
auto value = stack_pop();
|
||||||
|
|
||||||
|
this->heap.at(struct_ptr)
|
||||||
|
.val()
|
||||||
|
->as_struct()
|
||||||
|
.assign(field, std::move(value));
|
||||||
|
stack_push(Null());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Builtin::StructAt: {
|
||||||
|
assert_stack_has(2);
|
||||||
|
auto field = stack_pop().as_string().value;
|
||||||
|
auto struct_ptr = stack_pop().as_ptr().value;
|
||||||
|
|
||||||
|
stack_push(this->heap.at(struct_ptr).val()->as_struct().at(field));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VM::run_file_builtin(Builtin builtin_id)
|
void VM::run_file_builtin(Builtin builtin_id)
|
||||||
{
|
{
|
||||||
switch (builtin_id) {
|
switch (builtin_id) {
|
||||||
|
@ -199,6 +199,7 @@ private:
|
|||||||
void run_builtin(Builtin builtin_id);
|
void run_builtin(Builtin builtin_id);
|
||||||
void run_string_builtin(Builtin builtin_id);
|
void run_string_builtin(Builtin builtin_id);
|
||||||
void run_array_builtin(Builtin builtin_id);
|
void run_array_builtin(Builtin builtin_id);
|
||||||
|
void run_struct_builtin(Builtin builtin_id);
|
||||||
void run_file_builtin(Builtin builtin_id);
|
void run_file_builtin(Builtin builtin_id);
|
||||||
|
|
||||||
inline void step() { this->pc += 1; }
|
inline void step() { this->pc += 1; }
|
||||||
|
12
std/lib.slg
12
std/lib.slg
@ -29,6 +29,11 @@ pub fn array_length<T>(array: [T]) -> int {}
|
|||||||
#[builtin(ArrayAt)]
|
#[builtin(ArrayAt)]
|
||||||
pub fn array_at<T>(array: [T], index: int) -> T {}
|
pub fn array_at<T>(array: [T], index: int) -> T {}
|
||||||
|
|
||||||
|
#[builtin(StructNew)]
|
||||||
|
pub fn struct_new<S>() -> S {}
|
||||||
|
#[builtin(StructSet)]
|
||||||
|
pub fn struct_set<S, T>(subject: S, value: T) {}
|
||||||
|
|
||||||
#[builtin(FileOpen)]
|
#[builtin(FileOpen)]
|
||||||
pub fn file_open(filename: string, mode: string) -> int {}
|
pub fn file_open(filename: string, mode: string) -> int {}
|
||||||
#[builtin(FileClose)]
|
#[builtin(FileClose)]
|
||||||
@ -168,3 +173,10 @@ pub fn array_to_sorted(array: [int]) -> [int] {
|
|||||||
cloned
|
cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assert(value: bool, msg: string) {
|
||||||
|
if not value {
|
||||||
|
println("assertion failed: " + msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
11
tests/array_literal.slg
Normal file
11
tests/array_literal.slg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
mod std;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let ints = [1, 2, 3];
|
||||||
|
std::assert(ints[1] == 2, "test int array");
|
||||||
|
|
||||||
|
let strings = ["foo", "bar", "baz"];
|
||||||
|
std::assert(strings[1] == "bar", "test string array");
|
||||||
|
|
||||||
|
std::println("tests ran successfully");
|
||||||
|
}
|
20
tests/struct_literal.slg
Normal file
20
tests/struct_literal.slg
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
mod std;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let d = true;
|
||||||
|
|
||||||
|
let v = struct {
|
||||||
|
a: 123,
|
||||||
|
b: struct {
|
||||||
|
c: "foo",
|
||||||
|
d: d,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::assert(v.a == 123, "test field");
|
||||||
|
std::assert(v.b.c == "foo", "test nested field");
|
||||||
|
std::assert(v.b.d == true, "test resolved field");
|
||||||
|
|
||||||
|
std::println("tests ran successfully");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user