struct and array literal syntax
This commit is contained in:
		
							parent
							
								
									1bbb759981
								
							
						
					
					
						commit
						eb2a6d90f3
					
				| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user