struct and array literal syntax
This commit is contained in:
parent
d981e60f8f
commit
f56df189c4
@ -53,7 +53,9 @@ export const Builtins = {
|
||||
ArrayPush: 0x22,
|
||||
ArrayAt: 0x23,
|
||||
ArrayLength: 0x24,
|
||||
StructSet: 0x30,
|
||||
StructNew: 0x30,
|
||||
StructSet: 0x31,
|
||||
StructAt: 0x32,
|
||||
Print: 0x40,
|
||||
FileOpen: 0x41,
|
||||
FileClose: 0x42,
|
||||
|
@ -59,6 +59,8 @@ export type ExprKind =
|
||||
sym: Sym;
|
||||
}
|
||||
| { type: "group"; expr: Expr }
|
||||
| { type: "array"; exprs: Expr[] }
|
||||
| { type: "struct"; fields: Field[] }
|
||||
| { type: "field"; subject: Expr; ident: string }
|
||||
| { type: "index"; subject: Expr; value: Expr }
|
||||
| {
|
||||
@ -101,6 +103,12 @@ export type BinaryType =
|
||||
| "or"
|
||||
| "and";
|
||||
|
||||
export type Field = {
|
||||
ident: string;
|
||||
expr: Expr;
|
||||
pos: Pos;
|
||||
};
|
||||
|
||||
export type Param = {
|
||||
ident: string;
|
||||
etype?: EType;
|
||||
@ -141,7 +149,8 @@ export type ETypeKind =
|
||||
sym: Sym;
|
||||
}
|
||||
| { type: "array"; inner: EType }
|
||||
| { type: "struct"; fields: Param[] };
|
||||
| { type: "struct"; fields: Param[] }
|
||||
| { type: "type_of"; expr: Expr };
|
||||
|
||||
export type GenericParam = {
|
||||
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;
|
||||
|
||||
@ -21,6 +21,8 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
||||
visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitIdentExpr?(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;
|
||||
visitIndexExpr?(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;
|
||||
visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitParam?(param: Param, ...args: Args): VisitRes;
|
||||
visitField?(field: Field, ...args: Args): VisitRes;
|
||||
visitEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitErrorEType?(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;
|
||||
visitArrayEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitStructEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitTypeOfEType?(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.right, v, ...args);
|
||||
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":
|
||||
if (v.visitIfExpr?.(expr, ...args) == "stop") return;
|
||||
visitExpr(expr.kind.cond, v, ...args);
|
||||
@ -235,6 +247,15 @@ export function visitParam<Args extends unknown[] = []>(
|
||||
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[] = []>(
|
||||
etype: EType,
|
||||
v: AstVisitor<Args>,
|
||||
@ -271,6 +292,10 @@ export function visitEType<Args extends unknown[] = []>(
|
||||
if (v.visitStructEType?.(etype, ...args) == "stop") return;
|
||||
etype.kind.fields.map((field) => visitParam(field, v, ...args));
|
||||
break;
|
||||
case "type_of":
|
||||
if (v.visitTypeOfEType?.(etype, ...args) == "stop") return;
|
||||
visitExpr(etype.kind.expr, v, ...args);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`etype '${
|
||||
|
@ -326,6 +326,10 @@ export class Checker {
|
||||
return { type: "string" };
|
||||
case "group":
|
||||
return this.checkExpr(expr.kind.expr);
|
||||
case "array":
|
||||
throw new Error("should have been desugared");
|
||||
case "struct":
|
||||
return this.checkStructExpr(expr);
|
||||
case "field":
|
||||
return this.checkFieldExpr(expr);
|
||||
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 {
|
||||
if (expr.kind.type !== "field") {
|
||||
throw new Error();
|
||||
@ -446,8 +459,8 @@ export class Checker {
|
||||
if (subject.type === "fn") {
|
||||
if (expr.kind.args.length !== subject.params.length) {
|
||||
this.report(
|
||||
`incorrect number of arguments` +
|
||||
`, expected ${subject.params.length}`,
|
||||
`expected ${subject.params.length} arguments` +
|
||||
`, got ${expr.kind.args.length}`,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
@ -657,8 +670,8 @@ export class Checker {
|
||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
||||
if (args.length !== params.length) {
|
||||
this.report(
|
||||
`incorrect number of arguments` +
|
||||
`, expected ${params.length}`,
|
||||
`expected ${params.length} arguments` +
|
||||
`, got ${args.length}`,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
@ -989,7 +1002,11 @@ export class Checker {
|
||||
}));
|
||||
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) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { AstCreator, Mod, Stmt } from "./ast.ts";
|
||||
import { Checker } from "./checker.ts";
|
||||
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
|
||||
import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
|
||||
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
|
||||
import { Reporter } from "./info.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 { Pos } from "./token.ts";
|
||||
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
|
||||
|
||||
export type CompileResult = {
|
||||
program: number[];
|
||||
@ -34,6 +36,8 @@ export class Compiler {
|
||||
).resolve();
|
||||
|
||||
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);
|
||||
|
||||
|
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":
|
||||
return void this.lowerExpr(expr.kind.expr);
|
||||
case "field":
|
||||
break;
|
||||
return this.lowerFieldExpr(expr);
|
||||
case "index":
|
||||
return this.lowerIndexExpr(expr);
|
||||
case "call":
|
||||
@ -298,6 +298,20 @@ class MonoFnLowerer {
|
||||
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) {
|
||||
if (expr.kind.type !== "index") {
|
||||
throw new Error();
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
ETypeKind,
|
||||
Expr,
|
||||
ExprKind,
|
||||
Field,
|
||||
GenericParam,
|
||||
Param,
|
||||
Stmt,
|
||||
@ -17,7 +18,7 @@ import { printStackTrace, Reporter } from "./info.ts";
|
||||
import { Lexer } from "./lexer.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 {
|
||||
private currentToken: Token | null;
|
||||
@ -203,6 +204,9 @@ export class Parser {
|
||||
args.push(this.parseExpr());
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.done() || this.test(")")) {
|
||||
break;
|
||||
}
|
||||
args.push(this.parseExpr());
|
||||
}
|
||||
}
|
||||
@ -536,6 +540,80 @@ export class Parser {
|
||||
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 {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
@ -815,6 +893,12 @@ export class Parser {
|
||||
this.step();
|
||||
return this.expr({ type: "group", expr }, pos);
|
||||
}
|
||||
if (this.test("[")) {
|
||||
return this.parseArray();
|
||||
}
|
||||
if (this.test("struct")) {
|
||||
return this.parseStruct();
|
||||
}
|
||||
if (this.test("{")) {
|
||||
return this.parseBlock();
|
||||
}
|
||||
|
@ -47,6 +47,24 @@ export function vtypesEqual(
|
||||
if (a.type === "array" && b.type === "array") {
|
||||
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.params.length !== b.params.length) {
|
||||
return false;
|
||||
@ -101,6 +119,12 @@ export function vtypeToString(vtype: VType): string {
|
||||
if (vtype.type === "array") {
|
||||
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") {
|
||||
const paramString = vtype.params.map((param) =>
|
||||
`${param.ident}: ${vtypeToString(param.vtype)}`
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "alloc.hpp"
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace sliger::heap;
|
||||
|
||||
@ -14,3 +15,18 @@ auto Array::at(int32_t index) & -> Value&
|
||||
}
|
||||
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 {
|
||||
std::unordered_map<std::string, Value> fields;
|
||||
|
||||
auto at(const std::string&) & -> Value&;
|
||||
void assign(const std::string&, Value&& value);
|
||||
};
|
||||
|
||||
enum class AllocType {
|
||||
|
@ -54,7 +54,9 @@ enum class Builtin : uint32_t {
|
||||
ArrayPush = 0x22,
|
||||
ArrayAt = 0x23,
|
||||
ArrayLength = 0x24,
|
||||
StructSet = 0x30,
|
||||
StructNew = 0x30,
|
||||
StructSet = 0x31,
|
||||
StructAt = 0x32,
|
||||
Print = 0x40,
|
||||
FileOpen = 0x41,
|
||||
FileClose = 0x42,
|
||||
|
@ -289,6 +289,11 @@ void VM::run_instruction()
|
||||
this->current_pos = { index, line, col };
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cerr << std::format("unrecognized instruction '{}', pc = {}",
|
||||
std::to_underlying(op), this->pc);
|
||||
std::exit(1);
|
||||
break;
|
||||
}
|
||||
this->instruction_counter += 1;
|
||||
}
|
||||
@ -331,12 +336,11 @@ void VM::run_builtin(Builtin builtin_id)
|
||||
run_array_builtin(builtin_id);
|
||||
break;
|
||||
|
||||
case Builtin::StructSet: {
|
||||
assert_stack_has(2);
|
||||
std::cerr << std::format("not implemented\n");
|
||||
std::exit(1);
|
||||
case Builtin::StructNew:
|
||||
case Builtin::StructSet:
|
||||
case Builtin::StructAt:
|
||||
run_struct_builtin(builtin_id);
|
||||
break;
|
||||
}
|
||||
|
||||
case Builtin::Print:
|
||||
case Builtin::FileOpen:
|
||||
@ -407,6 +411,7 @@ void VM::run_string_builtin(Builtin builtin_id)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VM::run_array_builtin(Builtin 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)
|
||||
{
|
||||
switch (builtin_id) {
|
||||
|
@ -199,6 +199,7 @@ private:
|
||||
void run_builtin(Builtin builtin_id);
|
||||
void run_string_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);
|
||||
|
||||
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)]
|
||||
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)]
|
||||
pub fn file_open(filename: string, mode: string) -> int {}
|
||||
#[builtin(FileClose)]
|
||||
@ -168,3 +173,10 @@ pub fn array_to_sorted(array: [int]) -> [int] {
|
||||
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