suse
This commit is contained in:
parent
181250cd01
commit
3032623f70
@ -7,10 +7,6 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Suslang/"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
|
||||
</ItemGroup>
|
||||
|
@ -1,6 +1,8 @@
|
||||
using Amogulator.Suslang;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Amogulator;
|
||||
public partial class AmogulatorVM : ObservableObject {
|
||||
@ -10,13 +12,17 @@ public partial class AmogulatorVM : ObservableObject {
|
||||
|
||||
[RelayCommand]
|
||||
private void run() {
|
||||
var ast = new Parser(this.Input, new Lexer(this.Input)).parseExpr();
|
||||
var compiler = new Compiler();
|
||||
compiler.compileExpr(ast);
|
||||
var bytecode = compiler.result();
|
||||
var vm = new VM(bytecode);
|
||||
vm.evaluate();
|
||||
this.Output = ast.astString(0) + "\n\nresult = " + ((IntValue) vm.stack.Peek()).value;
|
||||
try {
|
||||
var ast = new Parser(this.Input, new Lexer(this.Input)).parseExpr();
|
||||
var compiler = new Compiler();
|
||||
compiler.compileExpr(ast);
|
||||
var bytecode = compiler.result();
|
||||
var vm = new VM(bytecode);
|
||||
vm.evaluate();
|
||||
this.Output = $"ast = {ast.astString(0)}\n\nbytecode = [\n{string.Join("", bytecode.Select((op) => " " + op.operationString() + "\n"))}]\n\nresult = {vm.stack.Peek().valueString()}";
|
||||
} catch (Exception e) {
|
||||
this.Output = $"error = {e.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,6 +39,8 @@
|
||||
FontFamily="Consolas monospace"
|
||||
Background="Black"
|
||||
Foreground="White"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True"
|
||||
/>
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
|
@ -1,17 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows;
|
||||
|
||||
namespace Amogulator;
|
||||
/// <summary>
|
||||
@ -21,8 +8,4 @@ public partial class MainWindow : Window {
|
||||
public MainWindow() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Label_Scroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
85
Suslang/AST.cs
Normal file
85
Suslang/AST.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
|
||||
namespace Amogulator.Suslang;
|
||||
|
||||
public interface Expr {
|
||||
string astString(int depth);
|
||||
}
|
||||
|
||||
public class ErrorExpr : Expr {
|
||||
public Position position;
|
||||
public string message;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"ErrorExpr {{ {this.position.line}:{this.position.col} \"{this.message}\" }}";
|
||||
}
|
||||
|
||||
public class IdExpr : Expr {
|
||||
public string value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"IdExpr {{ \"{this.value}\" }}";
|
||||
}
|
||||
|
||||
public class IntExpr : Expr {
|
||||
public int value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"IntExpr {{ {this.value} }}";
|
||||
}
|
||||
|
||||
public class FloatExpr : Expr {
|
||||
public double value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"FloatExpr {{ {this.value} }}";
|
||||
}
|
||||
|
||||
public class StringExpr : Expr {
|
||||
public string value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"FloatExpr {{ \"{this.value}\" }}";
|
||||
}
|
||||
|
||||
public class BoolExpr : Expr {
|
||||
public bool value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"BoolExpr {{ {this.value} }}";
|
||||
}
|
||||
|
||||
public enum UnaryType {
|
||||
Not,
|
||||
Negate,
|
||||
}
|
||||
|
||||
public class UnaryExpr : Expr {
|
||||
public UnaryType type;
|
||||
public Expr value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"UnaryExpr {{\n{new string(' ', (depth + 1) * 4)}type: "
|
||||
+ $"{this.type},\n{new string(' ', (depth + 1) * 4)}value: "
|
||||
+ $"{this.value.astString(depth + 1)}\n{new String(' ', depth * 4)}}}";
|
||||
}
|
||||
|
||||
public enum BinaryType {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
}
|
||||
|
||||
public class BinaryExpr : Expr {
|
||||
public BinaryType type;
|
||||
public Expr left, right;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"BinaryExpr {{\n{new String(' ', (depth + 1) * 4)}type: "
|
||||
+ $"{this.type.ToString()},\n{new String(' ', (depth + 1) * 4)}left: "
|
||||
+ $"{this.left.astString(depth + 1)},\n{new String(' ', (depth + 1) * 4)}right: "
|
||||
+ $"{this.right.astString(depth + 1)},\n{new String(' ', depth * 4)}}}";
|
||||
}
|
||||
|
41
Suslang/Bytecode.cs
Normal file
41
Suslang/Bytecode.cs
Normal file
@ -0,0 +1,41 @@
|
||||
namespace Amogulator.Suslang;
|
||||
|
||||
public interface Operation {
|
||||
string operationString();
|
||||
}
|
||||
|
||||
public class PushIntOperation : Operation {
|
||||
public int value;
|
||||
|
||||
public string operationString() => $"PushIntOperation {{ {value} }}";
|
||||
}
|
||||
|
||||
public class PushFloatOperation : Operation {
|
||||
public double value;
|
||||
|
||||
public string operationString() => $"PushFloatOperation {{ {value} }}";
|
||||
}
|
||||
|
||||
public class PushStringOperation : Operation {
|
||||
public string value;
|
||||
|
||||
public string operationString() => $"PushStringOperation {{ \"{value}\" }}";
|
||||
}
|
||||
|
||||
public class PushBoolOperation : Operation {
|
||||
public bool value;
|
||||
|
||||
public string operationString() => $"PushBoolOperation {{ {value} }}";
|
||||
}
|
||||
|
||||
public class AddIntOperation : Operation { public string operationString() => $"AddIntOperation"; }
|
||||
public class SubtractIntOperation : Operation { public string operationString() => $"SubtractIntOperation"; }
|
||||
public class MultiplyIntOperation : Operation { public string operationString() => $"MultiplyIntOperation"; }
|
||||
public class DivideIntOperation : Operation { public string operationString() => $"DivideIntOperation"; }
|
||||
public class ModuloIntOperation : Operation { public string operationString() => $"ModuloIntOperation"; }
|
||||
public class AddFloatOperation : Operation { public string operationString() => $"AddFloatOperation"; }
|
||||
public class SubtractFloatOperation : Operation { public string operationString() => $"SubtractFloatOperation"; }
|
||||
public class MultiplyFloatOperation : Operation { public string operationString() => $"MultiplyFloatOperation"; }
|
||||
public class DivideFloatOperation : Operation { public string operationString() => $"DivideFloatOperation"; }
|
||||
public class ModuloFloatOperation : Operation { public string operationString() => $"ModuloFloatOperation"; }
|
||||
|
72
Suslang/Compiler.cs
Normal file
72
Suslang/Compiler.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Amogulator.Suslang;
|
||||
|
||||
|
||||
public class Compiler {
|
||||
private List<Operation> operations = new();
|
||||
|
||||
public List<Operation> result() => this.operations;
|
||||
|
||||
public string compileExpr(Expr expr) =>
|
||||
expr switch {
|
||||
IntExpr => compileIntExpr((IntExpr) expr),
|
||||
FloatExpr => compileFloatExpr((FloatExpr) expr),
|
||||
StringExpr => compileStringExpr((StringExpr) expr),
|
||||
BoolExpr => compileBoolExpr((BoolExpr) expr),
|
||||
UnaryExpr => compileUnaryExpr((UnaryExpr) expr),
|
||||
BinaryExpr => compileBinaryExpr((BinaryExpr) expr),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
public string compileIntExpr(IntExpr expr) {
|
||||
this.operations.Add(new PushIntOperation() { value = expr.value });
|
||||
return "int";
|
||||
}
|
||||
|
||||
public string compileFloatExpr(FloatExpr expr) {
|
||||
this.operations.Add(new PushFloatOperation() { value = expr.value });
|
||||
return "float";
|
||||
}
|
||||
|
||||
public string compileStringExpr(StringExpr expr) {
|
||||
this.operations.Add(new PushStringOperation() { value = expr.value });
|
||||
return "string";
|
||||
}
|
||||
|
||||
public string compileBoolExpr(BoolExpr expr) {
|
||||
this.operations.Add(new PushBoolOperation() { value = expr.value });
|
||||
return "bool";
|
||||
}
|
||||
|
||||
public string compileUnaryExpr(UnaryExpr expr) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string compileBinaryExpr(BinaryExpr expr) {
|
||||
var leftType = compileExpr(expr.left);
|
||||
var rightType = compileExpr(expr.right);
|
||||
if ((leftType, rightType) == ("int", "int")) {
|
||||
this.operations.Add(expr.type switch {
|
||||
BinaryType.Add => new AddIntOperation(),
|
||||
BinaryType.Subtract => new SubtractIntOperation(),
|
||||
BinaryType.Multiply => new MultiplyIntOperation(),
|
||||
BinaryType.Divide => new DivideIntOperation(),
|
||||
BinaryType.Modulo => new ModuloIntOperation(),
|
||||
});
|
||||
return "int";
|
||||
} else if ((leftType, rightType) == ("float", "float")) {
|
||||
this.operations.Add(expr.type switch {
|
||||
BinaryType.Add => new AddFloatOperation(),
|
||||
BinaryType.Subtract => new SubtractFloatOperation(),
|
||||
BinaryType.Multiply => new MultiplyFloatOperation(),
|
||||
BinaryType.Divide => new DivideFloatOperation(),
|
||||
BinaryType.Modulo => new ModuloFloatOperation(),
|
||||
});
|
||||
return "float";
|
||||
} else {
|
||||
throw new Exception("type mismatch");
|
||||
}
|
||||
}
|
||||
}
|
135
Suslang/Lexer.cs
Normal file
135
Suslang/Lexer.cs
Normal file
@ -0,0 +1,135 @@
|
||||
namespace Amogulator.Suslang;
|
||||
|
||||
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();
|
||||
return 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':
|
||||
return zeroToken();
|
||||
case '.':
|
||||
return dotToken();
|
||||
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;
|
||||
if (!done()) {
|
||||
if (current() == '\n') {
|
||||
this.line += 1;
|
||||
this.col = 1;
|
||||
} else {
|
||||
this.col += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
Suslang/Parser.cs
Normal file
155
Suslang/Parser.cs
Normal file
@ -0,0 +1,155 @@
|
||||
namespace Amogulator.Suslang;
|
||||
|
||||
public class Parser {
|
||||
private string text;
|
||||
private Lexer lexer;
|
||||
private Token currentToken;
|
||||
|
||||
public Parser(string text, Lexer lexer) {
|
||||
this.text = text;
|
||||
this.lexer = lexer;
|
||||
this.currentToken = this.lexer.next();
|
||||
}
|
||||
|
||||
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.Subtract,
|
||||
left = left,
|
||||
right = right,
|
||||
};
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
|
||||
public Expr parsePrec12() {
|
||||
var left = parsePrec13();
|
||||
if (!done() && current().type == TokenType.Asterisk) {
|
||||
step();
|
||||
var right = parsePrec12();
|
||||
return new BinaryExpr() {
|
||||
type = BinaryType.Multiply,
|
||||
left = left,
|
||||
right = right,
|
||||
};
|
||||
} else if (!done() && current().type == TokenType.Slash) {
|
||||
step();
|
||||
var right = parsePrec12();
|
||||
return new BinaryExpr() {
|
||||
type = BinaryType.Divide,
|
||||
left = left,
|
||||
right = right,
|
||||
};
|
||||
} else if (!done() && current().type == TokenType.Percent) {
|
||||
step();
|
||||
var right = parsePrec12();
|
||||
return new BinaryExpr() {
|
||||
type = BinaryType.Modulo,
|
||||
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 Expr 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).Replace(".", ",")),
|
||||
};
|
||||
} else {
|
||||
return new IntExpr() {
|
||||
value = int.Parse(this.text.Substring(start.index, intToken.length)),
|
||||
};
|
||||
}
|
||||
} else if (current().type == TokenType.Decimal) {
|
||||
var decimalToken = current();
|
||||
step();
|
||||
return new FloatExpr() {
|
||||
value = double.Parse("0" + this.text.Substring(start.index, decimalToken.length).Replace(".", ",")),
|
||||
};
|
||||
} 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 Position position() => current().position;
|
||||
private Token current() => this.currentToken;
|
||||
private bool done() => this.currentToken.type == TokenType.Eof;
|
||||
|
||||
private void step() {
|
||||
this.currentToken = this.lexer.next();
|
||||
}
|
||||
}
|
@ -1,575 +0,0 @@
|
||||
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();
|
||||
return 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':
|
||||
return zeroToken();
|
||||
case '.':
|
||||
return dotToken();
|
||||
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;
|
||||
if (!done()) {
|
||||
if (current() == '\n') {
|
||||
this.line += 1;
|
||||
this.col = 1;
|
||||
} else {
|
||||
this.col += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Expr {
|
||||
string astString(int depth);
|
||||
}
|
||||
|
||||
public class ErrorExpr : Expr {
|
||||
public Position position;
|
||||
public string message;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"ErrorExpr {{ {this.position.line}:{this.position.col} \"{this.message}\" }}";
|
||||
}
|
||||
|
||||
public class IdExpr : Expr {
|
||||
public string value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"IdExpr {{ \"{this.value}\" }}";
|
||||
}
|
||||
|
||||
public class IntExpr : Expr {
|
||||
public int value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"IntExpr {{ {this.value} }}";
|
||||
}
|
||||
|
||||
public class FloatExpr : Expr {
|
||||
public double value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"FloatExpr {{ {this.value} }}";
|
||||
}
|
||||
|
||||
public class StringExpr : Expr {
|
||||
public string value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"FloatExpr {{ \"{this.value}\" }}";
|
||||
}
|
||||
|
||||
public class BoolExpr : Expr {
|
||||
public bool value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"BoolExpr {{ {this.value} }}";
|
||||
}
|
||||
|
||||
public enum UnaryType {
|
||||
Not,
|
||||
Negate,
|
||||
}
|
||||
|
||||
public class UnaryExpr : Expr {
|
||||
public UnaryType type;
|
||||
public Expr value;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"UnaryExpr {{\n{new string(' ', depth + 1)}type: "
|
||||
+ $"{this.type},\n{new string(' ', depth + 1)}value: "
|
||||
+ $"{this.value.astString(depth + 1)}\n{new String(' ', depth)}}}";
|
||||
}
|
||||
|
||||
public enum BinaryType {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
}
|
||||
|
||||
public class BinaryExpr : Expr {
|
||||
public BinaryType type;
|
||||
public Expr left, right;
|
||||
|
||||
public string astString(int depth) =>
|
||||
$"BinaryExpr {{\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 string text;
|
||||
private Lexer lexer;
|
||||
private Token currentToken;
|
||||
|
||||
public Parser(string text, Lexer lexer) {
|
||||
this.text = text;
|
||||
this.lexer = lexer;
|
||||
this.currentToken = this.lexer.next();
|
||||
}
|
||||
|
||||
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.Subtract,
|
||||
left = left,
|
||||
right = right,
|
||||
};
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
|
||||
public Expr parsePrec12() {
|
||||
var left = parsePrec13();
|
||||
if (!done() && current().type == TokenType.Asterisk) {
|
||||
step();
|
||||
var right = parsePrec12();
|
||||
return new BinaryExpr() {
|
||||
type = BinaryType.Multiply,
|
||||
left = left,
|
||||
right = right,
|
||||
};
|
||||
} else if (!done() && current().type == TokenType.Slash) {
|
||||
step();
|
||||
var right = parsePrec12();
|
||||
return new BinaryExpr() {
|
||||
type = BinaryType.Divide,
|
||||
left = left,
|
||||
right = right,
|
||||
};
|
||||
} else if (!done() && current().type == TokenType.Percent) {
|
||||
step();
|
||||
var right = parsePrec12();
|
||||
return new BinaryExpr() {
|
||||
type = BinaryType.Modulo,
|
||||
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 Expr 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).Replace(".", ",")),
|
||||
};
|
||||
} else {
|
||||
return new IntExpr() {
|
||||
value = int.Parse(this.text.Substring(start.index, intToken.length)),
|
||||
};
|
||||
}
|
||||
} else if (current().type == TokenType.Decimal) {
|
||||
var decimalToken = current();
|
||||
step();
|
||||
return new FloatExpr() {
|
||||
value = double.Parse("0" + this.text.Substring(start.index, decimalToken.length).Replace(".", ",")),
|
||||
};
|
||||
} 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 Position position() => current().position;
|
||||
private Token current() => this.currentToken;
|
||||
private bool done() => this.currentToken.type == TokenType.Eof;
|
||||
|
||||
private void step() {
|
||||
this.currentToken = this.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
public interface Operation { }
|
||||
|
||||
public class PushIntOperation : Operation {
|
||||
public int value;
|
||||
}
|
||||
|
||||
public class PushFloatOperation : Operation {
|
||||
public double value;
|
||||
}
|
||||
|
||||
public class PushStringOperation : Operation {
|
||||
public string value;
|
||||
}
|
||||
|
||||
public class PushBoolOperation : Operation {
|
||||
public bool value;
|
||||
}
|
||||
|
||||
public class AddIntOperation : Operation { }
|
||||
public class SubtractIntOperation : Operation { }
|
||||
public class MultiplyIntOperation : Operation { }
|
||||
public class DivideIntOperation : Operation { }
|
||||
public class ModuloIntOperation : Operation { }
|
||||
public class AddFloatOperation : Operation { }
|
||||
public class SubtractFloatOperation : Operation { }
|
||||
public class MultiplyFloatOperation : Operation { }
|
||||
public class DivideFloatOperation : Operation { }
|
||||
public class ModuloFloatOperation : Operation { }
|
||||
|
||||
public class Compiler {
|
||||
private List<Operation> operations = new();
|
||||
|
||||
public List<Operation> result() => this.operations;
|
||||
|
||||
public string compileExpr(Expr expr) {
|
||||
switch (expr) {
|
||||
case IntExpr:
|
||||
return compileIntExpr((IntExpr) expr);
|
||||
break;
|
||||
case FloatExpr:
|
||||
return compileFloatExpr((FloatExpr) expr);
|
||||
break;
|
||||
case StringExpr:
|
||||
return compileStringExpr((StringExpr) expr);
|
||||
break;
|
||||
case BoolExpr:
|
||||
return compileBoolExpr((BoolExpr) expr);
|
||||
break;
|
||||
case UnaryExpr:
|
||||
return compileUnaryExpr((UnaryExpr) expr);
|
||||
break;
|
||||
case BinaryExpr:
|
||||
return compileBinaryExpr((BinaryExpr) expr);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
};
|
||||
}
|
||||
|
||||
public string compileIntExpr(IntExpr expr) {
|
||||
this.operations.Add(new PushIntOperation() { value = expr.value });
|
||||
return "int";
|
||||
}
|
||||
|
||||
public string compileFloatExpr(FloatExpr expr) {
|
||||
this.operations.Add(new PushFloatOperation() { value = expr.value });
|
||||
return "float";
|
||||
}
|
||||
|
||||
public string compileStringExpr(StringExpr expr) {
|
||||
this.operations.Add(new PushStringOperation() { value = expr.value });
|
||||
return "string";
|
||||
}
|
||||
|
||||
public string compileBoolExpr(BoolExpr expr) {
|
||||
this.operations.Add(new PushBoolOperation() { value = expr.value });
|
||||
return "bool";
|
||||
}
|
||||
|
||||
public string compileUnaryExpr(UnaryExpr expr) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string compileBinaryExpr(BinaryExpr expr) {
|
||||
var leftType = compileExpr(expr.left);
|
||||
var rightType = compileExpr(expr.right);
|
||||
if ((leftType, rightType) == ("int", "int")) {
|
||||
this.operations.Add(expr.type switch {
|
||||
BinaryType.Add => new AddIntOperation(),
|
||||
BinaryType.Subtract => new SubtractIntOperation(),
|
||||
BinaryType.Multiply => new MultiplyIntOperation(),
|
||||
BinaryType.Divide => new DivideIntOperation(),
|
||||
BinaryType.Modulo => new ModuloIntOperation(),
|
||||
});
|
||||
return "int";
|
||||
} else if ((leftType, rightType) == ("int", "int")) {
|
||||
this.operations.Add(expr.type switch {
|
||||
BinaryType.Add => new AddFloatOperation(),
|
||||
BinaryType.Subtract => new SubtractFloatOperation(),
|
||||
BinaryType.Multiply => new MultiplyFloatOperation(),
|
||||
BinaryType.Divide => new DivideFloatOperation(),
|
||||
BinaryType.Modulo => new ModuloFloatOperation(),
|
||||
});
|
||||
return "float";
|
||||
} else {
|
||||
throw new Exception("type mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Value { }
|
||||
|
||||
public class IntValue : Value { public int value; }
|
||||
public class FloatValue : Value { public double value; }
|
||||
|
||||
public class VM {
|
||||
private int pc = 0;
|
||||
private List<Operation> program;
|
||||
public Stack<Value> stack = new();
|
||||
|
||||
public VM(List<Operation> program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
public void evaluate() {
|
||||
while (this.pc < this.program.Count) {
|
||||
switch (this.program[this.pc]) {
|
||||
case PushIntOperation op:
|
||||
this.stack.Push(new IntValue() { value = op.value });
|
||||
break;
|
||||
case PushFloatOperation op:
|
||||
this.stack.Push(new FloatValue() { value = op.value });
|
||||
break;
|
||||
case AddIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value + ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case SubtractIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value - ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case MultiplyIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value * ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case DivideIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value / ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case ModuloIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value % ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case AddFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value + ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case SubtractFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value - ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case MultiplyFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value * ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case DivideFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value / ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case ModuloFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value % ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.pc += 1;
|
||||
}
|
||||
}
|
||||
}
|
32
Suslang/Token.cs
Normal file
32
Suslang/Token.cs
Normal file
@ -0,0 +1,32 @@
|
||||
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;
|
||||
}
|
75
Suslang/VM.cs
Normal file
75
Suslang/VM.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Amogulator.Suslang;
|
||||
|
||||
public interface Value {
|
||||
string valueString();
|
||||
}
|
||||
|
||||
public class IntValue : Value {
|
||||
public int value;
|
||||
|
||||
public string valueString() => $"{this.value}";
|
||||
}
|
||||
|
||||
public class FloatValue : Value {
|
||||
public double value;
|
||||
|
||||
public string valueString() => $"{this.value}";
|
||||
}
|
||||
|
||||
public class VM {
|
||||
private int pc = 0;
|
||||
private List<Operation> program;
|
||||
public Stack<Value> stack = new();
|
||||
|
||||
public VM(List<Operation> program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
public void evaluate() {
|
||||
while (this.pc < this.program.Count) {
|
||||
switch (this.program[this.pc]) {
|
||||
case PushIntOperation op:
|
||||
this.stack.Push(new IntValue() { value = op.value });
|
||||
break;
|
||||
case PushFloatOperation op:
|
||||
this.stack.Push(new FloatValue() { value = op.value });
|
||||
break;
|
||||
case AddIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value + ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case SubtractIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value - ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case MultiplyIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value * ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case DivideIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value / ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case ModuloIntOperation:
|
||||
this.stack.Push(new IntValue() { value = ((IntValue) this.stack.Pop()).value % ((IntValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case AddFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value + ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case SubtractFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value - ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case MultiplyFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value * ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case DivideFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value / ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
case ModuloFloatOperation:
|
||||
this.stack.Push(new FloatValue() { value = ((FloatValue) this.stack.Pop()).value % ((FloatValue) this.stack.Pop()).value });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.pc += 1;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user