UNPKG

luma-lang

Version:

The Embeddable Luma Language Compiler and Runtime

1,575 lines (1,564 loc) 168 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/LumaError.ts var LumaError = class extends Error { constructor(options) { super(options.message, { cause: options.cause }); __publicField(this, "moduleName"); __publicField(this, "position"); this.moduleName = options.moduleName; this.position = options.position; } }; // src/Tokenizer/Keywords.ts var Keywords = [ "fn", "on", "if", "else", "do", "while", "for", "in", "break", "continue", "return", "true", "false", "null", "not", "this", "and", "or", "local", "public", "import", "wait", "new", "class", "extends", "parent" ]; // src/Tokenizer/Operators.ts var Operators = { ASSIGN: "=", PLUS: "+", MINUS: "-", MULTIPLY: "*", DIVIDE: "/", MODULO: "%", EXPONENTIATION: "^", EQUALS: "==", NOT_EQUALS: "!=", GREATER_THAN: ">", LESS_THAN: "<", GREATER_EQUAL: ">=", LESS_EQUAL: "<=", NOT: "!", RANGE: "..", IS: "is" }; // src/Tokenizer/Punctuation.ts var Punctuation = { MEMBER_ACCESS: ".", COMMA: ",", COLON: ":", QUESTION_MARK: "?", OPEN_PAREN: "(", CLOSE_PAREN: ")", OPEN_BRACKET: "[", CLOSE_BRACKET: "]", OPEN_BRACE: "{", CLOSE_BRACE: "}", BANG: "!" }; // src/Tokenizer/TokenStream.ts var TokenStream = class { constructor(tokens) { __publicField(this, "_tokens"); __publicField(this, "_index", 0); this._tokens = tokens; } get tokens() { return [...this._tokens]; } get length() { return this._tokens.length; } peek(offset = 0) { const targetIndex = this._index + offset; if (targetIndex < 0 || targetIndex >= this._tokens.length) { return null; } return this._tokens[targetIndex]; } consume() { const token = this.peek(0); if (!token) { throw new Error("Unexpected End of File"); } this._index++; return token; } get isEof() { return this._index >= this._tokens.length; } }; // src/Tokenizer/TokenType.ts var TokenType = { IDENTIFIER: "IDENTIFIER", KEYWORD: "KEYWORD", OPERATOR: "OPERATOR", PUNCTUATION: "PUNCTUATION", STRING: "STRING", NUMBER: "NUMBER", COMMENT: "COMMENT", BLOCK_COMMENT: "BLOCK_COMMENT", NEWLINE: "NEWLINE", INDENT: "INDENT", DEDENT: "DEDENT", EOF: "EOF" }; // src/Tokenizer/Tokenizer.ts var _Tokenizer = class _Tokenizer { constructor(source, moduleName) { __publicField(this, "_tokens", []); __publicField(this, "_source"); __publicField(this, "_moduleName"); __publicField(this, "index", 0); __publicField(this, "line", 1); __publicField(this, "col", 1); __publicField(this, "indentStack", [0]); __publicField(this, "isAtStartOfLine", true); this._source = source.replace(/\r\n/g, "\n"); this._moduleName = moduleName; } static tokenize(source, moduleName = void 0) { if (!source.endsWith("\n")) { source += "\n"; } return new _Tokenizer(source, moduleName).tokenize(); } tokenize() { while (!this.isEof) { if (this.isAtStartOfLine) { if (this.handleIndentation()) { continue; } } const char = this._source[this.index]; if (char === "\n") { this.handleNewline(); continue; } if (char === " " || char === " ") { this.advance(1); continue; } if (this.parseComment()) continue; if (this.parseBlockComment()) continue; if (this.parseSymbol()) continue; if (this.parseNumberLiteral()) continue; if (this.parseStringLiteral()) continue; if (this.parseWord()) continue; this.throwUnexpectedCharacterError(); } while (this.indentStack.length > 1) { this.indentStack.pop(); this._tokens.push(this.createToken(TokenType.DEDENT, "")); } return new TokenStream(this._tokens); } handleNewline() { const lastToken = this._tokens[this._tokens.length - 1]; if (lastToken && lastToken.type !== TokenType.NEWLINE && lastToken.type !== TokenType.INDENT && lastToken.type !== TokenType.DEDENT) { this._tokens.push(this.createToken(TokenType.NEWLINE, "\\n")); } this.index++; this.line++; this.col = 1; this.isAtStartOfLine = true; } handleIndentation() { let spaces = 0; let tempIndex = this.index; while (tempIndex < this._source.length && (this._source[tempIndex] === " " || this._source[tempIndex] === " ")) { spaces += this._source[tempIndex] === " " ? 4 : 1; tempIndex++; } const char = this._source[tempIndex]; if (tempIndex >= this._source.length || char === "\n" || this._source.startsWith("//", tempIndex) || this._source.startsWith("/*", tempIndex)) { this.isAtStartOfLine = false; return false; } this.advance(tempIndex - this.index); const currentIndent = this.indentStack[this.indentStack.length - 1]; if (spaces > currentIndent) { this.indentStack.push(spaces); this._tokens.push(this.createToken(TokenType.INDENT, spaces.toString())); } else if (spaces < currentIndent) { while (spaces < this.indentStack[this.indentStack.length - 1]) { this.indentStack.pop(); this._tokens.push(this.createToken(TokenType.DEDENT, "")); } if (spaces !== this.indentStack[this.indentStack.length - 1]) { this.throwError("Indentation error: Indent level does not match any outer block"); } } this.isAtStartOfLine = false; return false; } parseWord() { let tempIndex = this.index; if (!/[a-zA-Z_]/.test(this._source[tempIndex])) return false; while (tempIndex < this._source.length && /[a-zA-Z0-9_]/.test(this._source[tempIndex])) { tempIndex++; } const value = this._source.slice(this.index, tempIndex); const type = _Tokenizer.KEYWORD_SET.has(value) ? TokenType.KEYWORD : TokenType.IDENTIFIER; this._tokens.push(this.createToken(type, value)); this.advance(value.length); return true; } parseSymbol() { for (const symbol of _Tokenizer.SORTED_SYMBOLS) { if (this._source.startsWith(symbol, this.index)) { const type = _Tokenizer.PUNCTUATION_SET.has(symbol) ? TokenType.PUNCTUATION : TokenType.OPERATOR; this._tokens.push(this.createToken(type, symbol)); this.advance(symbol.length); return true; } } return false; } parseNumberLiteral() { const char = this._source[this.index]; const nextChar = this._source[this.index + 1]; const isDigit = char >= "0" && char <= "9"; const isDotStart = char === "." && (nextChar >= "0" && nextChar <= "9"); if (!isDigit && !isDotStart) return false; let tempIndex = this.index; let hasDot = false; while (tempIndex < this._source.length) { const c = this._source[tempIndex]; const next = this._source[tempIndex + 1]; if (c >= "0" && c <= "9") { tempIndex++; continue; } if (c === "_") { tempIndex++; continue; } if (c === ".") { if (next === ".") { break; } if (hasDot) { break; } hasDot = true; tempIndex++; continue; } if (c === "e" || c === "E") { tempIndex++; if (this._source[tempIndex] === "+" || this._source[tempIndex] === "-") { tempIndex++; } continue; } break; } const value = this._source.substring(this.index, tempIndex); this._tokens.push(this.createToken(TokenType.NUMBER, value)); this.advance(tempIndex - this.index); return true; } parseStringLiteral() { const quoteChar = this._source[this.index]; if (quoteChar !== '"' && quoteChar !== "'") return false; const startLine = this.line; const startCol = this.col; this.advance(1); let rawContent = ""; let braceCount = 0; let maybeInterpolation = false; let hasInterpolation = false; let isMultiLine = false; while (!this.isEof) { const char = this._source[this.index]; if (char === "\\") { rawContent += char; this.advance(1); if (!this.isEof) { const nextChar = this._source[this.index]; rawContent += nextChar; this.advance(1); } continue; } if (char === "{") { braceCount++; maybeInterpolation = true; } else if (char === "}" && braceCount > 0) { braceCount--; } if (char === quoteChar) { this.advance(1); if (maybeInterpolation && braceCount === 0) { hasInterpolation = true; } if (maybeInterpolation && braceCount > 0) { rawContent += char; continue; } break; } if (char === "\n") { isMultiLine = true; this.line++; this.col = 1; } rawContent += char; this.advance(1); } if (this.isEof && this._source[this.index - 1] !== quoteChar) { this.throwError("Unterminated string literal"); } if (isMultiLine) { rawContent = this.stripIndentation(rawContent); } if (braceCount !== 0) { throw new Error(`Unterminated interpolation expression in string literal at line ${startLine}, column ${startCol}`); } if (!hasInterpolation) { this._tokens.push({ type: TokenType.STRING, value: this.unescapeString(rawContent), position: { lineStart: startLine, columnStart: startCol, lineEnd: this.line, columnEnd: this.col } }); return true; } const segments = this.parseInterpolationSegments(rawContent); let hasEmittedTokens = false; for (const segment of segments) { if (hasEmittedTokens) { this._tokens.push({ type: TokenType.OPERATOR, value: Operators.PLUS, position: this.currentPos() }); } if (segment.type === "text") { const unescapedValue = this.unescapeString(segment.value); hasEmittedTokens = true; this._tokens.push({ type: TokenType.STRING, value: unescapedValue, position: { lineStart: startLine, columnStart: startCol, lineEnd: this.line, columnEnd: this.col } }); continue; } if (segment.type === "expr") { hasEmittedTokens = true; const tokens = this.tokenizeExpression(segment.value); if (tokens.length === 0) { this.throwError("Empty expression in string interpolation"); } this._tokens.push({ type: TokenType.PUNCTUATION, value: "(", position: { ...this.currentPos() } }); for (const token of tokens) { this._tokens.push(token); } this._tokens.push({ type: TokenType.PUNCTUATION, value: ")", position: { ...this.currentPos() } }); } } return true; } parseInterpolationSegments(rawInput) { const segments = []; let currentText = ""; let i = 0; let hasIndentedContent = false; while (i < rawInput.length) { const char = rawInput[i]; if (char === "\\") { currentText += char; i++; if (i < rawInput.length) { currentText += rawInput[i]; i++; } continue; } if (char === "{") { const result = this.extractBalancedExpression(rawInput, i + 1); if (result !== null) { hasIndentedContent = hasIndentedContent || currentText.trim().length > 0; segments.push({ type: "text", value: currentText }); currentText = ""; segments.push({ type: "expr", value: result.code }); i = result.endIndex + 1; continue; } } currentText += char; hasIndentedContent = hasIndentedContent || currentText.trim().length > 0; i++; } segments.push({ type: "text", value: currentText }); if (hasIndentedContent) { this.stripInterpolatedIndentation(segments); } return segments; } extractBalancedExpression(input, startIndex) { for (let i = startIndex, braceCount = 1; i < input.length; i++) { const char = input[i]; if (char === "\\") { i++; continue; } if (char === "{") { braceCount++; } else if (char === "}") { braceCount--; if (braceCount === 0) { return { code: input.slice(startIndex, i), endIndex: i }; } } } return null; } unescapeString(raw) { let result = ""; let i = 0; while (i < raw.length) { if (raw[i] === "\\" && i + 1 < raw.length) { const next = raw[i + 1]; switch (next) { case "n": result += "\n"; break; case "t": result += " "; break; case "r": result += "\r"; break; case '"': result += '"'; break; case "'": result += "'"; break; case "\\": result += "\\"; break; case "{": result += "{"; break; case "}": result += "}"; break; default: result += "\\" + next; } i += 2; } else { result += raw[i]; i++; } } return result; } currentPos() { return { lineStart: this.line, columnStart: this.col, lineEnd: this.line, columnEnd: this.col + 1 }; } stripInterpolatedIndentation(segments) { let minIndent = Infinity; for (const seg of segments) { if (seg.type !== "text") continue; const lines = seg.value.split("\n"); for (let i = 1; i < lines.length; i++) { const line = lines[i]; if (line.trim().length === 0) continue; let indent = 0; while (indent < line.length && (line[indent] === " " || line[indent] === " ")) { indent++; } if (indent < minIndent) minIndent = indent; } } if (minIndent === Infinity) minIndent = 0; for (const seg of segments) { if (seg.type !== "text") continue; const lines = seg.value.split("\n"); for (let i = 1; i < lines.length; i++) { if (lines[i].length >= minIndent) { lines[i] = lines[i].slice(minIndent); } } seg.value = lines.join("\n"); } if (segments[0].type === "text" && segments[0].value.startsWith("\n")) { segments[0].value = segments[0].value.slice(1); } const last = segments[segments.length - 1]; if (last.type === "text") { const lines = last.value.split("\n"); if (lines.length > 0 && lines[lines.length - 1].trim() === "") { lines.pop(); last.value = lines.join("\n"); } } } stripIndentation(raw) { const lines = raw.split("\n"); if (lines.length > 0 && lines[0].trim() === "") { lines.shift(); } if (lines.length > 0) { const lastLine = lines[lines.length - 1]; if (lastLine.trim() === "") { lines.pop(); } } let minIndent = Infinity; for (const line of lines) { if (line.trim().length === 0) continue; let indent = 0; while (indent < line.length && (line[indent] === " " || line[indent] === " ")) { indent++; } if (indent < minIndent) minIndent = indent; } if (minIndent === Infinity) minIndent = 0; return lines.map((line) => { if (line.length < minIndent) return line.trim(); return line.slice(minIndent); }).join("\n"); } parseComment() { if (!this._source.startsWith("//", this.index)) return false; while (!this.isEof && this._source[this.index] !== "\n") { this.advance(1); } return true; } parseBlockComment() { if (!this._source.startsWith("/*", this.index)) return false; this.advance(2); while (!this.isEof && !this._source.startsWith("*/", this.index)) { if (this._source[this.index] === "\n") { this.line++; this.col = 1; this.index++; } else { this.advance(1); } } if (!this.isEof) this.advance(2); return true; } createToken(type, value) { return { type, value, position: { lineStart: this.line, columnStart: this.col, lineEnd: this.line, columnEnd: this.col + value.length } }; } advance(n) { this.index += n; this.col += n; } get isEof() { return this.index >= this._source.length; } throwError(msg) { throw new LumaError({ message: `${msg} at line ${this.line}, column ${this.col}`, moduleName: this._moduleName, position: { lineStart: this.line, columnStart: this.col, lineEnd: this.line, columnEnd: this.col + 1 } }); } throwUnexpectedCharacterError() { this.throwError(`Unexpected character "${this._source[this.index]}"`); } tokenizeExpression(str) { const tokens = _Tokenizer.tokenize(str.trim()).tokens; return tokens.filter((t) => t.type !== TokenType.NEWLINE && t.type !== TokenType.INDENT && t.type !== TokenType.DEDENT); } }; __publicField(_Tokenizer, "KEYWORD_SET", new Set(Keywords)); __publicField(_Tokenizer, "PUNCTUATION_SET", new Set(Object.values(Punctuation))); __publicField(_Tokenizer, "SYMBOL_MAP", new Map([ ...Object.entries(Operators).map(([k, v]) => [v, k]), ...Object.entries(Punctuation).map(([k, v]) => [v, k]) ])); __publicField(_Tokenizer, "SORTED_SYMBOLS", Array.from(_Tokenizer.SYMBOL_MAP.keys()).sort((a, b) => b.length - a.length)); var Tokenizer = _Tokenizer; // src/Parser/Parser.ts var Parser = class _Parser { constructor(stream, moduleName) { __publicField(this, "stream"); __publicField(this, "moduleName"); __publicField(this, "lastToken"); this.stream = stream; this.moduleName = moduleName; } static parse(tokens, moduleName = void 0) { return new _Parser(tokens, moduleName).parse(); } parse() { try { const statements = []; while (!this.stream.isEof) { if (this.match(TokenType.NEWLINE)) continue; statements.push(this.parseStatement()); } return { type: "Script", body: statements, position: { lineStart: 1, lineEnd: 1, columnStart: 1, columnEnd: 1 } }; } catch (e) { if (!(e instanceof Error)) { console.warn("Parser did not throw an instance of Error!", e); throw e; } throw new LumaError({ message: e.message, moduleName: this.moduleName, position: this.lastToken?.position ?? { lineStart: 1, lineEnd: 1, columnStart: 1, columnEnd: 1 }, cause: e }); } } parseStatement() { const token = this.stream.peek(); if (!token) throw new Error("Unexpected EOF"); let isPublic = false, isLocal = false; if (this.match(TokenType.KEYWORD, "public")) { isPublic = true; } if (this.match(TokenType.KEYWORD, "local")) { isLocal = true; } if (this.check(TokenType.KEYWORD, "fn")) { if (isLocal) { throw new Error("Functions cannot be declared as 'local'. They are local by default unless marked 'public'."); } return this.parseFunctionDeclaration(isPublic); } if (this.check(TokenType.KEYWORD, "class")) { if (isLocal) { throw new Error("Classes cannot be declared as 'local'. They are local by default unless marked 'public'."); } return this.parseClassStatement(isPublic); } if (isPublic || isLocal) { return this.parseExpressionStatement(isPublic, isLocal); } if (token.type === TokenType.KEYWORD) { switch (token.value) { case "import": return this.parseImportStatement(); case "wait": return this.parseWaitStatement(); case "on": return this.parseEventHook(); case "return": return this.parseReturnStatement(); case "if": return this.parseIfStatement(); case "for": return this.parseForStatement(); case "while": return this.parseWhileStatement(); case "do": return this.parseDoWhileStatement(); case "break": return this.parseBreakStatement(); case "continue": return this.parseContinueStatement(); } } return this.parseExpressionStatement(); } parseEventHook() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "on"); const name = this.parseIdentifier(); const params = []; if (this.match(TokenType.PUNCTUATION, "(")) { if (!this.check(TokenType.PUNCTUATION, ")")) { do { params.push(this.parseIdentifier()); } while (this.match(TokenType.PUNCTUATION, ",")); } this.consume(TokenType.PUNCTUATION, ")"); } this.consume(TokenType.PUNCTUATION, ":"); const body = this.parseBlock(); return { type: "EventHook", name, params, body, position }; } parseClassStatement(isPublic = false) { const startPos = this.currentPos(); this.consume(TokenType.KEYWORD, "class"); const name = this.parseIdentifier(); const params = []; if (this.match(TokenType.PUNCTUATION, "(")) { if (!this.check(TokenType.PUNCTUATION, ")")) { do { if (this.match(TokenType.KEYWORD, "this")) { this.consume(TokenType.PUNCTUATION, "."); const id = this.parseIdentifier(); params.push({ type: "PromotedParameter", value: id.value, position: id.position }); } else { params.push(this.parseIdentifier()); } } while (this.match(TokenType.PUNCTUATION, ",")); } this.consume(TokenType.PUNCTUATION, ")"); } let parent = void 0, parentArgs = []; if (this.match(TokenType.KEYWORD, "extends")) { let primary = this.parsePrimary(); while (this.match(TokenType.PUNCTUATION, ".")) { const property = this.parseIdentifier(); primary = { type: "MemberExpression", object: primary, property, computed: false, position: primary.position // or combine positions }; } parent = primary; if (this.match(TokenType.PUNCTUATION, "(")) { if (!this.check(TokenType.PUNCTUATION, ")")) { do { parentArgs.push(this.parseExpression()); } while (this.match(TokenType.PUNCTUATION, ",")); } this.consume(TokenType.PUNCTUATION, ")"); } } if (!this.check(TokenType.PUNCTUATION, ":")) { return { type: "ClassStatement", name, properties: [], params, methods: [], position: startPos, isPublic, parent, parentArgs }; } this.consume(TokenType.PUNCTUATION, ":"); this.consume(TokenType.NEWLINE); this.consume(TokenType.INDENT); const properties = []; const methods = []; while (!this.match(TokenType.DEDENT) && !this.stream.isEof) { if (this.match(TokenType.NEWLINE)) { continue; } if (this.check(TokenType.KEYWORD, "fn")) { const method = this.parseFunctionDeclaration(false); if (method.type !== "FunctionDeclaration") { throw new Error("Methods inside classes cannot be defined as method definitions."); } methods.push(method); continue; } if (this.check(TokenType.IDENTIFIER)) { const key = this.parseIdentifier(); if (this.match(TokenType.PUNCTUATION, ":") || this.match(TokenType.OPERATOR, "=")) { const value = this.parseExpression(); properties.push({ key, value }); if (this.check(TokenType.NEWLINE)) { this.consume(TokenType.NEWLINE); } continue; } else { throw new Error(`Expected ':' for property definition.`); } } throw new Error("Expected 'fn' or property definition inside class body"); } return { type: "ClassStatement", name, properties, params, methods, position: startPos, isPublic, parent, parentArgs }; } parseFunctionDeclaration(isPublic = false) { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "fn"); const name = this.parseIdentifier(); let methodName = null; if (this.match(TokenType.PUNCTUATION, Punctuation.MEMBER_ACCESS)) { methodName = this.consume(TokenType.IDENTIFIER).value; } const params = []; if (this.match(TokenType.PUNCTUATION, "(")) { if (!this.check(TokenType.PUNCTUATION, ")")) { do { params.push(this.parseIdentifier()); } while (this.match(TokenType.PUNCTUATION, ",")); } this.consume(TokenType.PUNCTUATION, ")"); } this.consume(TokenType.PUNCTUATION, ":"); const body = this.parseBlock(); if (methodName && isPublic) { throw new Error("Method definitions cannot be public. You should mark the object as public instead."); } return methodName ? { type: "MethodDefinition", objectName: name, methodName, params, body, position } : { type: "FunctionDeclaration", name, params, body, position, isPublic }; } parseBlock() { this.consume(TokenType.NEWLINE); if (!this.check(TokenType.INDENT)) { const position2 = this.currentPos(); return { type: "Block", body: [], position: position2 }; } const position = this.currentPos(); this.consume(TokenType.INDENT); const statements = []; while (!this.check(TokenType.DEDENT) && !this.stream.isEof) { if (this.match(TokenType.NEWLINE)) continue; statements.push(this.parseStatement()); } this.consume(TokenType.DEDENT); return { type: "Block", body: statements, position }; } parseImportStatement() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "import"); const moduleNameToken = this.consume(TokenType.STRING); const moduleName = moduleNameToken.value; this.consume(TokenType.NEWLINE); return { type: "ImportStatement", moduleName, position }; } parseWaitStatement() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "wait"); const duration = this.parseExpression(); this.consume(TokenType.NEWLINE); return { type: "WaitStatement", duration, position }; } parseReturnStatement() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "return"); let argument; if (!this.check(TokenType.NEWLINE)) argument = this.parseExpression(); this.consume(TokenType.NEWLINE); return { type: "ReturnStatement", argument, position }; } parseIfStatement() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "if"); const test = this.parseExpression(); this.consume(TokenType.PUNCTUATION, ":"); const consequent = this.parseBlock(); let alternate; if (this.match(TokenType.KEYWORD, "else")) { this.consume(TokenType.PUNCTUATION, ":"); alternate = this.parseBlock(); } return { type: "IfStatement", test, consequent, alternate, position }; } parseForStatement() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "for"); const hasParen = this.match(TokenType.PUNCTUATION, "("); const iterator = this.parseIdentifier(); if (!this.match(TokenType.KEYWORD, "in")) { throw new Error("Expected 'in' after for-loop iterator"); } const collection = this.parseExpression(); if (hasParen) { this.consume(TokenType.PUNCTUATION, ")"); } this.consume(TokenType.PUNCTUATION, ":"); const body = this.parseBlock(); return { type: "ForStatement", iterator, collection, body, position }; } parseWhileStatement() { const startPos = this.currentPos(); this.consume(TokenType.KEYWORD, "while"); const condition = this.parseExpression(); this.consume(TokenType.PUNCTUATION, ":"); const body = this.parseBlock(); return { type: "WhileStatement", condition, body, position: startPos }; } parseDoWhileStatement() { const startPos = this.currentPos(); this.consume(TokenType.KEYWORD, "do"); this.consume(TokenType.PUNCTUATION, ":"); const body = this.parseBlock(); this.consume(TokenType.KEYWORD, "while"); const condition = this.parseExpression(); return { type: "DoWhileStatement", body, condition, position: startPos }; } parseBreakStatement() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "break"); if (!this.stream.isEof) this.match(TokenType.NEWLINE); return { type: "BreakStatement", position }; } parseContinueStatement() { const position = this.currentPos(); this.consume(TokenType.KEYWORD, "continue"); if (!this.stream.isEof) this.match(TokenType.NEWLINE); return { type: "ContinueStatement", position }; } parseExpressionStatement(isPublic = false, isLocal = false) { const position = this.currentPos(); const expression = this.parseExpression(isPublic, isLocal); if (!this.stream.isEof) this.consume(TokenType.NEWLINE); return { type: "ExpressionStatement", expression, position }; } parseExpression(isPublic = false, isLocal = false) { let left = this.parseLogicalOr(); if (isPublic || isLocal) { if (left.type !== "Identifier") { throw new Error("Only identifiers can be marked as public or local in assignments."); } if (!this.check(TokenType.OPERATOR, "=")) { throw new Error(`Expected assignment operator '=' after public/local identifier "${left.value}".`); } } if (this.match(TokenType.OPERATOR, "=")) { const right = this.parseExpression(); return { type: "AssignmentExpression", left, operator: "=", right, isPublic, isLocal }; } return left; } parseLogicalOr() { let left = this.parseLogicalAnd(); while (this.match(TokenType.KEYWORD, "or")) { const right = this.parseLogicalAnd(); left = { type: "LogicalExpression", operator: "or", left, right }; } return left; } parseLogicalAnd() { let left = this.parseEquality(); while (this.match(TokenType.KEYWORD, "and")) { const right = this.parseEquality(); left = { type: "LogicalExpression", operator: "and", left, right }; } return left; } parseEquality() { let left = this.parseRelational(); while (this.check(TokenType.OPERATOR, "==") || this.check(TokenType.OPERATOR, "!=")) { const operator = this.stream.consume().value; const right = this.parseRelational(); left = { type: "BinaryExpression", left, operator, right }; } return left; } parseRelational() { let left = this.parseRange(); while (this.check(TokenType.OPERATOR, "<") || this.check(TokenType.OPERATOR, ">") || this.check(TokenType.OPERATOR, "<=") || this.check(TokenType.OPERATOR, ">=") || this.check(TokenType.OPERATOR, "is") || this.check(TokenType.KEYWORD, "in") || this.check(TokenType.KEYWORD, "not")) { if (this.match(TokenType.KEYWORD, "not")) { if (!this.match(TokenType.KEYWORD, "in")) { throw new Error("Unexpected token 'not'. Did you mean 'not in'?"); } const operator2 = "not in"; const right2 = this.parseAdditive(); left = { type: "BinaryExpression", left, operator: operator2, right: right2 }; continue; } const token = this.stream.consume(); const operator = token.value; const right = this.parseAdditive(); left = { type: "BinaryExpression", left, operator, right }; } return left; } parseRange() { let left = this.parseAdditive(); while (this.check(TokenType.OPERATOR, "..")) { const operator = this.stream.consume().value; const right = this.parseAdditive(); left = { type: "BinaryExpression", left, operator, right }; } return left; } parseAdditive() { let left = this.parseMultiplicative(); while (this.check(TokenType.OPERATOR, "+") || this.check(TokenType.OPERATOR, "-")) { const operator = this.stream.consume().value; const right = this.parseMultiplicative(); left = { type: "BinaryExpression", left, operator, right }; } return left; } parseMultiplicative() { let left = this.parseUnary(); while (this.check(TokenType.OPERATOR, "*") || this.check(TokenType.OPERATOR, "/") || this.check(TokenType.OPERATOR, "%")) { const operator = this.stream.consume().value; const right = this.parseUnary(); left = { type: "BinaryExpression", left, operator, right }; } return left; } parseUnary() { if (this.match(TokenType.KEYWORD, "not") || this.match(TokenType.PUNCTUATION, "!")) { const argument = this.parseUnary(); return { type: "UnaryExpression", operator: "not", argument }; } if (this.match(TokenType.OPERATOR, "-")) { const argument = this.parseUnary(); return { type: "UnaryExpression", operator: "-", argument }; } return this.parseExponentiation(); } parseExponentiation() { const left = this.parsePostfix(); if (this.match(TokenType.OPERATOR, "^")) { const operator = "^"; const right = this.parseUnary(); return { type: "BinaryExpression", left, operator, right }; } return left; } parsePostfix() { let left = this.parsePrimary(); while (true) { if (this.match(TokenType.PUNCTUATION, "(")) { const args = []; if (!this.check(TokenType.PUNCTUATION, ")")) { do { args.push(this.parseExpression()); } while (this.match(TokenType.PUNCTUATION, ",")); } this.consume(TokenType.PUNCTUATION, ")"); left = { type: "CallExpression", callee: left, arguments: args }; } else if (this.match(TokenType.PUNCTUATION, ".")) { const property = this.parseIdentifier(); left = { type: "MemberExpression", object: left, property, computed: false }; } else if (this.match(TokenType.PUNCTUATION, "[")) { const property = this.parseExpression(); this.consume(TokenType.PUNCTUATION, "]"); left = { type: "MemberExpression", object: left, property, computed: true }; } else { break; } } return left; } parsePrimary() { const token = this.stream.peek(); if (token?.type === TokenType.NUMBER) { this.stream.consume(); return { type: "Literal", value: Number(token.value), raw: token.value }; } if (token?.type === TokenType.STRING) { this.stream.consume(); return { type: "Literal", value: token.value, raw: token.value }; } if (token?.type === TokenType.KEYWORD) { if (token.value === "this") { this.stream.consume(); return { type: "ThisExpression" }; } if (token.value === "parent") { this.stream.consume(); const startPos = this.currentPos(); this.consume(TokenType.PUNCTUATION, "."); const method = this.parseIdentifier(); const args = []; this.consume(TokenType.PUNCTUATION, "("); if (!this.check(TokenType.PUNCTUATION, ")")) { do { args.push(this.parseExpression()); } while (this.match(TokenType.PUNCTUATION, ",")); } this.consume(TokenType.PUNCTUATION, ")"); return { type: "ParentMethodCallExpression", methodName: method, arguments: args, position: startPos }; } if (token.value === "true") { this.stream.consume(); return { type: "Literal", value: true, raw: "true" }; } if (token.value === "false") { this.stream.consume(); return { type: "Literal", value: false, raw: "false" }; } if (token.value === "null") { this.stream.consume(); return { type: "Literal", value: null, raw: "null" }; } if (token.value === "new") { this.stream.consume(); const startPos = this.currentPos(); let className = this.parseIdentifier(); while (this.match(TokenType.PUNCTUATION, ".")) { const property = this.parseIdentifier(); className = { type: "MemberExpression", object: className, property, computed: false }; } const args = []; if (this.match(TokenType.PUNCTUATION, "(")) { if (!this.check(TokenType.PUNCTUATION, ")")) { do { args.push(this.parseExpression()); } while (this.match(TokenType.PUNCTUATION, ",")); } this.consume(TokenType.PUNCTUATION, ")"); } return { type: "NewExpression", className, arguments: args, position: startPos }; } } if (this.match(TokenType.PUNCTUATION, "[")) { return this.parseArrayExpression(); } if (this.match(TokenType.PUNCTUATION, "{")) { return this.parseObjectExpression(); } if (token?.type === TokenType.IDENTIFIER) { return this.parseIdentifier(); } if (this.match(TokenType.PUNCTUATION, "(")) { const expr = this.parseExpression(); this.consume(TokenType.PUNCTUATION, ")"); return expr; } throw new Error(`Unexpected token in Expression: ${token?.value}`); } skipFormatting() { while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) { } } parseArrayExpression() { this.skipFormatting(); const position = this.currentPos(); if (this.check(TokenType.PUNCTUATION, "]")) { this.consume(TokenType.PUNCTUATION, "]"); return { type: "ArrayExpression", elements: [], position }; } const firstExpr = this.parseExpression(); this.skipFormatting(); if (this.match(TokenType.KEYWORD, "for")) { const iteratorToken = this.consume(TokenType.IDENTIFIER); this.consume(TokenType.KEYWORD, "in"); const collection = this.parseExpression(); this.skipFormatting(); this.consume(TokenType.PUNCTUATION, "]"); return { type: "ArrayComprehension", expression: firstExpr, iterator: { type: "Identifier", value: iteratorToken.value, position: iteratorToken.position }, collection, position }; } const elements = [firstExpr]; while (this.match(TokenType.PUNCTUATION, ",")) { this.skipFormatting(); if (this.check(TokenType.PUNCTUATION, "]")) break; elements.push(this.parseExpression()); this.skipFormatting(); } this.consume(TokenType.PUNCTUATION, "]"); return { type: "ArrayExpression", elements, position }; } parseObjectExpression() { this.skipFormatting(); const startPos = this.currentPos(); if (this.check(TokenType.PUNCTUATION, "}")) { this.consume(TokenType.PUNCTUATION, "}"); return { type: "ObjectExpression", properties: [], position: startPos }; } const firstKey = this.parseIdentifier(); this.consume(TokenType.PUNCTUATION, ":"); const firstValue = this.parseExpression(); if (this.match(TokenType.KEYWORD, "for")) { const iterator = this.parseIdentifier(); this.consume(TokenType.KEYWORD, "in"); const collection = this.parseExpression(); this.consume(TokenType.PUNCTUATION, "}"); return { type: "ObjectComprehension", key: firstKey, value: firstValue, iterator, collection, position: startPos }; } const properties = []; properties.push({ type: "Property", key: firstKey, value: firstValue, position: startPos }); while (!this.check(TokenType.PUNCTUATION, "}")) { this.match(TokenType.PUNCTUATION, ","); this.skipFormatting(); if (this.check(TokenType.PUNCTUATION, "}")) break; const key = this.parseIdentifier(); this.consume(TokenType.PUNCTUATION, ":"); const value = this.parseExpression(); properties.push({ type: "Property", key, value, position: this.currentPos() }); } this.consume(TokenType.PUNCTUATION, "}"); return { type: "ObjectExpression", properties, position: startPos }; } parseIdentifier() { const position = this.currentPos(); const token = this.consume(TokenType.IDENTIFIER); return { type: "Identifier", value: token.value, position }; } consume(type, value) { const token = this.stream.peek(); if (!token || token.type !== type || value && token.value !== value) { const msg = `Expected ${type} ${value || ""}, but found ${token?.type} ${token?.value}`; let pos = ""; if (token?.position) { pos = ` at line ${token.position.lineStart}, column ${token.position.columnStart}`; } throw new Error(msg + pos); } this.lastToken = token; return this.stream.consume(); } match(type, value) { if (this.check(type, value)) { this.stream.consume(); return true; } return false; } check(type, value) { const token = this.stream.peek(); if (!token || token.type !== type) { return false; } return !(value && token.value !== value); } currentPos() { const token = this.stream.peek(); return token ? token.position : { lineStart: 0, lineEnd: 0, columnStart: 0, columnEnd: 0 }; } }; // src/Program/Opcodes.ts var Opcode = /* @__PURE__ */ ((Opcode3) => { Opcode3["HALT"] = "HALT"; Opcode3["CONST"] = "CONST"; Opcode3["SWAP"] = "SWAP"; Opcode3["EXPORT"] = "EXPORT"; Opcode3["IMPORT"] = "IMPORT"; Opcode3["WAIT"] = "WAIT"; Opcode3["NEW"] = "NEW"; Opcode3["SUPER"] = "SUPER"; Opcode3["ADD"] = "ADD"; Opcode3["SUB"] = "SUB"; Opcode3["MUL"] = "MUL"; Opcode3["DIV"] = "DIV"; Opcode3["MOD"] = "MOD"; Opcode3["EXP"] = "EXP"; Opcode3["NEG"] = "NEG"; Opcode3["EQ"] = "EQ"; Opcode3["NEQ"] = "NEQ"; Opcode3["GT"] = "GT"; Opcode3["GTE"] = "GTE"; Opcode3["LT"] = "LT"; Opcode3["LTE"] = "LTE"; Opcode3["IN"] = "IN"; Opcode3["IS"] = "IS"; Opcode3["NOT"] = "NOT"; Opcode3["JMP"] = "JMP"; Opcode3["JMP_IF_FALSE"] = "JMP_IF_FALSE"; Opcode3["JMP_IF_TRUE"] = "JMP_IF_TRUE"; Opcode3["DUP"] = "DUP"; Opcode3["POP"] = "POP"; Opcode3["LOAD"] = "LOAD"; Opcode3["STORE"] = "STORE"; Opcode3["LOAD_LOCAL"] = "LOAD_LOCAL"; Opcode3["STORE_LOCAL"] = "STORE_LOCAL"; Opcode3["CALL"] = "CALL"; Opcode3["CALL_METHOD"] = "CALL_METHOD"; Opcode3["CALL_PARENT"] = "CALL_PARENT"; Opcode3["RET"] = "RET"; Opcode3["MAKE_ARRAY"] = "MAKE_ARRAY"; Opcode3["MAKE_RANGE"] = "MAKE_RANGE"; Opcode3["MAKE_OBJECT"] = "MAKE_OBJECT"; Opcode3["MAKE_FUNCTION"] = "MAKE_FUNCTION"; Opcode3["MAKE_CLASS"] = "MAKE_CLASS"; Opcode3["MAKE_METHOD"] = "MAKE_METHOD"; Opcode3["GET_PROP"] = "GET_PROP"; Opcode3["SET_PROP"] = "SET_PROP"; Opcode3["ITER_INIT"] = "ITER_INIT"; Opcode3["ITER_NEXT"] = "ITER_NEXT"; Opcode3["ARRAY_PUSH"] = "ARRAY_PUSH"; return Opcode3; })(Opcode || {}); var OpcodeBytes = /* @__PURE__ */ ((OpcodeBytes2) => { OpcodeBytes2[OpcodeBytes2["HALT"] = 0] = "HALT"; OpcodeBytes2[OpcodeBytes2["CONST"] = 1] = "CONST"; OpcodeBytes2[OpcodeBytes2["SWAP"] = 2] = "SWAP"; OpcodeBytes2[OpcodeBytes2["EXPORT"] = 3] = "EXPORT"; OpcodeBytes2[OpcodeBytes2["IMPORT"] = 4] = "IMPORT"; OpcodeBytes2[OpcodeBytes2["WAIT"] = 5] = "WAIT"; OpcodeBytes2[OpcodeBytes2["NEW"] = 6] = "NEW"; OpcodeBytes2[OpcodeBytes2["SUPER"] = 7] = "SUPER"; OpcodeBytes2[OpcodeBytes2["ADD"] = 16] = "ADD"; OpcodeBytes2[OpcodeBytes2["SUB"] = 17] = "SUB"; OpcodeBytes2[OpcodeBytes2["MUL"] = 18] = "MUL"; OpcodeBytes2[OpcodeBytes2["DIV"] = 19] = "DIV"; OpcodeBytes2[OpcodeBytes2["MOD"] = 20] = "MOD"; OpcodeBytes2[OpcodeBytes2["EXP"] = 21] = "EXP"; OpcodeBytes2[OpcodeBytes2["NEG"] = 22] = "NEG"; OpcodeBytes2[OpcodeBytes2["EQ"] = 32] = "EQ"; OpcodeBytes2[OpcodeBytes2["NEQ"] = 33] = "NEQ"; OpcodeBytes2[OpcodeBytes2["GT"] = 34] = "GT"; OpcodeBytes2[OpcodeBytes2["GTE"] = 35] = "GTE"; OpcodeBytes2[OpcodeBytes2["LT"] = 36] = "LT"; OpcodeBytes2[OpcodeBytes2["LTE"] = 37] = "LTE"; OpcodeBytes2[OpcodeBytes2["IN"] = 38] = "IN"; OpcodeBytes2[OpcodeBytes2["IS"] = 39] = "IS"; OpcodeBytes2[OpcodeBytes2["NOT"] = 48] = "NOT"; OpcodeBytes2[OpcodeBytes2["JMP"] = 64] = "JMP"; OpcodeBytes2[OpcodeBytes2["JMP_IF_FALSE"] = 65] = "JMP_IF_FALSE"; OpcodeBytes2[OpcodeBytes2["JMP_IF_TRUE"] = 66] = "JMP_IF_TRUE"; OpcodeBytes2[OpcodeBytes2["DUP"] = 67] = "DUP"; OpcodeBytes2[OpcodeBytes2["POP"] = 68] = "POP"; OpcodeBytes2[OpcodeBytes2["LOAD"] = 80] = "LOAD"; OpcodeBytes2[OpcodeBytes2["STORE"] = 81] = "STORE"; OpcodeBytes2[OpcodeBytes2["LOAD_LOCAL"] = 82] = "LOAD_LOCAL"; OpcodeBytes2[OpcodeBytes2["STORE_LOCAL"] = 83] = "STORE_LOCAL"; OpcodeBytes2[OpcodeBytes2["CALL"] = 96] = "CALL"; OpcodeBytes2[OpcodeBytes2["CALL_METHOD"] = 97] = "CALL_METHOD"; OpcodeBytes2[OpcodeBytes2["CALL_PARENT"] = 98] = "CALL_PARENT"; OpcodeBytes2[OpcodeBytes2["RET"] = 99] = "RET"; OpcodeBytes2[OpcodeBytes2["MAKE_ARRAY"] = 112] = "MAKE_ARRAY"; OpcodeBytes2[OpcodeBytes2["MAKE_RANGE"] = 113] = "MAKE_RANGE"; OpcodeBytes2[OpcodeBytes2["MAKE_OBJECT"] = 114] = "MAKE_OBJECT"; OpcodeBytes2[OpcodeBytes2["MAKE_FUNCTION"] = 115] = "MAKE_FUNCTION"; OpcodeBytes2[OpcodeBytes2["MAKE_CLASS"] = 116] = "MAKE_CLASS"; OpcodeBytes2[OpcodeBytes2["MAKE_METHOD"] = 117] = "MAKE_METHOD"; OpcodeBytes2[OpcodeBytes2["GET_PROP"] = 128] = "GET_PROP"; OpcodeBytes2[OpcodeBytes2["SET_PROP"] = 129] = "SET_PROP"; OpcodeBytes2[OpcodeBytes2["ITER_INIT"] = 144] = "ITER_INIT"; OpcodeBytes2[OpcodeBytes2["ITER_NEXT"] = 145] = "ITER_NEXT"; OpcodeBytes2[OpcodeBytes2["ARRAY_PUSH"] = 146] = "ARRAY_PUSH"; return OpcodeBytes2; })(OpcodeBytes || {}); // src/Program/Binary/BinaryReader.ts var BinaryReader = class { constructor(buffer) { __publicField(this, "buffer"); __publicField(this, "view"); __publicField(this, "offset"); __publicField(this, "decoder"); this.buffer = buffer; this.view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); this.offset = 0; this.decoder = new TextDecoder("utf-8"); } readUInt8() { const value = this.view.getUint8(this.offset); this.offset += 1; return value; } readUInt16() { const value = this.view.getUint16(this.offset, true); this.offset += 2; return value; } readUInt32() { const value = this.view.getUint32(this.offset, true); this.offset += 4; return value; } readFloat64() { const value = this.view.getFloat64(this.offset, true); this.offset += 8; return value; } readString() { const length = this.readUInt32(); if (length === 0) { return ""; } const bytes = this.buffer.subarray(this.offset, this.offset + length); this.offset += length; return this.decoder.decode(bytes); } /** * Peek at the current offset without advancing it. */ get position() { return this.offset; } /** * Check if we have reached the end of the buffer. */ get isEof() { return this.offset >= this.buffer.byteLength; } }; // src/Program/ConstantPool.ts var ConstantPool = class _ConstantPool { constructor() { __publicField(this, "valueToId", /* @__PURE__ */ new Map()); __publicField(this, "idToValue", /* @__PURE__ */ new Map()); } static fromProgram(program) { const pool = new _ConstantPool(); for (const instr of program.instructions) { _ConstantPool._add(pool, instr.arg); } for (const funcName of program.exported.functions) { pool.add(funcName); } for (const varName of program.exported.variables) { pool.add(varName); } return pool; } static fromBinary(data) { const pool = new _ConstantPool(); const reader = data instanceof BinaryReader ? data : new BinaryReader(data); const count = reader.readUInt32(); for (let i = 0; i < count; i++) { const type = reader.readUInt8(); if (type === 0) { pool.add(reader.readString(), true); } else if (type === 1) { pool.add(reader.readFloat64(), true); } else { throw new Error(`Corrupt binary: Unknown constant type tag ${type} at offset ${reader.position}`); } } return pool; } static _add(pool, value) { if (value === null || value === void 0) { return; } if (typeof value === "string" || typeof value === "number") { pool.add(value); return; } if (Array.isArray(value)) { for (const item of value) { _ConstantPool._add(pool, item); } return; } if (typeof value === "object") { for (const key of Object.keys(value)) { if (typeof key === "string" || typeof key === "number") { pool.add(key); _ConstantPool._add(pool, value[key]); } } } } /** * Get constant pool ID by its value. * * @param {string | number} value * @returns {number | null} */ getId(value) { return this.valueToId.get(value) ?? null; } /** * Get constant pool value