336 lines
9.9 KiB
C#
336 lines
9.9 KiB
C#
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 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 {
|
|
"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 (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() {
|
|
string target = cur.Text.ToUpperInvariant();
|
|
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;
|
|
}
|
|
}
|
|
}
|