mini-lang / mini_lang.js
Trae Assistant
feat: complete hf spaces configuration with interactive demo
602c51e
const TokenType = {
NUMBER: "NUMBER",
IDENT: "IDENT",
LET: "LET",
PRINT: "PRINT",
EQUAL: "EQUAL",
PLUS: "PLUS",
MINUS: "MINUS",
STAR: "STAR",
SLASH: "SLASH",
SEMICOLON: "SEMICOLON",
LPAREN: "LPAREN",
RPAREN: "RPAREN",
EOF: "EOF",
};
const keywords = {
let: TokenType.LET,
print: TokenType.PRINT,
};
class Token {
constructor(type, lexeme, literal, position) {
this.type = type;
this.lexeme = lexeme;
this.literal = literal;
this.position = position;
}
}
class Lexer {
constructor(source) {
this.source = source;
this.tokens = [];
this.start = 0;
this.current = 0;
this.line = 1;
this.column = 1;
}
scanTokens() {
while (!this.isAtEnd()) {
this.start = this.current;
this.scanToken();
}
this.tokens.push(new Token(TokenType.EOF, "", null, { line: this.line, column: this.column }));
return this.tokens;
}
isAtEnd() {
return this.current >= this.source.length;
}
scanToken() {
const c = this.advance();
if (this.isDigit(c)) {
this.number();
return;
}
if (this.isAlpha(c)) {
this.identifier();
return;
}
switch (c) {
case "+":
this.addToken(TokenType.PLUS);
break;
case "-":
this.addToken(TokenType.MINUS);
break;
case "*":
this.addToken(TokenType.STAR);
break;
case "/":
if (this.match("/")) {
while (!this.isAtEnd() && this.peek() !== "\n") {
this.advance();
}
} else {
this.addToken(TokenType.SLASH);
}
break;
case "=":
this.addToken(TokenType.EQUAL);
break;
case ";":
this.addToken(TokenType.SEMICOLON);
break;
case "(":
this.addToken(TokenType.LPAREN);
break;
case ")":
this.addToken(TokenType.RPAREN);
break;
case " ":
case "\r":
case "\t":
break;
case "\n":
this.line += 1;
this.column = 1;
break;
default:
throw new Error("无法识别的字符: '" + c + "' 在第 " + this.line + " 行");
}
}
advance() {
const ch = this.source[this.current];
this.current += 1;
this.column += 1;
return ch;
}
addToken(type, literal = null) {
const text = this.source.slice(this.start, this.current);
this.tokens.push(new Token(type, text, literal, { line: this.line, column: this.column }));
}
match(expected) {
if (this.isAtEnd()) return false;
if (this.source[this.current] !== expected) return false;
this.current += 1;
this.column += 1;
return true;
}
peek() {
if (this.isAtEnd()) return "\0";
return this.source[this.current];
}
isDigit(c) {
return c >= "0" && c <= "9";
}
isAlpha(c) {
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_";
}
isAlphaNumeric(c) {
return this.isAlpha(c) || this.isDigit(c);
}
number() {
while (this.isDigit(this.peek())) {
this.advance();
}
if (this.peek() === "." && this.isDigit(this.peekNext())) {
this.advance();
while (this.isDigit(this.peek())) {
this.advance();
}
}
const text = this.source.slice(this.start, this.current);
const value = Number(text);
this.addToken(TokenType.NUMBER, value);
}
peekNext() {
if (this.current + 1 >= this.source.length) return "\0";
return this.source[this.current + 1];
}
identifier() {
while (this.isAlphaNumeric(this.peek())) {
this.advance();
}
const text = this.source.slice(this.start, this.current);
const type = keywords[text] || TokenType.IDENT;
this.addToken(type);
}
}
class Parser {
constructor(tokens) {
this.tokens = tokens;
this.current = 0;
}
parseProgram() {
const statements = [];
while (!this.isAtEnd()) {
statements.push(this.statement());
}
return { type: "Program", statements };
}
statement() {
if (this.match(TokenType.LET)) return this.letStatement();
if (this.match(TokenType.PRINT)) return this.printStatement();
const expr = this.expression();
this.consume(TokenType.SEMICOLON, "期望在表达式后面出现分号");
return { type: "ExprStmt", expression: expr };
}
letStatement() {
const nameToken = this.consume(TokenType.IDENT, "let 后面需要变量名");
this.consume(TokenType.EQUAL, "变量声明需要等号");
const initializer = this.expression();
this.consume(TokenType.SEMICOLON, "变量声明后需要分号");
return { type: "LetStmt", name: nameToken.lexeme, initializer };
}
printStatement() {
const value = this.expression();
this.consume(TokenType.SEMICOLON, "print 后需要分号");
return { type: "PrintStmt", expression: value };
}
expression() {
return this.term();
}
term() {
let expr = this.factor();
while (this.match(TokenType.PLUS) || this.match(TokenType.MINUS)) {
const operator = this.previous();
const right = this.factor();
expr = { type: "Binary", left: expr, operator: operator.type, right };
}
return expr;
}
factor() {
let expr = this.unary();
while (this.match(TokenType.STAR) || this.match(TokenType.SLASH)) {
const operator = this.previous();
const right = this.unary();
expr = { type: "Binary", left: expr, operator: operator.type, right };
}
return expr;
}
unary() {
if (this.match(TokenType.MINUS)) {
const operator = this.previous();
const right = this.unary();
return { type: "Unary", operator: operator.type, right };
}
return this.primary();
}
primary() {
if (this.match(TokenType.NUMBER)) {
return { type: "Literal", value: this.previous().literal };
}
if (this.match(TokenType.IDENT)) {
return { type: "Variable", name: this.previous().lexeme };
}
if (this.match(TokenType.LPAREN)) {
const expr = this.expression();
this.consume(TokenType.RPAREN, "缺少右括号");
return expr;
}
throw new Error("无法解析的表达式起始符号");
}
match(type) {
if (this.check(type)) {
this.advance();
return true;
}
return false;
}
consume(type, message) {
if (this.check(type)) return this.advance();
throw new Error(message);
}
check(type) {
if (this.isAtEnd()) return false;
return this.peek().type === type;
}
advance() {
if (!this.isAtEnd()) this.current += 1;
return this.previous();
}
isAtEnd() {
return this.peek().type === TokenType.EOF;
}
peek() {
return this.tokens[this.current];
}
previous() {
return this.tokens[this.current - 1];
}
}
class Interpreter {
constructor() {
this.globals = Object.create(null);
}
execute(program, outputCallback = console.log) {
for (const stmt of program.statements) {
this.executeStatement(stmt, outputCallback);
}
}
executeStatement(stmt, outputCallback) {
switch (stmt.type) {
case "LetStmt":
this.globals[stmt.name] = this.evaluate(stmt.initializer);
break;
case "PrintStmt":
const value = this.evaluate(stmt.expression);
outputCallback(value);
break;
case "ExprStmt":
this.evaluate(stmt.expression);
break;
default:
throw new Error("未知语句类型: " + stmt.type);
}
}
evaluate(expr) {
switch (expr.type) {
case "Literal":
return expr.value;
case "Variable":
if (Object.prototype.hasOwnProperty.call(this.globals, expr.name)) {
return this.globals[expr.name];
}
throw new Error("未定义的变量: " + expr.name);
case "Unary":
const right = this.evaluate(expr.right);
if (expr.operator === TokenType.MINUS) {
this.ensureNumber(right);
return -right;
}
throw new Error("未知一元运算符");
case "Binary":
const left = this.evaluate(expr.left);
const r = this.evaluate(expr.right);
if (expr.operator === TokenType.PLUS) {
this.ensureNumber(left);
this.ensureNumber(r);
return left + r;
}
if (expr.operator === TokenType.MINUS) {
this.ensureNumber(left);
this.ensureNumber(r);
return left - r;
}
if (expr.operator === TokenType.STAR) {
this.ensureNumber(left);
this.ensureNumber(r);
return left * r;
}
if (expr.operator === TokenType.SLASH) {
this.ensureNumber(left);
this.ensureNumber(r);
return left / r;
}
throw new Error("未知二元运算符");
default:
throw new Error("未知表达式类型: " + expr.type);
}
}
ensureNumber(value) {
if (typeof value !== "number" || Number.isNaN(value)) {
throw new Error("期望数字类型");
}
}
}
function run(source, outputCallback = console.log) {
const lexer = new Lexer(source);
const tokens = lexer.scanTokens();
const parser = new Parser(tokens);
const program = parser.parseProgram();
const interpreter = new Interpreter();
interpreter.execute(program, outputCallback);
}
if (typeof module !== "undefined" && module.exports) {
module.exports = { run };
} else {
// 浏览器环境
window.MiniLang = { run };
}