878 lines
32 KiB
C#
878 lines
32 KiB
C#
namespace STCompiler.Compiler;
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
using STCompiler.Common;
|
|
|
|
// === ENTRY POINT ===
|
|
class Program {
|
|
static int Main(string[] args) {
|
|
if (args.Length < 2) {
|
|
Console.WriteLine("Usage: StEmitter <input.st> <output.bin>");
|
|
return 1;
|
|
}
|
|
|
|
var input = File.ReadAllText(args[0]);
|
|
var parser = new StParser(input);
|
|
var prog = parser.ParseProgram();
|
|
|
|
if (parser.HasErrors) {
|
|
Console.WriteLine($"Compilation failed with {parser.Errors.Count} errors:");
|
|
foreach (var error in parser.Errors) {
|
|
Console.WriteLine($"Error at line {error.Line}: {error.Message}");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (prog == null) {
|
|
Console.WriteLine("Compilation failed: invalid program structure");
|
|
return 1;
|
|
}
|
|
|
|
try {
|
|
var emitter = new BytecodeEmitter();
|
|
emitter.Compile(prog);
|
|
File.WriteAllBytes(args[1], emitter.BuildBinary());
|
|
Console.WriteLine($"Wrote {args[1]}: consts={emitter.ConstantsCount}, vars={emitter.VarCount}, code={emitter.CodeLength}");
|
|
return 0;
|
|
} catch (Exception ex) {
|
|
Console.WriteLine($"Internal compiler error: {ex.Message}");
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// === AST ===
|
|
public enum VarType {
|
|
// Boolean
|
|
BOOL=1,
|
|
// Unsigned integers
|
|
BYTE=2, WORD=3, DWORD=4, LWORD=5,
|
|
// Signed integers
|
|
SINT=6, INT=7, DINT=8, LINT=9,
|
|
// Unsigned integers (alternative names)
|
|
USINT=10, UINT=11, UDINT=12, ULINT=13,
|
|
// Floating point
|
|
REAL=14, LREAL=15
|
|
}
|
|
public abstract class StNode{}
|
|
public class ProgramNode:StNode{ public List<VarDecl> Vars=new(); public List<Stmt> Stmts=new(); }
|
|
public class VarDecl:StNode{
|
|
required public string Name;
|
|
public VarType Type;
|
|
public Expr? Init;
|
|
}
|
|
|
|
public abstract class Stmt:StNode{}
|
|
public class AssignStmt:Stmt{
|
|
required public string Target;
|
|
required public Expr Expr;
|
|
}
|
|
public class IfStmt:Stmt{
|
|
required public Expr Cond;
|
|
public List<Stmt> ThenStmts=new();
|
|
public List<Stmt> ElseStmts=new();
|
|
}
|
|
public class WhileStmt:Stmt{
|
|
required public Expr Cond;
|
|
public List<Stmt> Body=new();
|
|
}
|
|
public class ForStmt:Stmt{
|
|
required public string Var;
|
|
required public Expr Start;
|
|
required public Expr End;
|
|
public Expr Step = new IntExpr(1);
|
|
public List<Stmt> Body=new();
|
|
}
|
|
|
|
public abstract class Expr:StNode {
|
|
public VarType Type; // Speichert den Typ des Ausdrucks
|
|
}
|
|
public class IntExpr:Expr {
|
|
public long Value;
|
|
public IntExpr(long v, VarType type = VarType.DINT) {
|
|
Value = v;
|
|
Type = type;
|
|
}
|
|
}
|
|
public class RealExpr:Expr {
|
|
public double Value;
|
|
public RealExpr(double v, VarType type = VarType.REAL) {
|
|
Value = v;
|
|
Type = type;
|
|
}
|
|
}
|
|
public class VarExpr:Expr {
|
|
public string Name;
|
|
public VarExpr(string n, VarType type) {
|
|
Name = n;
|
|
Type = type;
|
|
}
|
|
}
|
|
public class BinaryExpr:Expr {
|
|
public Expr L;
|
|
public Expr R;
|
|
public TokType Op;
|
|
public BinaryExpr(Expr l, TokType op, Expr r) {
|
|
L = l;
|
|
Op = op;
|
|
R = r;
|
|
Type = DetermineResultType(l.Type, r.Type);
|
|
}
|
|
|
|
private static VarType DetermineResultType(VarType left, VarType right) {
|
|
// Wenn einer der Operanden LREAL ist, ist das Ergebnis LREAL
|
|
if (left == VarType.LREAL || right == VarType.LREAL)
|
|
return VarType.LREAL;
|
|
// Wenn einer der Operanden REAL ist, ist das Ergebnis REAL
|
|
if (left == VarType.REAL || right == VarType.REAL)
|
|
return VarType.REAL;
|
|
// Bei gemischten Integer-Typen nehmen wir den größeren
|
|
if ((int)left > (int)right)
|
|
return left;
|
|
return right;
|
|
}
|
|
}
|
|
|
|
// === TOKENIZER ===
|
|
public enum TokType {
|
|
IDENT, INT, REAL, ASSIGN, SEMI, LPAREN, RPAREN,
|
|
PLUS, MINUS, MUL, DIV,
|
|
LT, GT, LE, GE, EQ, NEQ,
|
|
IF, THEN, ELSE, END_IF,
|
|
WHILE, DO, END_WHILE,
|
|
FOR, TO, BY, END_FOR,
|
|
PROGRAM, VAR, END_VAR, END_PROGRAM,
|
|
EOF
|
|
}
|
|
public class Token{
|
|
public TokType Type;
|
|
public string Text;
|
|
public int Line;
|
|
public Token(TokType t, string s, int line) { Type=t; Text=s; Line=line; }
|
|
}
|
|
|
|
public class CompileError {
|
|
public int Line;
|
|
public string Message;
|
|
public CompileError(int line, string msg) { Line=line; Message=msg; }
|
|
}
|
|
|
|
public class StLexer {
|
|
private readonly string src;
|
|
private int i;
|
|
private int currentLine = 1;
|
|
public List<CompileError> Errors = new();
|
|
public StLexer(string s){src=s;}
|
|
char Peek()=> i<src.Length?src[i]:'\0';
|
|
char Next(){
|
|
if (i >= src.Length) return '\0';
|
|
char c = src[i++];
|
|
if (c == '\n') currentLine++;
|
|
return c;
|
|
}
|
|
|
|
void AddError(string msg) => Errors.Add(new CompileError(currentLine, msg));
|
|
|
|
public Token NextToken() {
|
|
while (char.IsWhiteSpace(Peek())) Next();
|
|
if (Peek()=='\0') return new Token(TokType.EOF,"", currentLine);
|
|
|
|
if (char.IsLetter(Peek())||Peek()=='_'){
|
|
var sb=new StringBuilder();
|
|
int startLine = currentLine;
|
|
while (char.IsLetterOrDigit(Peek())||Peek()=='_') sb.Append(Next());
|
|
var s=sb.ToString().ToUpperInvariant();
|
|
return s switch {
|
|
"PROGRAM"=>new Token(TokType.PROGRAM,s,startLine),
|
|
"VAR"=>new Token(TokType.VAR,s,startLine),
|
|
"END_VAR"=>new Token(TokType.END_VAR,s,startLine),
|
|
"END_PROGRAM"=>new Token(TokType.END_PROGRAM,s,startLine),
|
|
"IF"=>new Token(TokType.IF,s,startLine),
|
|
"THEN"=>new Token(TokType.THEN,s,startLine),
|
|
"ELSE"=>new Token(TokType.ELSE,s,startLine),
|
|
"END_IF"=>new Token(TokType.END_IF,s,startLine),
|
|
"WHILE"=>new Token(TokType.WHILE,s,startLine),
|
|
"DO"=>new Token(TokType.DO,s,startLine),
|
|
"END_WHILE"=>new Token(TokType.END_WHILE,s,startLine),
|
|
"FOR"=>new Token(TokType.FOR,s,startLine),
|
|
"TO"=>new Token(TokType.TO,s,startLine),
|
|
"BY"=>new Token(TokType.BY,s,startLine),
|
|
"END_FOR"=>new Token(TokType.END_FOR,s,startLine),
|
|
_=>new Token(TokType.IDENT,s,startLine)
|
|
};
|
|
}
|
|
|
|
if (char.IsDigit(Peek())) {
|
|
var sb = new StringBuilder();
|
|
int startLine = currentLine;
|
|
bool isFloat = false;
|
|
|
|
// Ganze Zahl vor dem Dezimalpunkt
|
|
while(char.IsDigit(Peek()))
|
|
sb.Append(Next());
|
|
|
|
// Optional: Dezimalpunkt und Nachkommastellen
|
|
if (Peek() == '.') {
|
|
isFloat = true;
|
|
sb.Append(Next());
|
|
while(char.IsDigit(Peek()))
|
|
sb.Append(Next());
|
|
}
|
|
|
|
// Optional: Exponentialdarstellung
|
|
if (Peek() == 'E' || Peek() == 'e') {
|
|
isFloat = true;
|
|
sb.Append(Next());
|
|
if (Peek() == '+' || Peek() == '-')
|
|
sb.Append(Next());
|
|
if (!char.IsDigit(Peek())) {
|
|
AddError("Expected digits after exponent");
|
|
return new Token(TokType.EOF, "", startLine);
|
|
}
|
|
while(char.IsDigit(Peek()))
|
|
sb.Append(Next());
|
|
}
|
|
|
|
return new Token(isFloat ? TokType.REAL : TokType.INT, sb.ToString(), startLine);
|
|
}
|
|
|
|
int tokenLine = currentLine;
|
|
if (Peek()==':'){
|
|
Next();
|
|
if(Peek()=='='){
|
|
Next();
|
|
return new Token(TokType.ASSIGN,":=",tokenLine);
|
|
}
|
|
AddError("Expected '=' after ':' for assignment");
|
|
// Bei einem einzelnen ':' geben wir EOF zurück und stoppen das Parsen
|
|
i--; // Gehen einen Schritt zurück, damit der fehlerhafte ':' Token beim nächsten Mal neu gelesen wird
|
|
return new Token(TokType.EOF,"",tokenLine);
|
|
}
|
|
if (Peek()=='<'){
|
|
Next();
|
|
if (Peek()=='='){Next(); return new Token(TokType.LE,"<=",tokenLine);}
|
|
if (Peek()=='>'){Next(); return new Token(TokType.NEQ,"<>",tokenLine);}
|
|
return new Token(TokType.LT,"<",tokenLine);
|
|
}
|
|
if (Peek()=='>'){
|
|
Next();
|
|
if (Peek()=='='){Next(); return new Token(TokType.GE,">=",tokenLine);}
|
|
return new Token(TokType.GT,">",tokenLine);
|
|
}
|
|
if (Peek()=='='){Next();return new Token(TokType.EQ,"=",tokenLine);}
|
|
|
|
char c=Next();
|
|
if (c == ';') return new Token(TokType.SEMI,";",tokenLine);
|
|
if (c == '(') return new Token(TokType.LPAREN,"(",tokenLine);
|
|
if (c == ')') return new Token(TokType.RPAREN,")",tokenLine);
|
|
if (c == '+') return new Token(TokType.PLUS,"+",tokenLine);
|
|
if (c == '-') return new Token(TokType.MINUS,"-",tokenLine);
|
|
if (c == '*') return new Token(TokType.MUL,"*",tokenLine);
|
|
if (c == '/') return new Token(TokType.DIV,"/",tokenLine);
|
|
|
|
AddError($"Unexpected character '{c}'");
|
|
return new Token(TokType.EOF,"",tokenLine); // Skip invalid character
|
|
}
|
|
}
|
|
|
|
// === PARSER ===
|
|
public record Symbol {
|
|
required public string Name;
|
|
public VarType Type;
|
|
public int Index;
|
|
}
|
|
|
|
public class StParser {
|
|
StLexer lex;
|
|
Token cur;
|
|
Dictionary<string,Symbol> syms = new();
|
|
public List<CompileError> Errors => lex.Errors;
|
|
public bool HasErrors => Errors.Count > 0;
|
|
|
|
public StParser(string s){
|
|
lex=new StLexer(s);
|
|
cur=lex.NextToken();
|
|
}
|
|
|
|
void Next()=>cur=lex.NextToken();
|
|
|
|
void AddError(string msg) => lex.Errors.Add(new CompileError(cur.Line, msg));
|
|
|
|
bool Expect(TokType t){
|
|
if(cur.Type!=t) {
|
|
AddError($"Expected {t}, got {cur.Type}");
|
|
return false;
|
|
}
|
|
Next();
|
|
return true;
|
|
}
|
|
|
|
public ProgramNode? ParseProgram(){
|
|
var p=new ProgramNode();
|
|
if (!Expect(TokType.PROGRAM)) return null;
|
|
|
|
if(cur.Type==TokType.IDENT) Next();
|
|
|
|
if(cur.Type==TokType.VAR){
|
|
Next();
|
|
while(cur.Type==TokType.IDENT) {
|
|
var varDecl = ParseVarDecl();
|
|
if (varDecl == null) return null;
|
|
p.Vars.Add(varDecl);
|
|
}
|
|
if (!Expect(TokType.END_VAR)) return null;
|
|
}
|
|
|
|
while(cur.Type!=TokType.END_PROGRAM && cur.Type!=TokType.EOF) {
|
|
var stmt = ParseStmt();
|
|
if (stmt == null) return null;
|
|
p.Stmts.Add(stmt);
|
|
}
|
|
|
|
if (!Expect(TokType.END_PROGRAM)) return null;
|
|
return p;
|
|
}
|
|
|
|
VarDecl? ParseVarDecl(){
|
|
if (cur.Type != TokType.IDENT) {
|
|
AddError("Expected identifier for variable declaration");
|
|
return null;
|
|
}
|
|
string name=cur.Text.ToUpperInvariant(); // Variablennamen in Großbuchstaben
|
|
Next();
|
|
|
|
if (cur.Type != TokType.IDENT) {
|
|
AddError("Expected type name");
|
|
return null;
|
|
}
|
|
string tname=cur.Text.ToLowerInvariant();
|
|
Next();
|
|
|
|
VarType? vt = tname switch {
|
|
// Boolean
|
|
"bool" => VarType.BOOL,
|
|
// Unsigned integers
|
|
"byte" => VarType.BYTE,
|
|
"word" => VarType.WORD,
|
|
"dword" => VarType.DWORD,
|
|
"lword" => VarType.LWORD,
|
|
// Signed integers
|
|
"sint" => VarType.SINT,
|
|
"int" => VarType.INT,
|
|
"dint" => VarType.DINT,
|
|
"lint" => VarType.LINT,
|
|
// Unsigned integers (alternative names)
|
|
"usint" => VarType.USINT,
|
|
"uint" => VarType.UINT,
|
|
"udint" => VarType.UDINT,
|
|
"ulint" => VarType.ULINT,
|
|
// Floating point
|
|
"real" => VarType.REAL,
|
|
"lreal" => VarType.LREAL,
|
|
_ => null
|
|
};
|
|
|
|
if (vt == null) {
|
|
AddError($"Unknown type '{tname}'");
|
|
return null;
|
|
}
|
|
|
|
Expr? init=null;
|
|
if(cur.Type==TokType.ASSIGN){
|
|
Next(); // consume :=
|
|
init=ParseExpr();
|
|
if (init == null) {
|
|
AddError($"Expected expression after ':=' in variable declaration");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (!Expect(TokType.SEMI)) return null;
|
|
|
|
// Füge Variable zur Symboltabelle hinzu
|
|
syms[name] = new Symbol { Name = name, Type = vt.Value };
|
|
|
|
return new VarDecl{Name=name,Type=vt.Value,Init=init};
|
|
}
|
|
|
|
Stmt? ParseAssign() {
|
|
// Der Aufrufer hat bereits geprüft, dass wir bei einem IDENT sind
|
|
string target = cur.Text.ToUpperInvariant(); // Variablennamen in Großbuchstaben
|
|
Next(); // consume identifier
|
|
if (cur.Type != TokType.ASSIGN) {
|
|
AddError($"Expected ':=' after identifier '{target}'");
|
|
return null;
|
|
}
|
|
Next(); // consume :=
|
|
var e = ParseExpr();
|
|
if (e == null) return null;
|
|
if (!Expect(TokType.SEMI)) return null;
|
|
return new AssignStmt{Target=target, Expr=e};
|
|
}
|
|
|
|
Stmt? ParseStmt(){
|
|
switch(cur.Type) {
|
|
case TokType.IF: return ParseIf();
|
|
case TokType.WHILE: return ParseWhile();
|
|
case TokType.FOR: return ParseFor();
|
|
case TokType.IDENT:
|
|
return ParseAssign();
|
|
default:
|
|
AddError($"Unexpected token {cur.Type} in statement");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
IfStmt? ParseIf(){
|
|
Next(); // IF
|
|
var cond=ParseExpr();
|
|
if (cond == null) return null;
|
|
if (!Expect(TokType.THEN)) return null;
|
|
|
|
var node=new IfStmt{Cond=cond};
|
|
while(cur.Type!=TokType.ELSE && cur.Type!=TokType.END_IF && cur.Type!=TokType.EOF) {
|
|
var stmt = ParseStmt();
|
|
if (stmt == null) return null;
|
|
node.ThenStmts.Add(stmt);
|
|
}
|
|
|
|
if(cur.Type==TokType.ELSE){
|
|
Next();
|
|
while(cur.Type!=TokType.END_IF && cur.Type!=TokType.EOF) {
|
|
var stmt = ParseStmt();
|
|
if (stmt == null) return null;
|
|
node.ElseStmts.Add(stmt);
|
|
}
|
|
}
|
|
|
|
if (!Expect(TokType.END_IF)) return null;
|
|
if (!Expect(TokType.SEMI)) return null;
|
|
return node;
|
|
}
|
|
|
|
WhileStmt? ParseWhile(){
|
|
Next(); // WHILE
|
|
var cond=ParseExpr();
|
|
if (cond == null) return null;
|
|
if (!Expect(TokType.DO)) return null;
|
|
|
|
var ws=new WhileStmt{Cond=cond};
|
|
while(cur.Type!=TokType.END_WHILE && cur.Type!=TokType.EOF) {
|
|
var stmt = ParseStmt();
|
|
if (stmt == null) return null;
|
|
ws.Body.Add(stmt);
|
|
}
|
|
|
|
if (!Expect(TokType.END_WHILE)) return null;
|
|
if (!Expect(TokType.SEMI)) return null;
|
|
return ws;
|
|
}
|
|
|
|
ForStmt? ParseFor(){
|
|
Next(); // FOR
|
|
if (cur.Type != TokType.IDENT) {
|
|
AddError("Expected identifier for FOR loop variable");
|
|
return null;
|
|
}
|
|
string varName = cur.Text.ToUpperInvariant(); // Variablennamen in Großbuchstaben
|
|
Next(); // consume identifier
|
|
|
|
if (cur.Type != TokType.ASSIGN) {
|
|
AddError($"Expected ':=' after identifier '{varName}'");
|
|
return null;
|
|
}
|
|
Next(); // consume :=
|
|
var start = ParseExpr();
|
|
if (start == null) return null;
|
|
|
|
if (!Expect(TokType.TO)) return null;
|
|
var end = ParseExpr();
|
|
if (end == null) return null;
|
|
|
|
Expr step = new IntExpr(1);
|
|
if(cur.Type==TokType.BY){
|
|
Next();
|
|
step = ParseExpr() ?? step;
|
|
}
|
|
|
|
if (!Expect(TokType.DO)) return null;
|
|
|
|
var fs = new ForStmt{Var=varName, Start=start, End=end, Step=step};
|
|
while(cur.Type!=TokType.END_FOR && cur.Type!=TokType.EOF) {
|
|
var stmt = ParseStmt();
|
|
if (stmt == null) return null;
|
|
fs.Body.Add(stmt);
|
|
}
|
|
|
|
if (!Expect(TokType.END_FOR)) return null;
|
|
if (!Expect(TokType.SEMI)) return null;
|
|
return fs;
|
|
}
|
|
|
|
Expr? ParseExpr() => ParseCompare();
|
|
|
|
Expr? ParseCompare(){
|
|
var l = ParseAddSub();
|
|
if (l == null) return null;
|
|
|
|
while(cur.Type is TokType.LT or TokType.GT or TokType.LE or TokType.GE or TokType.EQ or TokType.NEQ){
|
|
var op=cur.Type;
|
|
Next();
|
|
var r=ParseAddSub();
|
|
if (r == null) return null;
|
|
l=new BinaryExpr(l,op,r);
|
|
}
|
|
return l;
|
|
}
|
|
|
|
Expr? ParseAddSub(){
|
|
var l=ParseMulDiv();
|
|
if (l == null) return null;
|
|
|
|
while(cur.Type==TokType.PLUS||cur.Type==TokType.MINUS){
|
|
var op=cur.Type;
|
|
Next();
|
|
var r=ParseMulDiv();
|
|
if (r == null) return null;
|
|
l=new BinaryExpr(l,op,r);
|
|
}
|
|
return l;
|
|
}
|
|
|
|
Expr? ParseMulDiv(){
|
|
var l=ParsePrimary();
|
|
if (l == null) return null;
|
|
|
|
while(cur.Type==TokType.MUL||cur.Type==TokType.DIV){
|
|
var op=cur.Type;
|
|
Next();
|
|
var r=ParsePrimary();
|
|
if (r == null) return null;
|
|
l=new BinaryExpr(l,op,r);
|
|
}
|
|
return l;
|
|
}
|
|
|
|
Expr? ParsePrimary(){
|
|
int startLine = cur.Line;
|
|
switch(cur.Type) {
|
|
case TokType.INT:
|
|
if (!long.TryParse(cur.Text, out var v)) {
|
|
AddError($"Invalid integer literal '{cur.Text}'");
|
|
return null;
|
|
}
|
|
Next();
|
|
return new IntExpr(v, VarType.DINT);
|
|
|
|
case TokType.REAL:
|
|
if (!double.TryParse(cur.Text, out var d)) {
|
|
AddError($"Invalid floating point literal '{cur.Text}'");
|
|
return null;
|
|
}
|
|
Next();
|
|
return new RealExpr(d);
|
|
|
|
case TokType.IDENT:
|
|
string n = cur.Text.ToUpperInvariant(); // Variablennamen in Großbuchstaben
|
|
Next();
|
|
Symbol? sym;
|
|
if (!syms.TryGetValue(n, out sym)) {
|
|
AddError($"Undeclared variable '{n}'");
|
|
return null;
|
|
}
|
|
return new VarExpr(n, sym.Type);
|
|
|
|
case TokType.LPAREN:
|
|
Next();
|
|
var e = ParseExpr();
|
|
if (e == null) return null;
|
|
if (!Expect(TokType.RPAREN)) return null;
|
|
return e;
|
|
|
|
default:
|
|
AddError($"Unexpected token {cur.Type} in expression");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// === BYTECODE ===
|
|
public class BytecodeEmitter {
|
|
List<object> consts = new(); // Kann nun int, long, float oder double speichern
|
|
Dictionary<string,Symbol> syms = new();
|
|
List<byte> code = new();
|
|
|
|
class Symbol {
|
|
required public string Name;
|
|
public VarType Type;
|
|
public int Index;
|
|
}
|
|
|
|
public int ConstantsCount => consts.Count;
|
|
public int VarCount => syms.Count;
|
|
public int CodeLength => code.Count;
|
|
|
|
public void Compile(ProgramNode p){
|
|
int idx=0;
|
|
foreach(var v in p.Vars)
|
|
syms[v.Name]=new Symbol{Name=v.Name,Type=v.Type,Index=idx++};
|
|
|
|
foreach(var v in p.Vars)
|
|
if(v.Init!=null){EmitExpr(v.Init);EmitByte(0x03);EmitU16((ushort)syms[v.Name].Index);}
|
|
|
|
foreach(var s in p.Stmts)
|
|
EmitStmt(s);
|
|
|
|
EmitByte(0xF0); // Program End
|
|
}
|
|
|
|
// Operationscodes für verschiedene Datentypen
|
|
Dictionary<(TokType, VarType), byte> opCodes = new() {
|
|
// Signed integer arithmetic
|
|
{(TokType.PLUS, VarType.SINT), Bytecode.OpCodes.ADD_SINT}, {(TokType.MINUS, VarType.SINT), Bytecode.OpCodes.SUB_SINT},
|
|
{(TokType.MUL, VarType.SINT), Bytecode.OpCodes.MUL_SINT}, {(TokType.DIV, VarType.SINT), Bytecode.OpCodes.DIV_SINT},
|
|
{(TokType.PLUS, VarType.INT), Bytecode.OpCodes.ADD_INT}, {(TokType.MINUS, VarType.INT), Bytecode.OpCodes.SUB_INT},
|
|
{(TokType.MUL, VarType.INT), Bytecode.OpCodes.MUL_INT}, {(TokType.DIV, VarType.INT), Bytecode.OpCodes.DIV_INT},
|
|
{(TokType.PLUS, VarType.DINT), Bytecode.OpCodes.ADD_DINT}, {(TokType.MINUS, VarType.DINT), Bytecode.OpCodes.SUB_DINT},
|
|
{(TokType.MUL, VarType.DINT), Bytecode.OpCodes.MUL_DINT}, {(TokType.DIV, VarType.DINT), Bytecode.OpCodes.DIV_DINT},
|
|
{(TokType.PLUS, VarType.LINT), Bytecode.OpCodes.ADD_LINT}, {(TokType.MINUS, VarType.LINT), Bytecode.OpCodes.SUB_LINT},
|
|
{(TokType.MUL, VarType.LINT), Bytecode.OpCodes.MUL_LINT}, {(TokType.DIV, VarType.LINT), Bytecode.OpCodes.DIV_LINT},
|
|
|
|
// Unsigned integer arithmetic
|
|
{(TokType.PLUS, VarType.USINT), Bytecode.OpCodes.ADD_USINT}, {(TokType.MINUS, VarType.USINT), Bytecode.OpCodes.SUB_USINT},
|
|
{(TokType.MUL, VarType.USINT), Bytecode.OpCodes.MUL_USINT}, {(TokType.DIV, VarType.USINT), Bytecode.OpCodes.DIV_USINT},
|
|
{(TokType.PLUS, VarType.UINT), Bytecode.OpCodes.ADD_UINT}, {(TokType.MINUS, VarType.UINT), Bytecode.OpCodes.SUB_UINT},
|
|
{(TokType.MUL, VarType.UINT), Bytecode.OpCodes.MUL_UINT}, {(TokType.DIV, VarType.UINT), Bytecode.OpCodes.DIV_UINT},
|
|
{(TokType.PLUS, VarType.UDINT), Bytecode.OpCodes.ADD_UDINT}, {(TokType.MINUS, VarType.UDINT), Bytecode.OpCodes.SUB_UDINT},
|
|
{(TokType.MUL, VarType.UDINT), Bytecode.OpCodes.MUL_UDINT}, {(TokType.DIV, VarType.UDINT), Bytecode.OpCodes.DIV_UDINT},
|
|
{(TokType.PLUS, VarType.ULINT), Bytecode.OpCodes.ADD_ULINT}, {(TokType.MINUS, VarType.ULINT), Bytecode.OpCodes.SUB_ULINT},
|
|
{(TokType.MUL, VarType.ULINT), Bytecode.OpCodes.MUL_ULINT}, {(TokType.DIV, VarType.ULINT), Bytecode.OpCodes.DIV_ULINT},
|
|
|
|
// Floating point arithmetic
|
|
{(TokType.PLUS, VarType.REAL), Bytecode.OpCodes.ADD_REAL}, {(TokType.MINUS, VarType.REAL), Bytecode.OpCodes.SUB_REAL},
|
|
{(TokType.MUL, VarType.REAL), Bytecode.OpCodes.MUL_REAL}, {(TokType.DIV, VarType.REAL), Bytecode.OpCodes.DIV_REAL},
|
|
{(TokType.PLUS, VarType.LREAL), Bytecode.OpCodes.ADD_LREAL}, {(TokType.MINUS, VarType.LREAL), Bytecode.OpCodes.SUB_LREAL},
|
|
{(TokType.MUL, VarType.LREAL), Bytecode.OpCodes.MUL_LREAL}, {(TokType.DIV, VarType.LREAL), Bytecode.OpCodes.DIV_LREAL},
|
|
|
|
// Comparisons signed
|
|
{(TokType.LT, VarType.SINT), Bytecode.OpCodes.LT_S}, {(TokType.GT, VarType.SINT), Bytecode.OpCodes.GT_S},
|
|
{(TokType.LE, VarType.SINT), Bytecode.OpCodes.LE_S}, {(TokType.GE, VarType.SINT), Bytecode.OpCodes.GE_S},
|
|
{(TokType.EQ, VarType.SINT), Bytecode.OpCodes.EQ_S}, {(TokType.NEQ, VarType.SINT), Bytecode.OpCodes.NEQ_S},
|
|
{(TokType.LT, VarType.INT), Bytecode.OpCodes.LT_S}, {(TokType.GT, VarType.INT), Bytecode.OpCodes.GT_S},
|
|
{(TokType.LE, VarType.INT), Bytecode.OpCodes.LE_S}, {(TokType.GE, VarType.INT), Bytecode.OpCodes.GE_S},
|
|
{(TokType.EQ, VarType.INT), Bytecode.OpCodes.EQ_S}, {(TokType.NEQ, VarType.INT), Bytecode.OpCodes.NEQ_S},
|
|
{(TokType.LT, VarType.DINT), Bytecode.OpCodes.LT_S}, {(TokType.GT, VarType.DINT), Bytecode.OpCodes.GT_S},
|
|
{(TokType.LE, VarType.DINT), Bytecode.OpCodes.LE_S}, {(TokType.GE, VarType.DINT), Bytecode.OpCodes.GE_S},
|
|
{(TokType.EQ, VarType.DINT), Bytecode.OpCodes.EQ_S}, {(TokType.NEQ, VarType.DINT), Bytecode.OpCodes.NEQ_S},
|
|
{(TokType.LT, VarType.LINT), Bytecode.OpCodes.LT_S}, {(TokType.GT, VarType.LINT), Bytecode.OpCodes.GT_S},
|
|
{(TokType.LE, VarType.LINT), Bytecode.OpCodes.LE_S}, {(TokType.GE, VarType.LINT), Bytecode.OpCodes.GE_S},
|
|
{(TokType.EQ, VarType.LINT), Bytecode.OpCodes.EQ_S}, {(TokType.NEQ, VarType.LINT), Bytecode.OpCodes.NEQ_S},
|
|
|
|
// Comparisons unsigned
|
|
{(TokType.LT, VarType.USINT), Bytecode.OpCodes.LT_U}, {(TokType.GT, VarType.USINT), Bytecode.OpCodes.GT_U},
|
|
{(TokType.LE, VarType.USINT), Bytecode.OpCodes.LE_U}, {(TokType.GE, VarType.USINT), Bytecode.OpCodes.GE_U},
|
|
{(TokType.EQ, VarType.USINT), Bytecode.OpCodes.EQ_U}, {(TokType.NEQ, VarType.USINT), Bytecode.OpCodes.NEQ_U},
|
|
{(TokType.LT, VarType.UINT), Bytecode.OpCodes.LT_U}, {(TokType.GT, VarType.UINT), Bytecode.OpCodes.GT_U},
|
|
{(TokType.LE, VarType.UINT), Bytecode.OpCodes.LE_U}, {(TokType.GE, VarType.UINT), Bytecode.OpCodes.GE_U},
|
|
{(TokType.EQ, VarType.UINT), Bytecode.OpCodes.EQ_U}, {(TokType.NEQ, VarType.UINT), Bytecode.OpCodes.NEQ_U},
|
|
{(TokType.LT, VarType.UDINT), Bytecode.OpCodes.LT_U}, {(TokType.GT, VarType.UDINT), Bytecode.OpCodes.GT_U},
|
|
{(TokType.LE, VarType.UDINT), Bytecode.OpCodes.LE_U}, {(TokType.GE, VarType.UDINT), Bytecode.OpCodes.GE_U},
|
|
{(TokType.EQ, VarType.UDINT), Bytecode.OpCodes.EQ_U}, {(TokType.NEQ, VarType.UDINT), Bytecode.OpCodes.NEQ_U},
|
|
{(TokType.LT, VarType.ULINT), Bytecode.OpCodes.LT_U}, {(TokType.GT, VarType.ULINT), Bytecode.OpCodes.GT_U},
|
|
{(TokType.LE, VarType.ULINT), Bytecode.OpCodes.LE_U}, {(TokType.GE, VarType.ULINT), Bytecode.OpCodes.GE_U},
|
|
{(TokType.EQ, VarType.ULINT), Bytecode.OpCodes.EQ_U}, {(TokType.NEQ, VarType.ULINT), Bytecode.OpCodes.NEQ_U},
|
|
|
|
// Comparisons floating
|
|
{(TokType.LT, VarType.REAL), Bytecode.OpCodes.LT_F}, {(TokType.GT, VarType.REAL), Bytecode.OpCodes.GT_F},
|
|
{(TokType.LE, VarType.REAL), Bytecode.OpCodes.LE_F}, {(TokType.GE, VarType.REAL), Bytecode.OpCodes.GE_F},
|
|
{(TokType.EQ, VarType.REAL), Bytecode.OpCodes.EQ_F}, {(TokType.NEQ, VarType.REAL), Bytecode.OpCodes.NEQ_F},
|
|
{(TokType.LT, VarType.LREAL), Bytecode.OpCodes.LT_F}, {(TokType.GT, VarType.LREAL), Bytecode.OpCodes.GT_F},
|
|
{(TokType.LE, VarType.LREAL), Bytecode.OpCodes.LE_F}, {(TokType.GE, VarType.LREAL), Bytecode.OpCodes.GE_F},
|
|
{(TokType.EQ, VarType.LREAL), Bytecode.OpCodes.EQ_F}, {(TokType.NEQ, VarType.LREAL), Bytecode.OpCodes.NEQ_F}
|
|
};
|
|
|
|
void EmitStmt(Stmt s){
|
|
switch(s){
|
|
case AssignStmt a:
|
|
Symbol? symbol = null;
|
|
if (!syms.TryGetValue(a.Target, out symbol)) {
|
|
throw new Exception($"Undeclared variable '{a.Target}'");
|
|
}
|
|
EmitExpr(a.Expr);
|
|
EmitByte(Bytecode.OpCodes.STORE_VAR); EmitU16((ushort)symbol.Index);
|
|
break;
|
|
|
|
case IfStmt iff:
|
|
EmitExpr(iff.Cond);
|
|
EmitByte(Bytecode.OpCodes.JZ); int jz=code.Count; EmitU16(0);
|
|
foreach(var st in iff.ThenStmts) EmitStmt(st);
|
|
if(iff.ElseStmts.Count>0){
|
|
EmitByte(Bytecode.OpCodes.JMP); int jmp=code.Count; EmitU16(0);
|
|
PatchJump(jz,code.Count);
|
|
foreach(var st in iff.ElseStmts) EmitStmt(st);
|
|
PatchJump(jmp,code.Count);
|
|
} else PatchJump(jz,code.Count);
|
|
break;
|
|
|
|
case WhileStmt w:
|
|
int loopStart=code.Count;
|
|
EmitExpr(w.Cond);
|
|
EmitByte(Bytecode.OpCodes.JZ); int jzpos=code.Count; EmitU16(0);
|
|
foreach(var st in w.Body) EmitStmt(st);
|
|
EmitByte(Bytecode.OpCodes.JMP); EmitU16((ushort)loopStart);
|
|
PatchJump(jzpos,code.Count);
|
|
break;
|
|
|
|
case ForStmt f:
|
|
Symbol? forSymbol = null;
|
|
if (!syms.TryGetValue(f.Var, out forSymbol)) {
|
|
throw new Exception($"Undeclared variable '{f.Var}'");
|
|
}
|
|
// Initialisierung: var := start
|
|
EmitExpr(f.Start);
|
|
EmitByte(Bytecode.OpCodes.STORE_VAR); EmitU16((ushort)forSymbol.Index);
|
|
|
|
int cmpPos = code.Count; // Position des Vergleichs
|
|
EmitExpr(new VarExpr(f.Var, forSymbol.Type));
|
|
EmitExpr(f.End);
|
|
var key = (TokType.LE, forSymbol.Type);
|
|
if (!opCodes.ContainsKey(key)) {
|
|
throw new Exception($"Comparison not supported for type {forSymbol.Type}");
|
|
}
|
|
EmitByte(opCodes[key]);
|
|
EmitByte(Bytecode.OpCodes.JZ); int jzFor = code.Count; EmitU16(0); // Jump zum Ende
|
|
|
|
// Body
|
|
foreach(var st in f.Body) EmitStmt(st);
|
|
|
|
// Inkrement: i := i + step
|
|
EmitExpr(new VarExpr(f.Var, forSymbol.Type));
|
|
EmitExpr(f.Step);
|
|
var addKey = (TokType.PLUS, forSymbol.Type);
|
|
if (!opCodes.ContainsKey(addKey)) {
|
|
throw new Exception($"Addition not supported for type {forSymbol.Type}");
|
|
}
|
|
EmitByte(opCodes[addKey]);
|
|
EmitByte(Bytecode.OpCodes.STORE_VAR); EmitU16((ushort)forSymbol.Index);
|
|
|
|
// Vergleich erneut, aber wir merken uns den Sprung zum Vergleich
|
|
EmitByte(Bytecode.OpCodes.JMP); EmitU16((ushort)cmpPos);
|
|
|
|
// Patch Jump: Ende der Schleife springt hierhin
|
|
PatchJump(jzFor, code.Count);
|
|
|
|
// Korrektur: Schleifenvariable auf Endwert, falls sie überläuft
|
|
EmitExpr(f.End);
|
|
EmitByte(Bytecode.OpCodes.STORE_VAR); EmitU16((ushort)syms[f.Var].Index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PatchJump(int pos,int target){code[pos]=(byte)(target&0xFF);code[pos+1]=(byte)(target>>8);}
|
|
void EmitExpr(Expr e){
|
|
switch(e){
|
|
case IntExpr ie:
|
|
int ci = AddConst(ie.Value);
|
|
EmitByte(Bytecode.OpCodes.PUSH_CONST); // PUSH_CONST
|
|
EmitU16((ushort)ci);
|
|
EmitByte((byte)ie.Type); // Typ der Konstante
|
|
break;
|
|
|
|
case RealExpr re:
|
|
int cri = AddConst(re.Value);
|
|
EmitByte(Bytecode.OpCodes.PUSH_REAL_CONST); // PUSH_REAL_CONST
|
|
EmitU16((ushort)cri);
|
|
EmitByte((byte)re.Type); // REAL oder LREAL
|
|
break;
|
|
|
|
case VarExpr ve:
|
|
Symbol? symbol = null;
|
|
if (!syms.TryGetValue(ve.Name, out symbol)) {
|
|
throw new Exception($"Undeclared variable '{ve.Name}'");
|
|
}
|
|
EmitByte(Bytecode.OpCodes.LOAD_VAR); // LOAD_VAR
|
|
EmitU16((ushort)symbol.Index);
|
|
break;
|
|
|
|
case BinaryExpr be:
|
|
EmitExpr(be.L);
|
|
EmitExpr(be.R);
|
|
|
|
var key = (be.Op, be.Type);
|
|
if (!opCodes.ContainsKey(key)) {
|
|
throw new Exception($"Unknown operator '{be.Op}' for type {be.Type}");
|
|
}
|
|
|
|
// Wenn nötig, Typenkonvertierung vor der Operation
|
|
if (be.L.Type != be.Type)
|
|
EmitByte((byte)(0x50 + (int)be.Type)); // CONVERT_TO_*
|
|
if (be.R.Type != be.Type)
|
|
EmitByte((byte)(0x50 + (int)be.Type)); // CONVERT_TO_*
|
|
|
|
EmitByte(opCodes[key]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int AddConst(object v) {
|
|
int i = consts.FindIndex(c => c.Equals(v));
|
|
if (i >= 0) return i;
|
|
consts.Add(v);
|
|
return consts.Count - 1;
|
|
}
|
|
|
|
void EmitByte(byte b) => code.Add(b);
|
|
void EmitU16(ushort v) {code.Add((byte)(v&0xFF));code.Add((byte)(v>>8));}
|
|
|
|
public byte[] BuildBinary() {
|
|
using var ms = new MemoryStream();
|
|
var w = new BinaryWriter(ms);
|
|
|
|
// Header
|
|
w.Write(Encoding.ASCII.GetBytes(Bytecode.Magic));
|
|
w.Write(Bytecode.Version);
|
|
|
|
// Konstanten
|
|
w.Write((ushort)consts.Count);
|
|
foreach(var c in consts) {
|
|
if (c is long l) {
|
|
w.Write((byte)1); // Long type marker
|
|
w.Write(l);
|
|
}
|
|
else if (c is double d) {
|
|
w.Write((byte)2); // Double type marker
|
|
w.Write(d);
|
|
}
|
|
else if (c is float f) {
|
|
w.Write((byte)3); // Float type marker
|
|
w.Write(f);
|
|
}
|
|
else if (c is int i) {
|
|
w.Write((byte)4); // Int type marker
|
|
w.Write(i);
|
|
}
|
|
else {
|
|
throw new Exception($"Unsupported constant type: {c.GetType()}");
|
|
}
|
|
}
|
|
|
|
// Variablen
|
|
w.Write((ushort)syms.Count);
|
|
var types = new byte[syms.Count];
|
|
foreach(var kv in syms)
|
|
types[kv.Value.Index] = (byte)kv.Value.Type;
|
|
foreach(var b in types)
|
|
w.Write(b);
|
|
|
|
// Code
|
|
w.Write((ushort)code.Count);
|
|
w.Write(code.ToArray());
|
|
|
|
return ms.ToArray();
|
|
}
|
|
}
|