luma-lang
Version:
The Embeddable Luma Language Compiler and Runtime
1,556 lines (1,544 loc) • 170 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Compiler: () => Compiler,
Debugger: () => Debugger,
Keywords: () => Keywords,
LumaError: () => LumaError,
LumaRuntimeError: () => LumaRuntimeError,
Opcode: () => Opcode,
OpcodeBytes: () => OpcodeBytes,
Operators: () => Operators,
Parser: () => Parser,
Punctuation: () => Punctuation,
Reader: () => Reader,
TokenStream: () => TokenStream,
TokenType: () => TokenType,
Tokenizer: () => Tokenizer,
VirtualMachine: () => VirtualMachine,
Writer: () => Writer
});
module.exports = __toCommonJS(index_exports);
// 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/P