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 tokens; public Parser(string text, List 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 compileExpr(Expr expr) { throw new NotImplementedException(); } } public interface Value { } public class VM { public Value evaluate(List program) { throw new NotImplementedException(); } }