413 lines
10 KiB
C#
413 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Amogulator.Suslang;
|
|
|
|
public enum TokenType {
|
|
Eof,
|
|
Invalid,
|
|
Id,
|
|
Int,
|
|
Hex,
|
|
Decimal,
|
|
String,
|
|
True,
|
|
False,
|
|
LParen,
|
|
RParen,
|
|
Plus,
|
|
Minus,
|
|
Asterisk,
|
|
Slash,
|
|
Percent,
|
|
Dot,
|
|
}
|
|
|
|
public struct Position {
|
|
public int index;
|
|
public int line, col;
|
|
}
|
|
|
|
public struct Token {
|
|
public TokenType type;
|
|
public Position position;
|
|
public int length;
|
|
}
|
|
|
|
public class Lexer {
|
|
private int i = 0;
|
|
private string text;
|
|
private int line = 1;
|
|
private int col = 1;
|
|
|
|
public Lexer(string text) {
|
|
this.text = text;
|
|
}
|
|
|
|
public Token next() {
|
|
if (done()) {
|
|
return token(TokenType.Eof, position());
|
|
} else if (" \t\r\n".Contains(current())) {
|
|
while (!done() && " \t\r\n".Contains(current()))
|
|
step();
|
|
return next();
|
|
} else if ("123456789".Contains(current())) {
|
|
return intToken();
|
|
} else if ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".Contains(current())) {
|
|
return idToken();
|
|
} else {
|
|
return staticToken();
|
|
}
|
|
}
|
|
|
|
private Token intToken() {
|
|
var start = position();
|
|
step();
|
|
while (!done() && "1234567890".Contains(current()))
|
|
step();
|
|
return token(TokenType.Int, start);
|
|
}
|
|
|
|
private Token idToken() {
|
|
var start = position();
|
|
step();
|
|
while (!done() && "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".Contains(current()))
|
|
step();
|
|
this.text.Substring(start.index, this.i - start.index).switch {
|
|
"false" => token(TokenType.False, start),
|
|
"true" => token(TokenType.True, start),
|
|
_ => token(TokenType.Id, start),
|
|
};
|
|
}
|
|
|
|
private Token staticToken() {
|
|
var start = position();
|
|
switch (current()) {
|
|
case '0':
|
|
break;
|
|
case '.':
|
|
break;
|
|
case '(':
|
|
step();
|
|
return token(TokenType.LParen, start);
|
|
case ')':
|
|
step();
|
|
return token(TokenType.RParen, start);
|
|
case '+':
|
|
step();
|
|
return token(TokenType.Plus, start);
|
|
case '-':
|
|
step();
|
|
return token(TokenType.Minus, start);
|
|
case '*':
|
|
step();
|
|
return token(TokenType.Asterisk, start);
|
|
case '/':
|
|
step();
|
|
return token(TokenType.Slash, start);
|
|
case '%':
|
|
step();
|
|
return token(TokenType.Percent, start);
|
|
default:
|
|
step();
|
|
return token(TokenType.Invalid, start);
|
|
}
|
|
}
|
|
|
|
private Token zeroToken() {
|
|
var start = position();
|
|
step();
|
|
if (!done() && (current() == 'x' || current() == 'X')) {
|
|
while (!done() && "1234567890abcdefABCDEF".Contains(current()))
|
|
step();
|
|
return token(TokenType.Hex, start);
|
|
} else {
|
|
while (!done() && "1234567890".Contains(current()))
|
|
step();
|
|
return token(TokenType.Int, start);
|
|
}
|
|
}
|
|
|
|
private Token dotToken() {
|
|
var start = position();
|
|
step();
|
|
if (!done() && "1234567890".Contains(current())) {
|
|
while (!done() && "1234567890".Contains(current()))
|
|
step();
|
|
return token(TokenType.Decimal, start);
|
|
} else {
|
|
return token(TokenType.Dot, start);
|
|
}
|
|
}
|
|
|
|
private Token token(TokenType type, Position start) => new Token() {
|
|
type = type,
|
|
position = start,
|
|
length = this.i - start.index,
|
|
};
|
|
|
|
private Position position() => new Position() {
|
|
index = this.i,
|
|
line = this.line,
|
|
col = this.col,
|
|
};
|
|
|
|
private char current() => this.text[this.i];
|
|
private bool done() => this.i >= this.text.Length;
|
|
|
|
private void step() {
|
|
this.i += 1;
|
|
}
|
|
}
|
|
|
|
public interface Expr {
|
|
string astString(int depth);
|
|
}
|
|
|
|
public ErrorExpr : Expr {
|
|
public Position position;
|
|
public string message;
|
|
|
|
public override astString(int depth) =>
|
|
$"ErrorExpr {{ {this.position.line}:{this.position.col} \"{this.message}\" }}";
|
|
}
|
|
|
|
public IdExpr : Expr {
|
|
public string value;
|
|
|
|
public override astString(int depth) =>
|
|
$"IdExpr {{ \"{this.value}\" }}";
|
|
}
|
|
|
|
public IntExpr : Expr {
|
|
public int value;
|
|
|
|
public override astString(int depth) =>
|
|
$"IntExpr {{ {this.value} }}";
|
|
}
|
|
|
|
public FloatExpr : Expr {
|
|
public double value;
|
|
|
|
public override astString(int depth) =>
|
|
$"FloatExpr {{ {this.value} }}";
|
|
}
|
|
|
|
public StringExpr : Expr {
|
|
public string value;
|
|
|
|
public override astString(int depth) =>
|
|
$"FloatExpr {{ \"{this.value}\" }}";
|
|
}
|
|
|
|
public BoolExpr : Expr {
|
|
public bool value;
|
|
|
|
public override astString(int depth) =>
|
|
$"BoolExpr {{ {this.value} }}";
|
|
}
|
|
|
|
public enum UnaryType {
|
|
Not,
|
|
Negate,
|
|
}
|
|
|
|
public UnaryExpr : Expr {
|
|
public UnaryType type;
|
|
public Expr value;
|
|
|
|
public override astString(int depth) =>
|
|
$"UnaryExpr {{\n{new String(' ', depth) + 1}type: "
|
|
+ $"{this.type.ToString()},\n{new String(' ', depth + 1)}value: "
|
|
+ $"{this.value.astString(depth + 1)}\n{new String(' ', depth)}}}";
|
|
}
|
|
|
|
public enum BinaryType {
|
|
Add,
|
|
Subtract,
|
|
Multiply,
|
|
Divide,
|
|
Modulus,
|
|
}
|
|
|
|
public BinaryExpr : Expr {
|
|
public BinaryType type;
|
|
public Expr left, right;
|
|
|
|
public override astString(int depth) =>
|
|
$"UnaryExpr {{\n{new String(' ', depth) + 1}type: "
|
|
+ $"{this.type.ToString()},\n{new String(' ', depth + 1)}left: "
|
|
+ $"{this.left.astString(depth + 1)},\n{new String(' ', depth + 1)}right: "
|
|
+ $"{this.right.astString(depth + 1)}\n{new String(' ', depth)}}}";
|
|
}
|
|
|
|
public class Parser {
|
|
private int i = 0;
|
|
private string text;
|
|
private List<Token> tokens;
|
|
|
|
public Parser(string text, List<Token> tokens) {
|
|
this.text = text;
|
|
this.tokens = tokens;
|
|
}
|
|
|
|
public Expr parseExpr() {
|
|
return parsePrec11();
|
|
}
|
|
|
|
public Expr parsePrec11() {
|
|
var left = parsePrec12();
|
|
if (!done() && current().type == TokenType.Plus) {
|
|
step();
|
|
var right = parsePrec11();
|
|
return new BinaryExpr() {
|
|
type = BinaryType.Add,
|
|
left = left,
|
|
right = right,
|
|
};
|
|
} else if (!done() && current().type == TokenType.Minus) {
|
|
step();
|
|
var right = parsePrec11();
|
|
return new BinaryExpr() {
|
|
type = BinaryType.Subtraction,
|
|
left = left,
|
|
right = right,
|
|
};
|
|
} else {
|
|
return left;
|
|
}
|
|
}
|
|
|
|
public Expr parsePrec12() {
|
|
var left = parsePrec13();
|
|
if (!done() && current().type == TokenType.Asterisk) {
|
|
step();
|
|
var right = parePrec12();
|
|
return new BinaryExpr() {
|
|
type = BinaryType.Multiply,
|
|
left = left,
|
|
right = right,
|
|
};
|
|
} else if (!done() && current().type == TokenType.Slash) {
|
|
step();
|
|
var right = parePrec12();
|
|
return new BinaryExpr() {
|
|
type = BinaryType.Divide,
|
|
left = left,
|
|
right = right,
|
|
};
|
|
} else if (!done() && current().type == TokenType.Percent) {
|
|
step();
|
|
var right = parePrec12();
|
|
return new BinaryExpr() {
|
|
type = BinaryType.Modulus,
|
|
left = left,
|
|
right = right,
|
|
};
|
|
} else {
|
|
return left;
|
|
}
|
|
}
|
|
|
|
public Expr parsePrec13() {
|
|
return parsePrec18();
|
|
}
|
|
|
|
public Expr parsePrec18() {
|
|
if (!done() && current().type == TokenType.LParen) {
|
|
step();
|
|
var expr = parseExpr();
|
|
if (done() || current().type TokenType.RParen) {
|
|
var pos = position();
|
|
step();
|
|
return new ErrorExpr() {
|
|
position = pos,
|
|
message = "expected ')'"
|
|
};
|
|
}
|
|
step();
|
|
return expr;
|
|
} else {
|
|
return parseOperand();
|
|
}
|
|
}
|
|
|
|
public parseOperand() {
|
|
var start = position();
|
|
if (done()) {
|
|
step();
|
|
return new ErrorExpr() {
|
|
position = start,
|
|
message = "expected value, got eof",
|
|
};
|
|
} else if (current().type == TokenType.Int) {
|
|
var intToken = current();
|
|
step();
|
|
if (!done() && current().type == TokenType.Decimal) {
|
|
var decimalToken = current();
|
|
step();
|
|
return new FloatExpr() {
|
|
value = double.Parse(this.text.Substring(start.index, intToken.length + decimalToken.length)),
|
|
};
|
|
} else {
|
|
return new IntExpr() {
|
|
value = int.Parse(this.text.Substring(start.index, intToken.length));
|
|
};
|
|
}
|
|
} else if (current().type == TokenType.String) {
|
|
var stringToken = current();
|
|
step();
|
|
return new StringExpr() {
|
|
value = this.text.Substring(start.index, stringToken.length),
|
|
};
|
|
} else if (current().type == TokenType.False) {
|
|
step();
|
|
return new BoolExpr() {
|
|
value = false,
|
|
};
|
|
} else if (current().type == TokenType.True) {
|
|
step();
|
|
return new BoolExpr() {
|
|
value = true,
|
|
};
|
|
} else {
|
|
step();
|
|
return new ErrorExpr() {
|
|
position = start,
|
|
message = "expected value",
|
|
};
|
|
}
|
|
}
|
|
|
|
private char current() => this.tokens[this.i];
|
|
private bool done() => this.i >= this.text.Length;
|
|
|
|
private void step() {
|
|
this.i += 1;
|
|
}
|
|
}
|
|
|
|
public interface Operation {}
|
|
|
|
public class PushInt {
|
|
public int value;
|
|
}
|
|
|
|
public class PushFloat {
|
|
public double value;
|
|
}
|
|
|
|
public class Compiler {
|
|
public List<Operation> compileExpr(Expr expr) {
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public interface Value { }
|
|
|
|
public class VM {
|
|
public Value evaluate(List<Operation> program) {
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|