diff --git a/compiler/ast.ts b/compiler/ast.ts index 38bfe0a..f4a5863 100644 --- a/compiler/ast.ts +++ b/compiler/ast.ts @@ -29,9 +29,11 @@ export type StmtKind = vtype?: VType; } | { type: "let"; param: Param; value: Expr } - | { type: "assign"; subject: Expr; value: Expr } + | { type: "assign"; assignType: AssignType; subject: Expr; value: Expr } | { type: "expr"; expr: Expr }; +export type AssignType = "=" | "+=" | "-="; + export type Expr = { kind: ExprKind; pos: Pos; @@ -59,6 +61,15 @@ export type ExprKind = type: "sym"; ident: string; sym: Sym; + } + | { type: "while"; cond: Expr; body: Expr } + | { type: "for_in"; param: Param; value: Expr; body: Expr } + | { + type: "for"; + decl?: Stmt; + cond?: Expr; + incr?: Stmt; + body: Expr; }; export type UnaryType = "not"; @@ -119,3 +130,25 @@ export type Anno = { values: Expr[]; pos: Pos; }; + +export class AstCreator { + private nextNodeId = 0; + + public stmt(kind: StmtKind, pos: Pos): Stmt { + const id = this.nextNodeId; + this.nextNodeId += 1; + return { kind, pos, id }; + } + + public expr(kind: ExprKind, pos: Pos): Expr { + const id = this.nextNodeId; + this.nextNodeId += 1; + return { kind, pos, id }; + } + + public etype(kind: ETypeKind, pos: Pos): EType { + const id = this.nextNodeId; + this.nextNodeId += 1; + return { kind, pos, id }; + } +} diff --git a/compiler/ast_visitor.ts b/compiler/ast_visitor.ts index 25e85b1..b63d1e3 100644 --- a/compiler/ast_visitor.ts +++ b/compiler/ast_visitor.ts @@ -28,6 +28,9 @@ export interface AstVisitor { visitBoolExpr?(expr: Expr, ...args: Args): VisitRes; visitNullExpr?(expr: Expr, ...args: Args): VisitRes; visitLoopExpr?(expr: Expr, ...args: Args): VisitRes; + visitWhileExpr?(expr: Expr, ...args: Args): VisitRes; + visitForInExpr?(expr: Expr, ...args: Args): VisitRes; + visitForExpr?(expr: Expr, ...args: Args): VisitRes; visitBlockExpr?(expr: Expr, ...args: Args): VisitRes; visitSymExpr?(expr: Expr, ...args: Args): VisitRes; visitParam?(param: Param, ...args: Args): VisitRes; @@ -157,6 +160,24 @@ export function visitExpr( if (v.visitLoopExpr?.(expr, ...args) == "stop") return; visitExpr(expr.kind.body, v, ...args); break; + case "while": + if (v.visitWhileExpr?.(expr, ...args) == "stop") return; + visitExpr(expr.kind.cond, v, ...args); + visitExpr(expr.kind.body, v, ...args); + break; + case "for_in": + if (v.visitForInExpr?.(expr, ...args) == "stop") return; + visitParam(expr.kind.param, v, ...args); + visitExpr(expr.kind.value, v, ...args); + visitExpr(expr.kind.body, v, ...args); + break; + case "for": + if (v.visitForExpr?.(expr, ...args) == "stop") return; + if (expr.kind.decl) visitStmt(expr.kind.decl, v, ...args); + if (expr.kind.cond) visitExpr(expr.kind.cond, v, ...args); + if (expr.kind.incr) visitStmt(expr.kind.incr, v, ...args); + visitExpr(expr.kind.body, v, ...args); + break; case "block": if (v.visitBlockExpr?.(expr, ...args) == "stop") return; expr.kind.stmts.map((stmt) => visitStmt(stmt, v, ...args)); diff --git a/compiler/checker.ts b/compiler/checker.ts index 1c894ea..a8b7451 100644 --- a/compiler/checker.ts +++ b/compiler/checker.ts @@ -1,4 +1,3 @@ -import { Builtins } from "./arch.ts"; import { EType, Expr, Stmt } from "./ast.ts"; import { printStackTrace, Reporter } from "./info.ts"; import { Pos } from "./token.ts"; @@ -124,7 +123,7 @@ export class Checker { if (isBuiltin) { return; } - + const { returnType } = stmt.kind.vtype!; this.fnReturnStack.push(returnType); const body = this.checkExpr(stmt.kind.body); @@ -166,6 +165,9 @@ export class Checker { throw new Error(); } const pos = stmt.pos; + if (stmt.kind.assignType !== "=") { + throw new Error("invalid ast: compound assign should be desugered"); + } const value = this.checkExpr(stmt.kind.value); switch (stmt.kind.subject.kind.type) { case "field": { @@ -199,7 +201,10 @@ export class Checker { case "index": { const subject = this.checkExpr(stmt.kind.subject.kind.subject); if (subject.type !== "array" && subject.type !== "string") { - this.report(`cannot index on non-array, got: ${subject.type}`, pos); + this.report( + `cannot index on non-array, got: ${subject.type}`, + pos, + ); return { type: "error" }; } const indexValue = this.checkExpr(stmt.kind.subject.kind.value); @@ -207,7 +212,10 @@ export class Checker { this.report("cannot index on array with non-int", pos); return { type: "error" }; } - if (subject.type == "array" && !vtypesEqual(subject.inner, value)) { + if ( + subject.type == "array" && + !vtypesEqual(subject.inner, value) + ) { this.report( `cannot assign incompatible type to array ` + `'${vtypeToString(subject)}'` + @@ -357,7 +365,7 @@ export class Checker { if (subject.type === "array") { return subject.inner; } - return { type: "int" } + return { type: "int" }; } public checkCallExpr(expr: Expr): VType { diff --git a/compiler/desugar/compound_assign.ts b/compiler/desugar/compound_assign.ts new file mode 100644 index 0000000..1ae6920 --- /dev/null +++ b/compiler/desugar/compound_assign.ts @@ -0,0 +1,50 @@ +import { AstCreator, Stmt } from "../ast.ts"; +import { AstVisitor, VisitRes, visitStmt, visitStmts } from "../ast_visitor.ts"; + +export class CompoundAssignDesugarer implements AstVisitor { + public constructor(private astCreator: AstCreator) {} + + public desugar(stmts: Stmt[]) { + visitStmts(stmts, this); + } + + visitAssignStmt(stmt: Stmt): VisitRes { + if (stmt.kind.type !== "assign") { + throw new Error(); + } + switch (stmt.kind.assignType) { + case "=": + return; + case "+=": { + stmt.kind = { + type: "assign", + assignType: "=", + subject: stmt.kind.subject, + value: this.astCreator.expr({ + type: "binary", + binaryType: "+", + left: stmt.kind.subject, + right: stmt.kind.value, + }, stmt.kind.value.pos), + }; + visitStmt(stmt, this); + return "stop"; + } + case "-=": { + stmt.kind = { + type: "assign", + assignType: "=", + subject: stmt.kind.subject, + value: this.astCreator.expr({ + type: "binary", + binaryType: "-", + left: stmt.kind.subject, + right: stmt.kind.value, + }, stmt.kind.value.pos), + }; + visitStmt(stmt, this); + return "stop"; + } + } + } +} diff --git a/compiler/desugar/special_loop.ts b/compiler/desugar/special_loop.ts new file mode 100644 index 0000000..5ec1ad8 --- /dev/null +++ b/compiler/desugar/special_loop.ts @@ -0,0 +1,242 @@ +import { AstCreator, Expr, ExprKind, Stmt, StmtKind } from "../ast.ts"; +import { AstVisitor, visitExpr, VisitRes, visitStmts } from "../ast_visitor.ts"; +import { Pos } from "../token.ts"; + +export class SpecialLoopDesugarer implements AstVisitor { + public constructor( + private astCreator: AstCreator, + ) {} + + public desugar(stmts: Stmt[]) { + visitStmts(stmts, this); + } + + visitWhileExpr(expr: Expr): VisitRes { + if (expr.kind.type !== "while") { + 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); + + expr.kind = { + type: "loop", + body: Expr({ + type: "block", + stmts: [ + Stmt({ + type: "expr", + expr: Expr({ + type: "if", + cond: Expr({ + type: "unary", + unaryType: "not", + subject: expr.kind.cond, + }), + truthy: Expr({ + type: "block", + stmts: [ + Stmt({ type: "break" }), + ], + }), + }), + }), + Stmt({ + type: "expr", + expr: expr.kind.body, + }), + ], + }), + }; + + visitExpr(expr, this); + return "stop"; + } + + visitForInExpr(expr: Expr): VisitRes { + if (expr.kind.type !== "for_in") { + 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); + + expr.kind = { + type: "block", + stmts: [ + Stmt({ + type: "let", + param: { ident: "::values", pos: npos }, + value: expr.kind.value, + }), + Stmt({ + type: "let", + param: { ident: "::length", pos: npos }, + value: Expr({ + type: "call", + subject: Expr({ + type: "ident", + value: "int_array_length", + }), + args: [ + Expr({ + type: "ident", + value: "::values", + }), + ], + }), + }), + Stmt({ + type: "let", + param: { ident: "::index", pos: npos }, + value: Expr({ type: "int", value: 0 }), + }, expr.pos), + Stmt({ + type: "expr", + expr: Expr({ + type: "loop", + body: Expr({ + type: "block", + stmts: [ + Stmt({ + type: "expr", + expr: Expr({ + type: "if", + cond: Expr({ + type: "unary", + unaryType: "not", + subject: Expr({ + type: "binary", + binaryType: "<", + left: Expr({ + type: "ident", + value: "::index", + }), + right: Expr({ + type: "ident", + value: "::length", + }), + }), + }), + truthy: Expr({ + type: "block", + stmts: [ + Stmt({ + type: "break", + }), + ], + }), + }), + }), + Stmt({ + type: "let", + param: expr.kind.param, + value: Expr({ + type: "index", + subject: Expr({ + type: "ident", + value: "::values", + }), + value: Expr({ + type: "ident", + value: "::index", + }), + }), + }, expr.pos), + Stmt({ + type: "expr", + expr: expr.kind.body, + }), + Stmt({ + type: "assign", + assignType: "+=", + subject: Expr({ + type: "ident", + value: "::index", + }), + value: Expr({ + type: "int", + value: 1, + }), + }), + ], + }), + }), + }), + ], + }; + + visitExpr(expr, this); + return "stop"; + } + + visitForExpr(expr: Expr): VisitRes { + if (expr.kind.type !== "for") { + throw new Error(); + } + const Expr = ( + kind: ExprKind, + pos: Pos = { index: 0, line: 1, col: 1 }, + ) => this.astCreator.expr(kind, pos); + const Stmt = ( + kind: StmtKind, + pos: Pos = { index: 0, line: 1, col: 1 }, + ) => this.astCreator.stmt(kind, pos); + + expr.kind = { + type: "block", + stmts: [ + ...[expr.kind.decl] + .filter((v) => v !== undefined), + Stmt({ + type: "expr", + expr: Expr({ + type: "loop", + body: Expr({ + type: "block", + stmts: [ + ...[expr.kind.cond] + .filter((v) => v !== undefined) + .map( + (cond) => + Stmt({ + type: "expr", + expr: Expr({ + type: "if", + cond: Expr({ + type: "unary", + unaryType: "not", + subject: cond, + }), + truthy: Expr({ + type: "block", + stmts: [ + Stmt({ + type: "break", + }), + ], + }), + }), + }), + ), + Stmt({ + type: "expr", + expr: expr.kind.body, + }), + ...[expr.kind.incr] + .filter((v) => v !== undefined), + ], + }), + }), + }), + ], + }; + + visitExpr(expr, this); + return "stop"; + } +} diff --git a/compiler/lexer.ts b/compiler/lexer.ts index b1e26e5..99c8e24 100644 --- a/compiler/lexer.ts +++ b/compiler/lexer.ts @@ -42,6 +42,9 @@ export class Lexer { "or", "and", "not", + "while", + "for", + "in", ]; if (keywords.includes(value)) { return this.token(value, pos); @@ -126,10 +129,6 @@ export class Lexer { this.step(); return this.token("+=", pos); } - if (first === "-" && !this.done() && this.test(">")) { - this.step(); - return this.token("->", pos); - } if (first === ":") { if (!this.done() && this.test(":")) { this.step(); diff --git a/compiler/main.ts b/compiler/main.ts index bcbd72f..6cb792b 100644 --- a/compiler/main.ts +++ b/compiler/main.ts @@ -1,5 +1,8 @@ -import { Ast, File } from "./ast.ts"; +import { Ast, AstCreator, File } from "./ast.ts"; +import { stmtToString } from "./ast_visitor.ts"; import { Checker } from "./checker.ts"; +import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts"; +import { SpecialLoopDesugarer } from "./desugar/special_loop.ts"; import { Reporter } from "./info.ts"; import { Lexer } from "./lexer.ts"; import { Lowerer } from "./lowerer.ts"; @@ -16,19 +19,22 @@ class Compilation { } } -//const text = await Deno.readTextFile("example.slg"); const text = await Deno.readTextFile(Deno.args[0]); +const astCreator = new AstCreator(); const reporter = new Reporter(); const lexer = new Lexer(text, reporter); -const parser = new Parser(lexer, reporter); +const parser = new Parser(lexer, astCreator, reporter); const ast = parser.parse(); -// console.log(JSON.stringify(ast, null, 4)); +new SpecialLoopDesugarer(astCreator).desugar(ast); new Resolver(reporter).resolve(ast); + +new CompoundAssignDesugarer(astCreator).desugar(ast); + new Checker(reporter).check(ast); if (reporter.errorOccured()) { @@ -40,7 +46,5 @@ const lowerer = new Lowerer(lexer.currentPos()); lowerer.lower(ast); lowerer.printProgram(); const program = lowerer.finish(); -//console.log(JSON.stringify(program, null, 4)); -// console.log(JSON.stringify(program)); await Deno.writeTextFile("out.slgbc", JSON.stringify(program)); diff --git a/compiler/parser.ts b/compiler/parser.ts index 0f62f85..75fee9b 100644 --- a/compiler/parser.ts +++ b/compiler/parser.ts @@ -1,5 +1,7 @@ import { Anno, + AssignType, + AstCreator, BinaryType, EType, ETypeKind, @@ -15,9 +17,12 @@ import { Pos, Token } from "./token.ts"; export class Parser { private currentToken: Token | null; - private nextNodeId = 0; - public constructor(private lexer: Lexer, private reporter: Reporter) { + public constructor( + private lexer: Lexer, + private astCreator: AstCreator, + private reporter: Reporter, + ) { this.currentToken = lexer.next(); } @@ -35,7 +40,9 @@ export class Parser { ) { stmts.push(this.parseSingleLineBlockStmt()); this.eatSemicolon(); - } else if (this.test("{") || this.test("if") || this.test("loop")) { + } else if ( + ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt)) + ) { const expr = this.parseMultiLineBlockExpr(); stmts.push(this.stmt({ type: "expr", expr }, expr.pos)); } else { @@ -57,6 +64,12 @@ export class Parser { if (this.test("loop")) { return this.parseLoop(); } + if (this.test("while")) { + return this.parseWhile(); + } + if (this.test("for")) { + return this.parseFor(); + } this.report("expected expr"); return this.expr({ type: "error" }, pos); } @@ -106,7 +119,9 @@ export class Parser { } else if (this.test("fn")) { stmts.push(this.parseSingleLineBlockStmt()); stmts.push(this.parseFn()); - } else if (this.test("{") || this.test("if") || this.test("loop")) { + } else if ( + ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt)) + ) { const expr = this.parseMultiLineBlockExpr(); if (this.test("}")) { this.step(); @@ -115,13 +130,19 @@ export class Parser { stmts.push(this.stmt({ type: "expr", expr }, expr.pos)); } else { const expr = this.parseExpr(); - if (this.test("=")) { + if (this.test("=") || this.test("+=") || this.test("-=")) { + const assignType = this.current().type as AssignType; this.step(); const value = this.parseExpr(); this.eatSemicolon(); stmts.push( this.stmt( - { type: "assign", subject: expr, value }, + { + type: "assign", + assignType, + subject: expr, + value, + }, expr.pos, ), ); @@ -294,15 +315,22 @@ export class Parser { const value = this.parseExpr(); return this.stmt({ type: "let", param, value }, pos); } + private parseAssign(): Stmt { const pos = this.pos(); const subject = this.parseExpr(); - if (!this.test("=")) { - return this.stmt({ type: "expr", expr: subject }, pos); + if (this.test("=") || this.test("+=") || this.test("-=")) { + const assignType = this.current().type as AssignType; + this.step(); + const value = this.parseExpr(); + return this.stmt({ + type: "assign", + assignType, + subject, + value, + }, pos); } - this.step(); - const value = this.parseExpr(); - return this.stmt({ type: "assign", subject, value }, pos); + return this.stmt({ type: "expr", expr: subject }, pos); } private parseReturn(): Stmt { @@ -329,13 +357,92 @@ export class Parser { const pos = this.pos(); this.step(); if (!this.test("{")) { - this.report("expected '}'"); + this.report("expected '{'"); return this.expr({ type: "error" }, pos); } const body = this.parseExpr(); return this.expr({ type: "loop", body }, pos); } + private parseWhile(): Expr { + const pos = this.pos(); + this.step(); + const cond = this.parseExpr(); + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ type: "error" }, pos); + } + const body = this.parseExpr(); + return this.expr({ type: "while", cond, body }, pos); + } + + private parseFor(): Expr { + const pos = this.pos(); + this.step(); + + if (this.test("(")) { + return this.parseForClassicTail(pos); + } + + const paramRes = this.parseParam(); + if (!paramRes.ok) { + return this.expr({ type: "error" }, pos); + } + const param = paramRes.value; + + if (!this.test("in")) { + this.report("expected 'in'"); + return this.expr({ type: "error" }, pos); + } + this.step(); + const value = this.parseExpr(); + + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ type: "error" }, pos); + } + const body = this.parseExpr(); + return this.expr({ type: "for_in", param, value, body }, pos); + } + + private parseForClassicTail(pos: Pos): Expr { + this.step(); + let decl: Stmt | undefined; + if (!this.test(";")) { + decl = this.parseLet(); + } + if (!this.test(";")) { + this.report("expected ';'"); + return this.expr({ type: "error" }, pos); + } + this.step(); + let cond: Expr | undefined; + if (!this.test(";")) { + cond = this.parseExpr(); + } + if (!this.test(";")) { + this.report("expected ';'"); + return this.expr({ type: "error" }, pos); + } + this.step(); + let incr: Stmt | undefined; + if (!this.test(")")) { + incr = this.parseAssign(); + } + if (!this.test(")")) { + this.report("expected '}'"); + return this.expr({ type: "error" }, pos); + } + this.step(); + + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ type: "error" }, pos); + } + const body = this.parseExpr(); + return this.expr({ type: "for", decl, cond, incr, body }, pos); + } + private parseIf(): Expr { const pos = this.pos(); this.step(); @@ -677,20 +784,14 @@ export class Parser { } private stmt(kind: StmtKind, pos: Pos): Stmt { - const id = this.nextNodeId; - this.nextNodeId += 1; - return { kind, pos, id }; + return this.astCreator.stmt(kind, pos); } private expr(kind: ExprKind, pos: Pos): Expr { - const id = this.nextNodeId; - this.nextNodeId += 1; - return { kind, pos, id }; + return this.astCreator.expr(kind, pos); } private etype(kind: ETypeKind, pos: Pos): EType { - const id = this.nextNodeId; - this.nextNodeId += 1; - return { kind, pos, id }; + return this.astCreator.etype(kind, pos); } } diff --git a/compiler/resolver.ts b/compiler/resolver.ts index 7483131..f58c67c 100644 --- a/compiler/resolver.ts +++ b/compiler/resolver.ts @@ -1,5 +1,11 @@ import { Expr, Stmt } from "./ast.ts"; -import { AstVisitor, visitExpr, VisitRes, visitStmts } from "./ast_visitor.ts"; +import { + AstVisitor, + visitExpr, + VisitRes, + visitStmt, + visitStmts, +} from "./ast_visitor.ts"; import { printStackTrace, Reporter } from "./info.ts"; import { FnSyms, @@ -115,6 +121,19 @@ export class Resolver implements AstVisitor<[Syms]> { return "stop"; } + visitForExpr(expr: Expr, syms: Syms): VisitRes { + if (expr.kind.type !== "for") { + throw new Error(); + } + const childSyms = new LeafSyms(syms); + if (expr.kind.decl) visitStmt(expr.kind.decl, this, syms); + if (expr.kind.cond) visitExpr(expr.kind.cond, this, syms); + if (expr.kind.incr) visitStmt(expr.kind.incr, this, syms); + visitExpr(expr.kind.body, this, childSyms); + + return "stop"; + } + private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) { this.reporter.reportError({ reporter: "Resolver", diff --git a/editors/vim/syntax/slige.vim b/editors/vim/syntax/slige.vim index 69fd2c8..89473ba 100644 --- a/editors/vim/syntax/slige.vim +++ b/editors/vim/syntax/slige.vim @@ -7,7 +7,7 @@ if exists("b:current_syntax") finish endif -syn keyword Keyword break return let fn loop if else struct import or and not +syn keyword Keyword break return let fn loop if else struct import or and not while for in syn keyword Special null syn keyword Type int string bool syn keyword Boolean true false @@ -17,6 +17,8 @@ syn match Operator '-' syn match Operator '\*' syn match Operator '/' syn match Operator '=' +syn match Operator '+=' +syn match Operator '-=' syn match Operator '==' syn match Operator '!=' syn match Operator '<' diff --git a/editors/vscode/syntaxes/slige.tmLanguage.json b/editors/vscode/syntaxes/slige.tmLanguage.json index 9791c78..5572d53 100644 --- a/editors/vscode/syntaxes/slige.tmLanguage.json +++ b/editors/vscode/syntaxes/slige.tmLanguage.json @@ -1,8 +1,8 @@ { -<<<<<<< HEAD "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", "name": "Slige", "patterns": [ + { "include": "#comments" }, { "include": "#keywords" }, { "include": "#strings" }, { "include": "#numbers" }, @@ -12,88 +12,25 @@ { "include": "#idents" } ], "repository": { + "comments": { + "patterns": [ + { + "name": "comment.line.slige", + "begin": "//", + "end": "\\n" + }, + { + "name": "comment.block.slige", + "begin": "/\\*", + "end": "\\*/" + } + ] + }, "keywords": { -======= - "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", - "name": "Slige", - "patterns": [ - { "include": "#comments" }, - { "include": "#keywords" }, - { "include": "#strings" }, - { "include": "#numbers" }, - { "include": "#operators" }, - { "include": "#punctuation" }, - { "include": "#functions" }, - { "include": "#idents" } - ], - "repository": { - "comments": { - "patterns": [ - { - "name": "comment.line.slige", - "begin": "//", - "end": "\\n" - }, - { - "name": "comment.block.slige", - "begin": "/\\*", - "end": "\\*/" - } - ] - }, - "keywords": { - "patterns": [ - { - "name": "keyword.control.slige", - "match": "\\b(break|return|let|fn|loop|if|else|struct|import|or|and|not)\\b" - }, - { - "name": "constant.language.slige", - "match": "\\b(null|false|true)\\b" - }, - { - "name": "storage.type.slige", - "match": "\\b(int|string|bool)\\b" - } - ] - }, - "strings": { - "name": "string.quoted.double.slige", - "begin": "\"", - "end": "\"", - "patterns": [ - { - "name": "constant.character.escape.slige", - "match": "\\\\." - } - ] - }, - "numbers": { - "patterns": [ - { - "name": "constant.numeric.slige", - "match": "\\b0\\b" - }, - { - "name": "constant.numeric.slige", - "match": "\\b[1-9][0-9]*(\\.[0-9]+)?\\b" - }, - { - "name": "constant.numeric.slige", - "match": "\\b0x[0-9a-fA-F]+?\\b" - }, - { - "name": "constant.numeric.slige", - "match": "\\b0b[01]+?\\b" - } - ] - }, - "operators": { ->>>>>>> 53a965f (everything) "patterns": [ { "name": "keyword.control.slige", - "match": "\\b(break|return|let|fn|loop|if|else|struct|import|or|and|not)\\b" + "match": "\\b(break|return|let|fn|loop|if|else|struct|import|or|and|not|while|for|in)\\b" }, { "name": "constant.language.slige", @@ -139,7 +76,7 @@ "operators": { "patterns": [ { - "match": "\\+|\\-|\\*|\\/|=|(==)|(!=)|<|>|(<=)|(>=)|\\.|:|(\\->)|(::)|(::<)", + "match": "\\+|\\-|\\*|\\/|=|(+=)|(-=)|(==)|(!=)|<|>|(<=)|(>=)|\\.|:|(\\->)|(::)|(::<)", "name": "keyword.operator.slige" } ] diff --git a/examples/for.slg b/examples/for.slg new file mode 100644 index 0000000..86d24c4 --- /dev/null +++ b/examples/for.slg @@ -0,0 +1,8 @@ + +fn main() { + + for (let i = 0; i < 10; i += 1) { + + } +} + diff --git a/examples/increment.slg b/examples/increment.slg new file mode 100644 index 0000000..46e5cb8 --- /dev/null +++ b/examples/increment.slg @@ -0,0 +1,6 @@ + +fn main() { + let i = 0; + i += 1; +} + diff --git a/examples/special_loops.slg b/examples/special_loops.slg new file mode 100644 index 0000000..ef95343 --- /dev/null +++ b/examples/special_loops.slg @@ -0,0 +1,80 @@ + +fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {} +fn string_char_at(str: string, index: int) -> int #[builtin(StringCharAt)] {} +fn string_length(str: string) -> int #[builtin(StringLength)] {} + +fn string_array_new() -> [string] #[builtin(ArrayNew)] {} +fn string_array_push(array: [string], value: string) #[builtin(ArrayPush)] {} +fn string_array_length(array: [string]) -> int #[builtin(ArrayLength)] {} +fn string_array_at(array: [string], index: int) -> string #[builtin(ArrayAt)] {} + +fn int_array_new() -> [int] #[builtin(ArrayNew)] {} +fn int_array_push(array: [int], value: int) #[builtin(ArrayPush)] {} +fn int_array_length(array: [int]) -> int #[builtin(ArrayLength)] {} +fn int_array_at(array: [int], index: int) -> int #[builtin(ArrayAt)] {} + +fn file_open(filename: string, mode: string) -> int #[builtin(FileOpen)] {} +fn file_close(file: int) #[builtin(FileClose)] {} +fn file_write_string(file: int, content: string) -> int #[builtin(FileWriteString)] {} +fn file_read_char(file: int) -> int #[builtin(FileReadChar)] {} +fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {} +fn file_flush(file: int) #[builtin(FileFlush)] {} +fn file_eof(file: int) -> bool #[builtin(FileEof)] {} + +fn stdin() -> int { 0 } +fn stdout() -> int { 1 } +fn stderr() -> int { 2 } + +fn file_read_line(file: int) -> string { + let line = ""; + loop { + if file_eof(file) { + break; + } + let ch = file_read_char(file); + if ch == "\n"[0] { + break; + } + line = string_push_char(line, ch); + } + line +} + +fn print(msg: string) #[builtin(Print)] {} +fn println(msg: string) { print(msg + "\n") } + +fn input(prompt: string) -> string { + print("> "); + file_flush(stdout()); + file_read_line(stdin()) +} + +// + +fn main() { + let i = 0; + while i < 3 { + println("hello world"); + i += 1; + } + + let chars = string_to_array("12435"); + + for char in chars { + println(string_push_char("", char)); + } +} + +fn string_to_array(value: string) -> [int] { + let result = int_array_new(); + let length = string_length(value); + + for (let i = 0; i < length; i += 1) { + int_array_push(result, value[i]); + } + + result +} + + + diff --git a/examples/while.slg b/examples/while.slg new file mode 100644 index 0000000..a8253d1 --- /dev/null +++ b/examples/while.slg @@ -0,0 +1,8 @@ + +fn main() { + let i = 0; + while i < 10 { + i += 1; + } +} + diff --git a/runtime/alloc.cpp b/runtime/alloc.cpp index b1c587e..374bb4b 100644 --- a/runtime/alloc.cpp +++ b/runtime/alloc.cpp @@ -8,7 +8,7 @@ auto Array::at(int32_t index) & -> Value& { if (index >= static_cast(this->values.size()) || index < 0) { std::cout << std::format( - "index not in range, expected to be in range (0..{}), got: {}", + "index not in range, expected to be in range (0..{}), got: {}\n", this->values.size(), index); exit(1); } diff --git a/runtime/value.cpp b/runtime/value.cpp index 34d0c4a..baf4c44 100644 --- a/runtime/value.cpp +++ b/runtime/value.cpp @@ -8,7 +8,7 @@ auto String::at(int32_t index) -> int32_t { if (index >= static_cast(this->value.length()) || index < 0) { std::cout << std::format( - "index not in range, expected to be in range (0..{}), got: {}", + "index not in range, expected to be in range (0..{}), got: {}\n", this->value.length() - 1, index); exit(1); } diff --git a/stdlib.slg b/stdlib.slg new file mode 100644 index 0000000..897e8fc --- /dev/null +++ b/stdlib.slg @@ -0,0 +1,51 @@ + +fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {} +fn string_char_at(str: string, index: int) -> int #[builtin(StringCharAt)] {} +fn string_length(str: string) -> int #[builtin(StringLength)] {} + +fn string_array_new() -> [string] #[builtin(ArrayNew)] {} +fn string_array_push(array: [string], value: string) #[builtin(ArrayPush)] {} +fn string_array_length(array: [string]) -> int #[builtin(ArrayLength)] {} +fn string_array_at(array: [string], index: int) -> string #[builtin(ArrayAt)] {} + +fn int_array_new() -> [int] #[builtin(ArrayNew)] {} +fn int_array_push(array: [int], value: int) #[builtin(ArrayPush)] {} +fn int_array_length(array: [int]) -> int #[builtin(ArrayLength)] {} +fn int_array_at(array: [int], index: int) -> int #[builtin(ArrayAt)] {} + +fn file_open(filename: string, mode: string) -> int #[builtin(FileOpen)] {} +fn file_close(file: int) #[builtin(FileClose)] {} +fn file_write_string(file: int, content: string) -> int #[builtin(FileWriteString)] {} +fn file_read_char(file: int) -> int #[builtin(FileReadChar)] {} +fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {} +fn file_flush(file: int) #[builtin(FileFlush)] {} +fn file_eof(file: int) -> bool #[builtin(FileEof)] {} + +fn stdin() -> int { 0 } +fn stdout() -> int { 1 } +fn stderr() -> int { 2 } + +fn file_read_line(file: int) -> string { + let line = ""; + loop { + if file_eof(file) { + break; + } + let ch = file_read_char(file); + if ch == "\n"[0] { + break; + } + line = string_push_char(line, ch); + } + line +} + +fn print(msg: string) #[builtin(Print)] {} +fn println(msg: string) { print(msg + "\n") } + +fn input(prompt: string) -> string { + print("> "); + file_flush(stdout()); + file_read_line(stdin()) +} +