Compare commits
2 Commits
ec4d7dc26e
...
74f256efb2
| Author | SHA1 | Date | |
|---|---|---|---|
| 74f256efb2 | |||
| 3cb6373915 |
@ -40,6 +40,9 @@ public static class Bytecode {
|
|||||||
public const byte JZ = 0x70;
|
public const byte JZ = 0x70;
|
||||||
public const byte JMP = 0x71;
|
public const byte JMP = 0x71;
|
||||||
|
|
||||||
|
// Array operations
|
||||||
|
public const byte ARRAY_BOUNDS_CHECK = 0xE0;
|
||||||
|
|
||||||
public const byte HALT = 0xF0;
|
public const byte HALT = 0xF0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +66,7 @@ public static class Bytecode {
|
|||||||
{ OpCodes.LT_U, "LT_U" }, { OpCodes.GT_U, "GT_U" }, { OpCodes.LE_U, "LE_U" }, { OpCodes.GE_U, "GE_U" }, { OpCodes.EQ_U, "EQ_U" }, { OpCodes.NEQ_U, "NEQ_U" },
|
{ OpCodes.LT_U, "LT_U" }, { OpCodes.GT_U, "GT_U" }, { OpCodes.LE_U, "LE_U" }, { OpCodes.GE_U, "GE_U" }, { OpCodes.EQ_U, "EQ_U" }, { OpCodes.NEQ_U, "NEQ_U" },
|
||||||
{ OpCodes.LT_F, "LT_F" }, { OpCodes.GT_F, "GT_F" }, { OpCodes.LE_F, "LE_F" }, { OpCodes.GE_F, "GE_F" }, { OpCodes.EQ_F, "EQ_F" }, { OpCodes.NEQ_F, "NEQ_F" },
|
{ OpCodes.LT_F, "LT_F" }, { OpCodes.GT_F, "GT_F" }, { OpCodes.LE_F, "LE_F" }, { OpCodes.GE_F, "GE_F" }, { OpCodes.EQ_F, "EQ_F" }, { OpCodes.NEQ_F, "NEQ_F" },
|
||||||
{ OpCodes.JZ, "JZ" }, { OpCodes.JMP, "JMP" },
|
{ OpCodes.JZ, "JZ" }, { OpCodes.JMP, "JMP" },
|
||||||
|
{ OpCodes.ARRAY_BOUNDS_CHECK, "ARRAY_BOUNDS_CHECK" },
|
||||||
{ OpCodes.HALT, "HALT" }
|
{ OpCodes.HALT, "HALT" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -18,5 +18,24 @@ public enum VarType {
|
|||||||
TIME_OF_DAY = 18, // Tageszeit (TOD) -> TimeSpan / DateTime
|
TIME_OF_DAY = 18, // Tageszeit (TOD) -> TimeSpan / DateTime
|
||||||
TOD = TIME_OF_DAY,
|
TOD = TIME_OF_DAY,
|
||||||
DATE_AND_TIME = 19,// Datum + Uhrzeit (DT) -> DateTime
|
DATE_AND_TIME = 19,// Datum + Uhrzeit (DT) -> DateTime
|
||||||
DT = DATE_AND_TIME
|
DT = DATE_AND_TIME,
|
||||||
|
// Array marker - actual array types use ArrayType class
|
||||||
|
ARRAY = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents an array type in structured text
|
||||||
|
public class ArrayType {
|
||||||
|
public VarType ElementType { get; set; } // Type of array elements
|
||||||
|
public int Start { get; set; } // Start index
|
||||||
|
public int End { get; set; } // End index
|
||||||
|
public int Length => End - Start + 1; // Number of elements
|
||||||
|
|
||||||
|
public ArrayType(VarType elementType, int start, int end) {
|
||||||
|
ElementType = elementType;
|
||||||
|
Start = start;
|
||||||
|
End = end;
|
||||||
|
if (end < start) throw new System.ArgumentException("Array end index must be greater than or equal to start index");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"ARRAY [{Start}..{End}] OF {ElementType}";
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -13,10 +13,10 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Common")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Common")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9864c1965fa3f1dc2950b7afd816891e75f6857d")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3cb63739152cd26ec43993953c7b93d4ab9bcce7")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Common")]
|
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Common")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Common")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Common")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
// Generated by the MSBuild WriteCodeFragment class.
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
3ee5798b43eb2bdc5cb3333bf67081acc7fb5bfbfd1c25a96138387cd1048786
|
72a5aa37f3f499d7234a1ea9f8597714d83c5061f8d255b5e45126805bf484a2
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
94
STCompiler.Compiler/Ast.cs
Normal file
94
STCompiler.Compiler/Ast.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
namespace STCompiler.Compiler;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using STCompiler.Common;
|
||||||
|
|
||||||
|
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; // Variable name
|
||||||
|
public Expr? Index; // Array index if this is an array assignment
|
||||||
|
required public Expr Expr; // Value to assign
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class ArrayAccessExpr : Expr {
|
||||||
|
public string ArrayName;
|
||||||
|
public Expr Index;
|
||||||
|
|
||||||
|
public ArrayAccessExpr(string name, Expr idx, VarType elementType) {
|
||||||
|
ArrayName = name;
|
||||||
|
Index = idx;
|
||||||
|
Type = elementType;
|
||||||
|
}
|
||||||
|
}
|
||||||
182
STCompiler.Compiler/Lexer.cs
Normal file
182
STCompiler.Compiler/Lexer.cs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
namespace STCompiler.Compiler;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using STCompiler.Common;
|
||||||
|
|
||||||
|
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,
|
||||||
|
ARRAY, OF, LBRACKET, RBRACKET, DOTS,
|
||||||
|
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 Peek2()=> i+1<src.Length?src[i+1]:'\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);
|
||||||
|
|
||||||
|
// Skip line comments starting with '//'
|
||||||
|
if (Peek() == '/' && Peek2() == '/') {
|
||||||
|
// consume '//'
|
||||||
|
Next(); Next();
|
||||||
|
// skip until end of line or EOF
|
||||||
|
while (Peek() != '\0' && Peek() != '\n') Next();
|
||||||
|
// consume newline if present
|
||||||
|
if (Peek() == '\n') Next();
|
||||||
|
// restart tokenization after the comment
|
||||||
|
return NextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
"ARRAY"=>new Token(TokType.ARRAY,s,startLine),
|
||||||
|
"OF"=>new Token(TokType.OF,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 oder Bereichsoperator
|
||||||
|
while(char.IsDigit(Peek()))
|
||||||
|
sb.Append(Next());
|
||||||
|
|
||||||
|
// Prüfe auf Bereichsoperator (..) oder Dezimalpunkt (.)
|
||||||
|
if (Peek() == '.') {
|
||||||
|
if (Peek2() == '.') {
|
||||||
|
// Es ist ein Bereichsoperator (..)
|
||||||
|
// Don't consume the dots, just return the number
|
||||||
|
return new Token(TokType.INT, sb.ToString(), startLine);
|
||||||
|
} else {
|
||||||
|
// Es ist ein Dezimalpunkt
|
||||||
|
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.LBRACKET,"[",tokenLine);
|
||||||
|
if (c == ']') return new Token(TokType.RBRACKET,"]",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);
|
||||||
|
if (c == '.') {
|
||||||
|
if (Peek() == '.') {
|
||||||
|
Next();
|
||||||
|
return new Token(TokType.DOTS,"..",tokenLine);
|
||||||
|
}
|
||||||
|
// Put back the dot for floating point numbers
|
||||||
|
i--;
|
||||||
|
return NextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
AddError($"Unexpected character '{c}'");
|
||||||
|
return new Token(TokType.EOF,"",tokenLine); // Skip invalid character
|
||||||
|
}
|
||||||
|
}
|
||||||
438
STCompiler.Compiler/Parser.cs
Normal file
438
STCompiler.Compiler/Parser.cs
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
namespace STCompiler.Compiler;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using STCompiler.Common;
|
||||||
|
|
||||||
|
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 Dictionary<string,ArrayType> arrayTypes = new(); // Add dictionary for array types
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
VarType? ParseType(string varName) {
|
||||||
|
if (cur.Type == TokType.ARRAY) {
|
||||||
|
Next(); // Skip ARRAY
|
||||||
|
if (!Expect(TokType.LBRACKET)) return null;
|
||||||
|
var start = ParseExpr();
|
||||||
|
if (start == null || start is not IntExpr startInt) {
|
||||||
|
AddError("Array start index must be an integer constant");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!Expect(TokType.DOTS)) return null;
|
||||||
|
var end = ParseExpr();
|
||||||
|
if (end == null || end is not IntExpr endInt) {
|
||||||
|
AddError("Array end index must be an integer constant");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!Expect(TokType.RBRACKET)) return null;
|
||||||
|
if (!Expect(TokType.OF)) return null;
|
||||||
|
if (cur.Type != TokType.IDENT) {
|
||||||
|
AddError("Expected element type name after OF");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
string elemTypeName = cur.Text.ToLowerInvariant();
|
||||||
|
Next();
|
||||||
|
|
||||||
|
VarType? elementType = elemTypeName switch {
|
||||||
|
"bool" => VarType.BOOL,
|
||||||
|
"byte" => VarType.BYTE,
|
||||||
|
"word" => VarType.WORD,
|
||||||
|
"dword" => VarType.DWORD,
|
||||||
|
"lword" => VarType.LWORD,
|
||||||
|
"sint" => VarType.SINT,
|
||||||
|
"int" => VarType.INT,
|
||||||
|
"dint" => VarType.DINT,
|
||||||
|
"lint" => VarType.LINT,
|
||||||
|
"usint" => VarType.USINT,
|
||||||
|
"uint" => VarType.UINT,
|
||||||
|
"udint" => VarType.UDINT,
|
||||||
|
"ulint" => VarType.ULINT,
|
||||||
|
"real" => VarType.REAL,
|
||||||
|
"lreal" => VarType.LREAL,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (elementType == null) {
|
||||||
|
AddError($"Unknown element type '{elemTypeName}'");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayTypes[varName] = new ArrayType(elementType.Value, (int)startInt.Value, (int)endInt.Value);
|
||||||
|
return VarType.ARRAY;
|
||||||
|
}
|
||||||
|
else if (cur.Type == TokType.IDENT) {
|
||||||
|
string typeName = cur.Text.ToLowerInvariant();
|
||||||
|
Next();
|
||||||
|
|
||||||
|
return typeName switch {
|
||||||
|
"bool" => VarType.BOOL,
|
||||||
|
"byte" => VarType.BYTE,
|
||||||
|
"word" => VarType.WORD,
|
||||||
|
"dword" => VarType.DWORD,
|
||||||
|
"lword" => VarType.LWORD,
|
||||||
|
"sint" => VarType.SINT,
|
||||||
|
"int" => VarType.INT,
|
||||||
|
"dint" => VarType.DINT,
|
||||||
|
"lint" => VarType.LINT,
|
||||||
|
"usint" => VarType.USINT,
|
||||||
|
"uint" => VarType.UINT,
|
||||||
|
"udint" => VarType.UDINT,
|
||||||
|
"ulint" => VarType.ULINT,
|
||||||
|
"real" => VarType.REAL,
|
||||||
|
"lreal" => VarType.LREAL,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
AddError("Expected type name or ARRAY");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
var vt = ParseType(name);
|
||||||
|
if (vt == null) {
|
||||||
|
AddError($"Invalid type for variable '{name}'");
|
||||||
|
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() {
|
||||||
|
string target = cur.Text.ToUpperInvariant();
|
||||||
|
Next(); // consume identifier
|
||||||
|
|
||||||
|
// Check if this is an array assignment
|
||||||
|
Expr? index = null;
|
||||||
|
if (cur.Type == TokType.LBRACKET) {
|
||||||
|
Symbol? sym;
|
||||||
|
if (!syms.TryGetValue(target, out sym)) {
|
||||||
|
AddError($"Undeclared variable '{target}'");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (sym.Type != VarType.ARRAY) {
|
||||||
|
AddError($"Cannot use array indexing on non-array variable '{target}'");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Next(); // consume [
|
||||||
|
index = ParseExpr();
|
||||||
|
if (index == null) return null;
|
||||||
|
if (!Expect(TokType.RBRACKET)) return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Index=index, 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;
|
||||||
|
// Support unary + and -
|
||||||
|
if (cur.Type == TokType.PLUS || cur.Type == TokType.MINUS) {
|
||||||
|
var sign = cur.Type;
|
||||||
|
Next();
|
||||||
|
var p = ParsePrimary();
|
||||||
|
if (p == null) return null;
|
||||||
|
Expr zero;
|
||||||
|
if (p.Type == VarType.REAL || p.Type == VarType.LREAL) {
|
||||||
|
zero = new RealExpr(0.0, p.Type);
|
||||||
|
} else {
|
||||||
|
zero = new IntExpr(0, p.Type);
|
||||||
|
}
|
||||||
|
var op = sign == TokType.PLUS ? TokType.PLUS : TokType.MINUS;
|
||||||
|
return new BinaryExpr(zero, op, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for array access
|
||||||
|
if (cur.Type == TokType.LBRACKET) {
|
||||||
|
if (sym.Type != VarType.ARRAY) {
|
||||||
|
AddError($"Cannot index non-array variable '{n}'");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Next(); // consume [
|
||||||
|
var idx = ParseExpr();
|
||||||
|
if (idx == null) return null;
|
||||||
|
|
||||||
|
if (!Expect(TokType.RBRACKET)) return null;
|
||||||
|
|
||||||
|
ArrayType? arrayType;
|
||||||
|
if (!arrayTypes.TryGetValue(n, out arrayType)) {
|
||||||
|
AddError($"Internal error: Array type information missing for '{n}'");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayAccessExpr(n, idx, arrayType.ElementType);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayType? GetArrayType(string name) {
|
||||||
|
ArrayType? type;
|
||||||
|
return arrayTypes.TryGetValue(name, out type) ? type : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,6 +33,10 @@ class Program {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
var emitter = new BytecodeEmitter();
|
var emitter = new BytecodeEmitter();
|
||||||
|
// Register array types from parser
|
||||||
|
foreach (var (name, arrayType) in parser.arrayTypes) {
|
||||||
|
emitter.RegisterArrayType(name, arrayType);
|
||||||
|
}
|
||||||
emitter.Compile(prog);
|
emitter.Compile(prog);
|
||||||
File.WriteAllBytes(args[1], emitter.BuildBinary());
|
File.WriteAllBytes(args[1], emitter.BuildBinary());
|
||||||
Console.WriteLine($"Wrote {args[1]}: consts={emitter.ConstantsCount}, vars={emitter.VarCount}, code={emitter.CodeLength}");
|
Console.WriteLine($"Wrote {args[1]}: consts={emitter.ConstantsCount}, vars={emitter.VarCount}, code={emitter.CodeLength}");
|
||||||
@ -44,601 +48,25 @@ class Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === AST ===
|
// Parser, Lexer und AST wurden ausgelagert nach Parser.cs, Lexer.cs und Ast.cs
|
||||||
public enum VarType {
|
// Die Implementierungen dort werden vom Entry-Point genutzt.
|
||||||
// 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 Peek2()=> i+1<src.Length?src[i+1]:'\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);
|
|
||||||
|
|
||||||
// Skip line comments starting with '//'
|
|
||||||
if (Peek() == '/' && Peek2() == '/') {
|
|
||||||
// consume '//'
|
|
||||||
Next(); Next();
|
|
||||||
// skip until end of line or EOF
|
|
||||||
while (Peek() != '\0' && Peek() != '\n') Next();
|
|
||||||
// consume newline if present
|
|
||||||
if (Peek() == '\n') Next();
|
|
||||||
// restart tokenization after the comment
|
|
||||||
return NextToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
// Support unary + and -
|
|
||||||
if (cur.Type == TokType.PLUS || cur.Type == TokType.MINUS) {
|
|
||||||
var sign = cur.Type;
|
|
||||||
Next();
|
|
||||||
var p = ParsePrimary();
|
|
||||||
if (p == null) return null;
|
|
||||||
Expr zero;
|
|
||||||
if (p.Type == VarType.REAL || p.Type == VarType.LREAL) {
|
|
||||||
zero = new RealExpr(0.0, p.Type);
|
|
||||||
} else {
|
|
||||||
zero = new IntExpr(0, p.Type);
|
|
||||||
}
|
|
||||||
var op = sign == TokType.PLUS ? TokType.PLUS : TokType.MINUS;
|
|
||||||
return new BinaryExpr(zero, op, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ===
|
// === BYTECODE ===
|
||||||
public class BytecodeEmitter {
|
public class BytecodeEmitter {
|
||||||
List<object> consts = new(); // Kann nun int, long, float oder double speichern
|
List<object> consts = new(); // Kann nun int, long, float oder double speichern
|
||||||
Dictionary<string,Symbol> syms = new();
|
Dictionary<string,Symbol> syms = new();
|
||||||
List<byte> code = new();
|
List<byte> code = new();
|
||||||
|
Dictionary<string,ArrayType> arrayTypes = new();
|
||||||
|
|
||||||
class Symbol {
|
class Symbol {
|
||||||
required public string Name;
|
required public string Name;
|
||||||
public VarType Type;
|
public VarType Type;
|
||||||
public int Index;
|
public int Index;
|
||||||
|
public ArrayType? ArrayInfo; // Null for non-array types
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterArrayType(string name, ArrayType type) {
|
||||||
|
arrayTypes[name] = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ConstantsCount => consts.Count;
|
public int ConstantsCount => consts.Count;
|
||||||
@ -647,8 +75,19 @@ public class BytecodeEmitter {
|
|||||||
|
|
||||||
public void Compile(ProgramNode p){
|
public void Compile(ProgramNode p){
|
||||||
int idx=0;
|
int idx=0;
|
||||||
foreach(var v in p.Vars)
|
foreach(var v in p.Vars) {
|
||||||
syms[v.Name]=new Symbol{Name=v.Name,Type=v.Type,Index=idx++};
|
var symbol = new Symbol{Name=v.Name, Type=v.Type, Index=idx};
|
||||||
|
if (v.Type == VarType.ARRAY) {
|
||||||
|
var arrayInfo = arrayTypes[v.Name];
|
||||||
|
symbol.ArrayInfo = arrayInfo;
|
||||||
|
// For arrays, increment the index by array size
|
||||||
|
idx += arrayInfo.Length;
|
||||||
|
} else {
|
||||||
|
// For regular variables, increment by 1
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
syms[v.Name] = symbol;
|
||||||
|
}
|
||||||
|
|
||||||
foreach(var v in p.Vars)
|
foreach(var v in p.Vars)
|
||||||
if(v.Init!=null){EmitExpr(v.Init);EmitByte(Bytecode.OpCodes.STORE_VAR);EmitU16((ushort)syms[v.Name].Index);}
|
if(v.Init!=null){EmitExpr(v.Init);EmitByte(Bytecode.OpCodes.STORE_VAR);EmitU16((ushort)syms[v.Name].Index);}
|
||||||
@ -731,8 +170,43 @@ public class BytecodeEmitter {
|
|||||||
if (!syms.TryGetValue(a.Target, out symbol)) {
|
if (!syms.TryGetValue(a.Target, out symbol)) {
|
||||||
throw new Exception($"Undeclared variable '{a.Target}'");
|
throw new Exception($"Undeclared variable '{a.Target}'");
|
||||||
}
|
}
|
||||||
EmitExpr(a.Expr);
|
|
||||||
EmitByte(Bytecode.OpCodes.STORE_VAR); EmitU16((ushort)symbol.Index);
|
// If this is an array assignment, we need to evaluate the index and adjust the target address
|
||||||
|
if (symbol.Type == VarType.ARRAY && a.Index != null) {
|
||||||
|
// Evaluate array index
|
||||||
|
EmitExpr(a.Index);
|
||||||
|
|
||||||
|
// Bounds check
|
||||||
|
if (symbol.ArrayInfo == null)
|
||||||
|
throw new Exception($"Internal error: Array info missing for '{a.Target}'");
|
||||||
|
|
||||||
|
EmitByte(Bytecode.OpCodes.ARRAY_BOUNDS_CHECK);
|
||||||
|
EmitU16((ushort)symbol.ArrayInfo.Start);
|
||||||
|
EmitU16((ushort)symbol.ArrayInfo.End);
|
||||||
|
|
||||||
|
// Convert to zero-based index
|
||||||
|
if (symbol.ArrayInfo.Start != 0) {
|
||||||
|
EmitByte(Bytecode.OpCodes.PUSH_CONST);
|
||||||
|
EmitU16((ushort)AddConst(symbol.ArrayInfo.Start));
|
||||||
|
EmitByte(Bytecode.OpCodes.SUB_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base address
|
||||||
|
EmitByte(Bytecode.OpCodes.PUSH_CONST);
|
||||||
|
EmitU16((ushort)symbol.Index);
|
||||||
|
EmitByte(Bytecode.OpCodes.ADD_INT);
|
||||||
|
|
||||||
|
// Now evaluate and store the value
|
||||||
|
EmitExpr(a.Expr);
|
||||||
|
EmitByte(Bytecode.OpCodes.STORE_VAR);
|
||||||
|
EmitU16(0); // Index is already on stack
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Regular variable assignment
|
||||||
|
EmitExpr(a.Expr);
|
||||||
|
EmitByte(Bytecode.OpCodes.STORE_VAR);
|
||||||
|
EmitU16((ushort)symbol.Index);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IfStmt iff:
|
case IfStmt iff:
|
||||||
@ -808,14 +282,12 @@ public class BytecodeEmitter {
|
|||||||
int ci = AddConst(ie.Value);
|
int ci = AddConst(ie.Value);
|
||||||
EmitByte(Bytecode.OpCodes.PUSH_CONST); // PUSH_CONST
|
EmitByte(Bytecode.OpCodes.PUSH_CONST); // PUSH_CONST
|
||||||
EmitU16((ushort)ci);
|
EmitU16((ushort)ci);
|
||||||
// removed emitting the type byte into code stream; type is stored with constant table
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RealExpr re:
|
case RealExpr re:
|
||||||
int cri = AddConst(re.Value);
|
int cri = AddConst(re.Value);
|
||||||
EmitByte(Bytecode.OpCodes.PUSH_REAL_CONST); // PUSH_REAL_CONST
|
EmitByte(Bytecode.OpCodes.PUSH_REAL_CONST); // PUSH_REAL_CONST
|
||||||
EmitU16((ushort)cri);
|
EmitU16((ushort)cri);
|
||||||
// removed emitting the type byte into code stream; type is stored with constant table
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VarExpr ve:
|
case VarExpr ve:
|
||||||
@ -826,6 +298,42 @@ public class BytecodeEmitter {
|
|||||||
EmitByte(Bytecode.OpCodes.LOAD_VAR); // LOAD_VAR
|
EmitByte(Bytecode.OpCodes.LOAD_VAR); // LOAD_VAR
|
||||||
EmitU16((ushort)symbol.Index);
|
EmitU16((ushort)symbol.Index);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ArrayAccessExpr ae:
|
||||||
|
Symbol? arraySymbol = null;
|
||||||
|
if (!syms.TryGetValue(ae.ArrayName, out arraySymbol)) {
|
||||||
|
throw new Exception($"Undeclared array '{ae.ArrayName}'");
|
||||||
|
}
|
||||||
|
if (arraySymbol.Type != VarType.ARRAY || arraySymbol.ArrayInfo == null) {
|
||||||
|
throw new Exception($"Variable '{ae.ArrayName}' is not an array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit the index expression first
|
||||||
|
EmitExpr(ae.Index);
|
||||||
|
|
||||||
|
// Emit array bounds check
|
||||||
|
int start = arraySymbol.ArrayInfo.Start;
|
||||||
|
int end = arraySymbol.ArrayInfo.End;
|
||||||
|
EmitByte(Bytecode.OpCodes.ARRAY_BOUNDS_CHECK);
|
||||||
|
EmitU16((ushort)start);
|
||||||
|
EmitU16((ushort)end);
|
||||||
|
|
||||||
|
// Convert index to zero-based
|
||||||
|
if (start != 0) {
|
||||||
|
EmitByte(Bytecode.OpCodes.PUSH_CONST);
|
||||||
|
EmitU16((ushort)AddConst(start));
|
||||||
|
EmitByte(Bytecode.OpCodes.SUB_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load array base address
|
||||||
|
EmitByte(Bytecode.OpCodes.PUSH_CONST);
|
||||||
|
EmitU16((ushort)arraySymbol.Index);
|
||||||
|
EmitByte(Bytecode.OpCodes.ADD_INT);
|
||||||
|
|
||||||
|
// Load array element
|
||||||
|
EmitByte(Bytecode.OpCodes.LOAD_VAR);
|
||||||
|
EmitU16(0); // Index is already on stack
|
||||||
|
break;
|
||||||
|
|
||||||
case BinaryExpr be:
|
case BinaryExpr be:
|
||||||
EmitExpr(be.L);
|
EmitExpr(be.L);
|
||||||
@ -835,14 +343,6 @@ public class BytecodeEmitter {
|
|||||||
if (!opCodes.ContainsKey(key)) {
|
if (!opCodes.ContainsKey(key)) {
|
||||||
throw new Exception($"Unknown operator '{be.Op}' for type {be.Type}");
|
throw new Exception($"Unknown operator '{be.Op}' for type {be.Type}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wenn nötig, Typenkonvertierung vor der Operation
|
|
||||||
// Conversion opcodes removed - runtime will handle type differences using constant type markers and operation opcodes
|
|
||||||
// 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]);
|
EmitByte(opCodes[key]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -890,11 +390,29 @@ public class BytecodeEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate total variable space needed (accounting for arrays)
|
||||||
|
int totalVarSpace = 0;
|
||||||
|
foreach(var kv in syms) {
|
||||||
|
if (kv.Value.Type == VarType.ARRAY && kv.Value.ArrayInfo != null) {
|
||||||
|
totalVarSpace = Math.Max(totalVarSpace, kv.Value.Index + kv.Value.ArrayInfo.Length);
|
||||||
|
} else {
|
||||||
|
totalVarSpace = Math.Max(totalVarSpace, kv.Value.Index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Variablen
|
// Variablen
|
||||||
w.Write((ushort)syms.Count);
|
w.Write((ushort)totalVarSpace);
|
||||||
var types = new byte[syms.Count];
|
var types = new byte[totalVarSpace];
|
||||||
foreach(var kv in syms)
|
foreach(var kv in syms) {
|
||||||
types[kv.Value.Index] = (byte)kv.Value.Type;
|
if (kv.Value.Type == VarType.ARRAY && kv.Value.ArrayInfo != null) {
|
||||||
|
// Fill array elements with their element type
|
||||||
|
for (int i = 0; i < kv.Value.ArrayInfo.Length; i++) {
|
||||||
|
types[kv.Value.Index + i] = (byte)kv.Value.ArrayInfo.ElementType;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
types[kv.Value.Index] = (byte)kv.Value.Type;
|
||||||
|
}
|
||||||
|
}
|
||||||
foreach(var b in types)
|
foreach(var b in types)
|
||||||
w.Write(b);
|
w.Write(b);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -8,8 +8,16 @@ VAR
|
|||||||
x REAL := 1.5;
|
x REAL := 1.5;
|
||||||
y LREAL := 2.5;
|
y LREAL := 2.5;
|
||||||
cnt INT := 0;
|
cnt INT := 0;
|
||||||
|
// numbers ARRAY [1..10] OF INT;
|
||||||
|
// matrix ARRAY [0..3] OF REAL;
|
||||||
END_VAR
|
END_VAR
|
||||||
|
|
||||||
|
|
||||||
|
// numbers[1] := 42;
|
||||||
|
// numbers[2] := numbers[1] * 2;
|
||||||
|
// matrix[0] := 3.14;
|
||||||
|
|
||||||
|
|
||||||
// Einfacher If-Test
|
// Einfacher If-Test
|
||||||
IF a = 0 THEN
|
IF a = 0 THEN
|
||||||
a := 1;
|
a := 1;
|
||||||
|
|||||||
@ -13,10 +13,10 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Compiler")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Compiler")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9864c1965fa3f1dc2950b7afd816891e75f6857d")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3cb63739152cd26ec43993953c7b93d4ab9bcce7")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Compiler")]
|
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Compiler")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Compiler")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Compiler")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
// Generated by the MSBuild WriteCodeFragment class.
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
f6dd1650f122ef522a0e29b4e2791479bb04684921f3ea39b7363b57cd76e656
|
cc701f58f98db810dc498092a84a16f3679a63cf316500de1b157491a545da80
|
||||||
|
|||||||
Binary file not shown.
@ -1 +1 @@
|
|||||||
721350078d8e27a83d96cc6d0dfa91b6ac559a5f81a22d9c78bae64825ed0c98
|
ef34cbbbf6d7558fb23c1b07b1bdfb963a1168dbf40a10e84ba2675b09fc9880
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -46,10 +46,29 @@ class Program {
|
|||||||
byte tb = r.ReadByte();
|
byte tb = r.ReadByte();
|
||||||
var vt = (VarType)tb;
|
var vt = (VarType)tb;
|
||||||
varTypes[i] = vt;
|
varTypes[i] = vt;
|
||||||
if (Enum.IsDefined(typeof(VarType), vt))
|
|
||||||
|
if (vt == VarType.ARRAY) {
|
||||||
|
// Look ahead to find array length and element type
|
||||||
|
int arrayStart = i;
|
||||||
|
int length = 1;
|
||||||
|
VarType elementType = 0; // Initialize to INT as default
|
||||||
|
|
||||||
|
if (i + 1 < nVars) {
|
||||||
|
elementType = (VarType)varTypes[i + 1];
|
||||||
|
while (i + length < nVars && varTypes[i + length] == varTypes[i + 1]) {
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($" Var[{i}] type = ARRAY[{length}] OF {elementType}");
|
||||||
|
i += length - 1; // Skip array elements
|
||||||
|
}
|
||||||
|
else if (Enum.IsDefined(typeof(VarType), vt)) {
|
||||||
Console.WriteLine($" Var[{i}] type = {vt}");
|
Console.WriteLine($" Var[{i}] type = {vt}");
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
Console.WriteLine($" Var[{i}] type = {tb} (unknown)");
|
Console.WriteLine($" Var[{i}] type = {tb} (unknown)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort codeLen = r.ReadUInt16();
|
ushort codeLen = r.ReadUInt16();
|
||||||
@ -71,6 +90,12 @@ class Program {
|
|||||||
case Bytecode.OpCodes.JZ: { ushort target = ReadU16(code, ref ip); Console.WriteLine($"JZ addr={target:0000}"); break; }
|
case Bytecode.OpCodes.JZ: { ushort target = ReadU16(code, ref ip); Console.WriteLine($"JZ addr={target:0000}"); break; }
|
||||||
case Bytecode.OpCodes.JMP: { ushort target = ReadU16(code, ref ip); Console.WriteLine($"JMP addr={target:0000}"); break; }
|
case Bytecode.OpCodes.JMP: { ushort target = ReadU16(code, ref ip); Console.WriteLine($"JMP addr={target:0000}"); break; }
|
||||||
case Bytecode.OpCodes.HALT: Console.WriteLine("HALT"); break;
|
case Bytecode.OpCodes.HALT: Console.WriteLine("HALT"); break;
|
||||||
|
case Bytecode.OpCodes.ARRAY_BOUNDS_CHECK: {
|
||||||
|
Console.WriteLine("ARRAY_BOUNDS_CHECK");
|
||||||
|
// Skip the next byte as it's part of the array bounds check instruction
|
||||||
|
if (ip < code.Length) ip++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: Console.WriteLine($"{Bytecode.OpName(op)}"); break;
|
default: Console.WriteLine($"{Bytecode.OpName(op)}"); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -13,10 +13,10 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Disassembler")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Disassembler")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9864c1965fa3f1dc2950b7afd816891e75f6857d")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3cb63739152cd26ec43993953c7b93d4ab9bcce7")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Disassembler")]
|
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Disassembler")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Disassembler")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Disassembler")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
// Generated by the MSBuild WriteCodeFragment class.
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
1d7c20312d485399a6349304512afd19a40ff18597fb7dff3871190dd81cd69c
|
2175f71304910ca37c6a64a54785d953c58ecd76944c17fb6fe56c6a95a4d3e4
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -54,6 +54,42 @@ class Program {
|
|||||||
Console.WriteLine($"CodeLen: {codeLen} bytes");
|
Console.WriteLine($"CodeLen: {codeLen} bytes");
|
||||||
var code = r.ReadBytes(codeLen);
|
var code = r.ReadBytes(codeLen);
|
||||||
|
|
||||||
|
// Initialize arrays for element type tracking
|
||||||
|
var arrayRanges = new Dictionary<int, (int start, int length, VarType elementType)>();
|
||||||
|
Console.WriteLine("\nAnalyzing variable types:");
|
||||||
|
for (int i = 0; i < nVars; i++) {
|
||||||
|
Console.WriteLine($" [{i}] = {(VarType)varTypes[i]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("\nDetecting arrays:");
|
||||||
|
for (int i = 0; i < nVars; i++) {
|
||||||
|
if (varTypes[i] == (byte)VarType.ARRAY && i + 2 <= nVars) {
|
||||||
|
// An array consists of:
|
||||||
|
// - ARRAY marker at position i
|
||||||
|
// - ElementType at position i+1
|
||||||
|
// - Array elements starting at position i+2
|
||||||
|
int arrayStart = i;
|
||||||
|
VarType elementType = (VarType)varTypes[i + 1];
|
||||||
|
int startOfElements = i + 2;
|
||||||
|
|
||||||
|
// Count elements until we hit either end of vars or a different type
|
||||||
|
int length = 0;
|
||||||
|
for (int j = startOfElements; j < nVars; j++) {
|
||||||
|
if (varTypes[j] != (byte)elementType) break;
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayRanges[arrayStart] = (startOfElements, length, elementType);
|
||||||
|
Console.WriteLine($"Found array at index {arrayStart}:");
|
||||||
|
Console.WriteLine($" - Element type: {elementType}");
|
||||||
|
Console.WriteLine($" - Elements start at: {startOfElements}");
|
||||||
|
Console.WriteLine($" - Length: {length}");
|
||||||
|
|
||||||
|
// Skip past the array definition (ARRAY marker + element type)
|
||||||
|
i = startOfElements - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var stack = new Stack<object>();
|
var stack = new Stack<object>();
|
||||||
var vars = new object[nVars];
|
var vars = new object[nVars];
|
||||||
int ip = 0;
|
int ip = 0;
|
||||||
@ -61,7 +97,7 @@ class Program {
|
|||||||
while (ip < code.Length) {
|
while (ip < code.Length) {
|
||||||
int addr = ip;
|
int addr = ip;
|
||||||
byte op = code[ip++];
|
byte op = code[ip++];
|
||||||
Console.Write($"{addr:0000}: 0x{op:X2} {Bytecode.OpName(op)} ");
|
Console.Write($"{addr:0000}: 0x{op:X2} ");
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case Bytecode.OpCodes.NOP: Console.WriteLine("NOP"); break;
|
case Bytecode.OpCodes.NOP: Console.WriteLine("NOP"); break;
|
||||||
case Bytecode.OpCodes.PUSH_CONST: {
|
case Bytecode.OpCodes.PUSH_CONST: {
|
||||||
@ -76,12 +112,103 @@ class Program {
|
|||||||
Console.WriteLine($"PUSH_REAL_CONST {ci} ({consts[ci]})");
|
Console.WriteLine($"PUSH_REAL_CONST {ci} ({consts[ci]})");
|
||||||
stack.Push(consts[ci]);
|
stack.Push(consts[ci]);
|
||||||
break; }
|
break; }
|
||||||
case Bytecode.OpCodes.LOAD_VAR: { ushort vi = (ushort)(code[ip++] | (code[ip++] << 8)); Console.WriteLine($"LOAD_VAR {vi}"); stack.Push(vars[vi]); break; }
|
case Bytecode.OpCodes.LOAD_VAR: {
|
||||||
case Bytecode.OpCodes.STORE_VAR: { ushort vi = (ushort)(code[ip++] | (code[ip++] << 8)); Console.WriteLine($"STORE_VAR {vi}"); vars[vi] = stack.Pop(); break; }
|
ushort vi = (ushort)(code[ip++] | (code[ip++] << 8));
|
||||||
case Bytecode.OpCodes.JZ: { ushort target = (ushort)(code[ip++] | (code[ip++] << 8)); Console.WriteLine($"JZ addr={target}"); var cond = stack.Pop(); bool isFalse = cond is int ci ? ci == 0 : cond is long cl ? cl == 0L : cond is double cd ? cd == 0.0 : cond == null; if (isFalse) ip = target; break; }
|
Console.WriteLine($"LOAD_VAR {vi}");
|
||||||
case Bytecode.OpCodes.JMP: { ushort target = (ushort)(code[ip++] | (code[ip++] << 8)); Console.WriteLine($"JMP addr={target}"); ip = target; break; }
|
|
||||||
case Bytecode.OpCodes.HALT: Console.WriteLine("HALT"); ip = code.Length; break;
|
// Check if the variable index is within bounds
|
||||||
default: Console.WriteLine();
|
if (vi >= vars.Length) {
|
||||||
|
throw new Exception($"Variable index {vi} is out of bounds (max: {vars.Length - 1})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arrayRanges.ContainsKey(vi)) {
|
||||||
|
var (start, arrayLength, arrayElementType) = arrayRanges[vi];
|
||||||
|
var arrayData = new object[arrayLength];
|
||||||
|
Array.Copy(vars, start, arrayData, 0, arrayLength);
|
||||||
|
stack.Push(arrayData);
|
||||||
|
} else {
|
||||||
|
stack.Push(vars[vi]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Bytecode.OpCodes.STORE_VAR: {
|
||||||
|
ushort vi = (ushort)(code[ip++] | (code[ip++] << 8));
|
||||||
|
Console.WriteLine($"STORE_VAR {vi}");
|
||||||
|
|
||||||
|
// Check if the variable index is within bounds
|
||||||
|
if (vi >= vars.Length) {
|
||||||
|
throw new Exception($"Variable index {vi} is out of bounds (max: {vars.Length - 1})");
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = stack.Pop();
|
||||||
|
if (arrayRanges.ContainsKey(vi)) {
|
||||||
|
var (start, arrayLength, arrayElementType) = arrayRanges[vi];
|
||||||
|
if (value is object[] arr) {
|
||||||
|
Array.Copy(arr, 0, vars, start, Math.Min(arr.Length, arrayLength));
|
||||||
|
} else {
|
||||||
|
Console.WriteLine($"Warning: Attempting to store non-array value in array variable at index {vi}");
|
||||||
|
vars[vi] = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vars[vi] = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Bytecode.OpCodes.JZ: { ushort target = (ushort)(code[ip++] | (code[ip++] << 8)); Console.WriteLine($"JZ addr={target:0000}"); var cond = stack.Pop(); bool isFalse = cond is int ci ? ci == 0 : cond is long cl ? cl == 0L : cond is double cd ? cd == 0.0 : cond == null; if (isFalse) ip = target; break; }
|
||||||
|
case Bytecode.OpCodes.JMP: {
|
||||||
|
ushort target = (ushort)(code[ip++] | (code[ip++] << 8));
|
||||||
|
Console.WriteLine($"JMP addr={target:0000}");
|
||||||
|
ip = target;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Bytecode.OpCodes.HALT:
|
||||||
|
Console.WriteLine("HALT");
|
||||||
|
ip = code.Length;
|
||||||
|
break;
|
||||||
|
case Bytecode.OpCodes.ARRAY_BOUNDS_CHECK:
|
||||||
|
// Extract the array index from the instruction
|
||||||
|
byte arrayIndex = 0;
|
||||||
|
if (ip < code.Length) {
|
||||||
|
arrayIndex = code[ip];
|
||||||
|
ip++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this variable is actually an array
|
||||||
|
if (!arrayRanges.ContainsKey(arrayIndex)) {
|
||||||
|
Console.WriteLine($"Warning: ARRAY_BOUNDS_CHECK instruction for non-array variable {arrayIndex}");
|
||||||
|
continue; // Skip this instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"ARRAY_BOUNDS_CHECK for array at index {arrayIndex}");
|
||||||
|
|
||||||
|
if (stack.Count < 1) {
|
||||||
|
throw new Exception("Stack underflow during array bounds check");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert index value safely
|
||||||
|
var indexObj = stack.Pop();
|
||||||
|
int index = Convert.ToInt32(indexObj);
|
||||||
|
|
||||||
|
// The arrayStart from the instruction should be the array marker directly
|
||||||
|
if (!arrayRanges.TryGetValue(arrayIndex, out var arrayInfo)) {
|
||||||
|
// This should not happen since we checked above
|
||||||
|
throw new Exception($"Array metadata missing for index {arrayIndex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var (elementsStart, length, elementType) = arrayInfo;
|
||||||
|
|
||||||
|
// Adjust for IEC array indexing (1-based)
|
||||||
|
int adjustedIndex = index - 1;
|
||||||
|
if (adjustedIndex < 0 || adjustedIndex >= length) {
|
||||||
|
throw new Exception($"Array index {index} out of bounds [1..{length}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the actual element address
|
||||||
|
stack.Push(elementsStart + adjustedIndex);
|
||||||
|
Console.WriteLine($"Accessing element {index} of array, mapped to variable index {elementsStart + adjustedIndex}");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
;
|
||||||
// fallback handlers
|
// fallback handlers
|
||||||
if (Bytecode.OpName(op).StartsWith("ADD_") || Bytecode.OpName(op).StartsWith("SUB_") || Bytecode.OpName(op).StartsWith("MUL_") || Bytecode.OpName(op).StartsWith("DIV_")) {
|
if (Bytecode.OpName(op).StartsWith("ADD_") || Bytecode.OpName(op).StartsWith("SUB_") || Bytecode.OpName(op).StartsWith("MUL_") || Bytecode.OpName(op).StartsWith("DIV_")) {
|
||||||
dynamic b = stack.Pop(); dynamic a = stack.Pop(); if (Bytecode.OpName(op).StartsWith("ADD_")) stack.Push(a + b); else if (Bytecode.OpName(op).StartsWith("SUB_")) stack.Push(a - b); else if (Bytecode.OpName(op).StartsWith("MUL_")) stack.Push(a * b); else stack.Push(a / b);
|
dynamic b = stack.Pop(); dynamic a = stack.Pop(); if (Bytecode.OpName(op).StartsWith("ADD_")) stack.Push(a + b); else if (Bytecode.OpName(op).StartsWith("SUB_")) stack.Push(a - b); else if (Bytecode.OpName(op).StartsWith("MUL_")) stack.Push(a * b); else stack.Push(a / b);
|
||||||
@ -98,6 +225,47 @@ class Program {
|
|||||||
stack.Push(res ? 1 : 0);
|
stack.Push(res ? 1 : 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Array operations
|
||||||
|
if (op == Bytecode.OpCodes.ARRAY_BOUNDS_CHECK) {
|
||||||
|
// Extract the array index from the instruction
|
||||||
|
byte arrayIndexInner = 0;
|
||||||
|
if (ip < code.Length) {
|
||||||
|
arrayIndexInner = code[ip];
|
||||||
|
ip++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this variable is actually an array
|
||||||
|
if (!arrayRanges.ContainsKey(arrayIndexInner)) {
|
||||||
|
Console.WriteLine($"Warning: ARRAY_BOUNDS_CHECK instruction for non-array variable {arrayIndexInner}");
|
||||||
|
continue; // Skip this instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"ARRAY_BOUNDS_CHECK for array at index {arrayIndexInner}");
|
||||||
|
|
||||||
|
if (stack.Count < 1) {
|
||||||
|
throw new Exception("Stack underflow during array bounds check");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert index value safely
|
||||||
|
var indexObjInner = stack.Pop();
|
||||||
|
int indexInner = Convert.ToInt32(indexObjInner);
|
||||||
|
|
||||||
|
// The arrayStart from the instruction should be the array marker directly
|
||||||
|
if (!arrayRanges.TryGetValue(arrayIndexInner, out var arrayInfoInner)) {
|
||||||
|
// This should not happen since we checked above
|
||||||
|
throw new Exception($"Array metadata missing for index {arrayIndexInner}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var (elementsStartInner, lengthInner, elementTypeInner) = arrayInfoInner;
|
||||||
|
if (indexInner < 0 || indexInner >= lengthInner) {
|
||||||
|
throw new Exception($"Array index {indexInner} out of bounds [0..{lengthInner-1}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the actual element address
|
||||||
|
stack.Push(elementsStartInner + indexInner);
|
||||||
|
Console.WriteLine($"Accessing element {indexInner} of array, mapped to variable index {elementsStartInner + indexInner}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
throw new Exception($"Unknown opcode 0x{op:X2}");
|
throw new Exception($"Unknown opcode 0x{op:X2}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -13,10 +13,10 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Simulator")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("STCompiler.Simulator")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9864c1965fa3f1dc2950b7afd816891e75f6857d")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3cb63739152cd26ec43993953c7b93d4ab9bcce7")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Simulator")]
|
[assembly: System.Reflection.AssemblyProductAttribute("STCompiler.Simulator")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Simulator")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("STCompiler.Simulator")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
// Generated by the MSBuild WriteCodeFragment class.
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
7a1166fec5b2204048691a4c5ed6c88cb952b3b293ec9ef79ad85cd86bbe7e09
|
b05e088189133b131132a72fa879b53ae0f21051a8f25a2e49266f0f500e3142
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user