Phase 1: Sprach-Frontend (Lexer, AST, Parser, Semantik) + Entscheidungen

Frontend:
- Lexer komplett: Typ-Suffixe, Literal-Typisierung (Entscheidung: > 7
  signifikante Stellen -> DOUBLE), Hex/Oktal, Zeilenfortsetzung mit _,
  Strings mit ""-Escape, case-insensitive Keywords (Bibliotheksnamen
  bleiben Bezeichner)
- AST fuer Module/Prozeduren/Anweisungen/Ausdruecke
- Parser: fehlertolerant, zeilenorientiert; Kern-Anweisungssatz inkl.
  Bloecke, ON [LOCAL] ERROR, DEF FN (einzeilig); Datei-E/A als
  Phase-3-Platzhalter
- Semantik: Symboltabellen, implizite Deklaration, DEFtype, OPTION
  EXPLICIT, Arrays, Builtin-Signaturen, Labelpruefung; Hardware-Features
  (PEEK/POKE/...) werden zur Compile-Zeit abgewiesen
- Meilenstein: Testkorpus parst und wird typgeprueft (corpus.rs); 27 Tests

Entscheidungen eingearbeitet:
- Binaries heissen tb (IDE) und tbc (Compiler)
- Dynamische Terminalgroesse statt 80x25 (Minimum 80x25, btop-artiger
  Hinweis darunter); tb-ui::screen mit resize(), Spike angepasst
- Vollstaendigkeits-Leitplanke: 100% Sprache/Stdlib minus deklarierte
  Non-Features; Original-Doku als Guiding Principle; Inventar-Aufgabe
- CURRENCY als i64-Festkomma; ISAM wird implementiert; breite Zeichen
  belegen 2 Zellen; GET/PUT-Strings als UTF-32; Blink als hell simuliert
- LICENSE: MIT

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 09:16:01 +02:00
parent 8002ac2388
commit 333e794540
21 changed files with 3799 additions and 138 deletions

View File

@@ -1,4 +1,247 @@
//! AST-Definitionen: Module, Prozeduren (`SUB`/`FUNCTION`), Anweisungen, Ausdrücke,
//! benutzerdefinierte Typen (`TYPE … END TYPE`), Deklarationen.
//! AST-Definitionen: Module, Prozeduren, Anweisungen, Ausdrücke,
//! Deklarationen und benutzerdefinierte Typen.
// Platzhalter — wird in Phase 1 ausgearbeitet (siehe PLAN.md)
use crate::lexer::Suffix;
use crate::SourcePos;
/// Typangabe in `AS`-Klauseln.
#[derive(Debug, Clone, PartialEq)]
pub enum TypeName {
Integer,
Long,
Single,
Double,
Currency,
Str,
FixedStr(i64),
Udt(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
Neg,
Not,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Pow, Mul, Div, IntDiv, Mod, Add, Sub,
Eq, Ne, Lt, Le, Gt, Ge,
And, Or, Xor, Eqv, Imp,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
IntLit(i16),
LongLit(i32),
SingleLit(f32),
DoubleLit(f64),
CurrencyLit(i64),
StrLit(String),
/// Benannter Zugriff: Variable, Arrayelement, Funktionsaufruf oder
/// Konstante — Auflösung erfolgt in der Semantik.
Name {
name: String,
suffix: Option<Suffix>,
args: Option<Vec<Expr>>,
pos: SourcePos,
},
Unary { op: UnOp, operand: Box<Expr>, pos: SourcePos },
Binary { op: BinOp, lhs: Box<Expr>, rhs: Box<Expr>, pos: SourcePos },
/// Ausgelassenes Argument (`LOCATE , 5`).
Missing,
}
impl Expr {
pub fn pos(&self) -> SourcePos {
match self {
Expr::Name { pos, .. }
| Expr::Unary { pos, .. }
| Expr::Binary { pos, .. } => *pos,
_ => SourcePos::default(),
}
}
}
/// Sprungziel: alphanumerisches Label oder Zeilennummer.
#[derive(Debug, Clone, PartialEq)]
pub enum LabelRef {
Name(String),
Line(u32),
}
#[derive(Debug, Clone, PartialEq)]
pub enum PrintItem {
Expr(Expr),
Comma,
Semicolon,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CaseSpec {
Expr(Expr),
Range(Expr, Expr),
Is(BinOp, Expr),
}
#[derive(Debug, Clone, PartialEq)]
pub struct CaseArm {
/// Leer = `CASE ELSE`.
pub specs: Vec<CaseSpec>,
pub body: Vec<Stmt>,
pub pos: SourcePos,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VarDecl {
pub name: String,
pub suffix: Option<Suffix>,
/// `None` = Skalar; sonst Dimensionen `(untergrenze TO obergrenze)`.
pub dims: Option<Vec<(Option<Expr>, Expr)>>,
pub as_type: Option<TypeName>,
pub pos: SourcePos,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitKind {
For,
Do,
Sub,
Function,
Def,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptionKind {
Explicit,
Base(u8),
}
#[derive(Debug, Clone, PartialEq)]
pub enum OnErrorAction {
Goto(LabelRef),
ResumeNext,
Disable, // GOTO 0
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResumeKind {
Retry, // RESUME [0]
Next,
Label(LabelRef),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub name: String,
pub suffix: Option<Suffix>,
pub array: bool,
pub as_type: Option<TypeName>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcKind {
Sub,
Function,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProcSig {
pub kind: ProcKind,
pub name: String,
pub suffix: Option<Suffix>,
pub params: Vec<Param>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Proc {
pub sig: ProcSig,
pub is_static: bool,
pub body: Vec<Stmt>,
pub pos: SourcePos,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
Label(String),
LineNumber(u32),
Assign { target: Expr, value: Expr, pos: SourcePos },
Print {
file: Option<Expr>,
using: Option<Expr>,
items: Vec<PrintItem>,
pos: SourcePos,
},
Input {
line: bool,
file: Option<Expr>,
keep_cursor: bool,
prompt: Option<(String, bool)>, // (Text, mit Fragezeichen)
vars: Vec<Expr>,
pos: SourcePos,
},
If {
cond: Expr,
then_body: Vec<Stmt>,
elseifs: Vec<(Expr, Vec<Stmt>)>,
else_body: Option<Vec<Stmt>>,
pos: SourcePos,
},
Select { expr: Expr, arms: Vec<CaseArm>, pos: SourcePos },
For {
var: Expr,
from: Expr,
to: Expr,
step: Option<Expr>,
body: Vec<Stmt>,
pos: SourcePos,
},
DoLoop {
pre: Option<(bool, Expr)>, // (ist UNTIL, Bedingung)
post: Option<(bool, Expr)>,
body: Vec<Stmt>,
pos: SourcePos,
},
While { cond: Expr, body: Vec<Stmt>, pos: SourcePos },
Goto { target: LabelRef, pos: SourcePos },
Gosub { target: LabelRef, pos: SourcePos },
OnGoto { expr: Expr, targets: Vec<LabelRef>, gosub: bool, pos: SourcePos },
Return { target: Option<LabelRef>, pos: SourcePos },
End,
StopStmt,
System,
Exit { kind: ExitKind, pos: SourcePos },
Dim { shared: bool, redim: bool, decls: Vec<VarDecl>, pos: SourcePos },
Erase { names: Vec<Expr>, pos: SourcePos },
ConstDecl { items: Vec<(String, Option<Suffix>, Expr)>, pos: SourcePos },
DefType { ty: TypeName, ranges: Vec<(char, char)>, pos: SourcePos },
OptionStmt { kind: OptionKind, pos: SourcePos },
TypeDecl { name: String, fields: Vec<(String, TypeName)>, pos: SourcePos },
Declare { sig: ProcSig, pos: SourcePos },
/// Expliziter oder impliziter Prozedur-/Builtin-Aufruf als Anweisung.
Call { name: String, suffix: Option<Suffix>, args: Vec<Expr>, pos: SourcePos },
OnError { local: bool, action: OnErrorAction, pos: SourcePos },
Resume { kind: ResumeKind, pos: SourcePos },
ErrorStmt { code: Expr, pos: SourcePos },
Data { items: Vec<String>, pos: SourcePos },
ReadStmt { vars: Vec<Expr>, pos: SourcePos },
Restore { target: Option<LabelRef>, pos: SourcePos },
/// Einzeilige `DEF FNname(...) = ausdruck`-Definition.
DefFn {
name: String,
suffix: Option<Suffix>,
params: Vec<Param>,
body: Expr,
pos: SourcePos,
},
/// Geparst, aber erst in Phase 3 implementiert (Datei-E/A u. ä.);
/// die Tokens der Anweisung wurden übersprungen.
NotYetImplemented { keyword: String, pos: SourcePos },
}
#[derive(Debug, Clone, PartialEq)]
pub struct Module {
pub name: String,
pub body: Vec<Stmt>,
pub procs: Vec<Proc>,
}