Phase 4: Formularmodell und Objektsprache
This commit is contained in:
@@ -14,7 +14,12 @@ pub struct ParseOutput {
|
||||
}
|
||||
|
||||
pub fn parse(module_name: &str, tokens: &[Token]) -> ParseOutput {
|
||||
let mut p = P { toks: tokens, i: 0, diags: Vec::new(), at_line_start: true };
|
||||
let mut p = P {
|
||||
toks: tokens,
|
||||
i: 0,
|
||||
diags: Vec::new(),
|
||||
at_line_start: true,
|
||||
};
|
||||
let mut body = Vec::new();
|
||||
let mut procs = Vec::new();
|
||||
|
||||
@@ -40,7 +45,11 @@ pub fn parse(module_name: &str, tokens: &[Token]) -> ParseOutput {
|
||||
}
|
||||
|
||||
ParseOutput {
|
||||
module: Module { name: module_name.to_string(), body, procs },
|
||||
module: Module {
|
||||
name: module_name.to_string(),
|
||||
body,
|
||||
procs,
|
||||
},
|
||||
diagnostics: p.diags,
|
||||
}
|
||||
}
|
||||
@@ -85,7 +94,10 @@ impl<'a> P<'a> {
|
||||
}
|
||||
fn err(&mut self, msg: impl Into<String>) {
|
||||
let pos = self.pos();
|
||||
self.diags.push(Diagnostic { pos, message: msg.into() });
|
||||
self.diags.push(Diagnostic {
|
||||
pos,
|
||||
message: msg.into(),
|
||||
});
|
||||
}
|
||||
fn expect_kw(&mut self, kw: Kw, what: &str) -> bool {
|
||||
if self.eat_kw(kw) {
|
||||
@@ -205,11 +217,21 @@ impl<'a> P<'a> {
|
||||
}
|
||||
TokenKind::MetaStatic => {
|
||||
self.advance();
|
||||
Some(Stmt::MetaArrays { static_arrays: true, pos })
|
||||
Some(Stmt::MetaArrays {
|
||||
static_arrays: true,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::MetaDynamic => {
|
||||
self.advance();
|
||||
Some(Stmt::MetaArrays { static_arrays: false, pos })
|
||||
Some(Stmt::MetaArrays {
|
||||
static_arrays: false,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::MetaForm => {
|
||||
self.advance();
|
||||
Some(Stmt::MetaForm { pos })
|
||||
}
|
||||
TokenKind::Kw(Kw::Print) => self.parse_print(pos, false),
|
||||
TokenKind::Kw(Kw::Input) => {
|
||||
@@ -368,8 +390,9 @@ impl<'a> P<'a> {
|
||||
}
|
||||
Some(Stmt::ConstDecl { items, pos })
|
||||
}
|
||||
TokenKind::Kw(k @ (Kw::DefInt | Kw::DefLng | Kw::DefSng
|
||||
| Kw::DefDbl | Kw::DefStr | Kw::DefCur)) => {
|
||||
TokenKind::Kw(
|
||||
k @ (Kw::DefInt | Kw::DefLng | Kw::DefSng | Kw::DefDbl | Kw::DefStr | Kw::DefCur),
|
||||
) => {
|
||||
self.advance();
|
||||
let ty = match k {
|
||||
Kw::DefInt => TypeName::Integer,
|
||||
@@ -382,9 +405,7 @@ impl<'a> P<'a> {
|
||||
let mut ranges = Vec::new();
|
||||
loop {
|
||||
let a = match self.k() {
|
||||
TokenKind::Ident { name, suffix: None }
|
||||
if name.len() == 1 =>
|
||||
{
|
||||
TokenKind::Ident { name, suffix: None } if name.len() == 1 => {
|
||||
self.advance();
|
||||
name.chars().next().unwrap()
|
||||
}
|
||||
@@ -396,9 +417,7 @@ impl<'a> P<'a> {
|
||||
};
|
||||
let b = if self.eat(&TokenKind::Minus) {
|
||||
match self.k() {
|
||||
TokenKind::Ident { name, suffix: None }
|
||||
if name.len() == 1 =>
|
||||
{
|
||||
TokenKind::Ident { name, suffix: None } if name.len() == 1 => {
|
||||
self.advance();
|
||||
name.chars().next().unwrap()
|
||||
}
|
||||
@@ -420,11 +439,12 @@ impl<'a> P<'a> {
|
||||
TokenKind::Kw(Kw::Option) => {
|
||||
self.advance();
|
||||
match self.k() {
|
||||
TokenKind::Ident { name, suffix: None }
|
||||
if name == "EXPLICIT" =>
|
||||
{
|
||||
TokenKind::Ident { name, suffix: None } if name == "EXPLICIT" => {
|
||||
self.advance();
|
||||
Some(Stmt::OptionStmt { kind: OptionKind::Explicit, pos })
|
||||
Some(Stmt::OptionStmt {
|
||||
kind: OptionKind::Explicit,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::Ident { name, suffix: None } if name == "BASE" => {
|
||||
self.advance();
|
||||
@@ -439,7 +459,10 @@ impl<'a> P<'a> {
|
||||
0
|
||||
}
|
||||
};
|
||||
Some(Stmt::OptionStmt { kind: OptionKind::Base(base), pos })
|
||||
Some(Stmt::OptionStmt {
|
||||
kind: OptionKind::Base(base),
|
||||
pos,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
self.err("Expected: BASE or EXPLICIT");
|
||||
@@ -466,7 +489,12 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
Some(Stmt::Call { name, suffix, args, pos })
|
||||
Some(Stmt::Call {
|
||||
name,
|
||||
suffix,
|
||||
args,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
self.err("Expected: identifier");
|
||||
@@ -510,7 +538,11 @@ impl<'a> P<'a> {
|
||||
_ => cur.push(c),
|
||||
}
|
||||
}
|
||||
items.push(if quotiert { cur } else { cur.trim().to_string() });
|
||||
items.push(if quotiert {
|
||||
cur
|
||||
} else {
|
||||
cur.trim().to_string()
|
||||
});
|
||||
Some(Stmt::Data { items, pos })
|
||||
}
|
||||
TokenKind::Kw(Kw::Read) => {
|
||||
@@ -594,7 +626,13 @@ impl<'a> P<'a> {
|
||||
var = Some(self.parse_name_ref()?);
|
||||
}
|
||||
}
|
||||
Some(Stmt::GetPut { put: k == Kw::Put, file, recnum, var, pos })
|
||||
Some(Stmt::GetPut {
|
||||
put: k == Kw::Put,
|
||||
file,
|
||||
recnum,
|
||||
var,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::Kw(k @ (Kw::Lset | Kw::Rset)) => {
|
||||
self.advance();
|
||||
@@ -605,7 +643,12 @@ impl<'a> P<'a> {
|
||||
return None;
|
||||
}
|
||||
let value = self.parse_expr()?;
|
||||
Some(Stmt::LsetRset { rset: k == Kw::Rset, target, value, pos })
|
||||
Some(Stmt::LsetRset {
|
||||
rset: k == Kw::Rset,
|
||||
target,
|
||||
value,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::Kw(Kw::Write) => {
|
||||
self.advance();
|
||||
@@ -636,7 +679,11 @@ impl<'a> P<'a> {
|
||||
self.err("Expected: ,");
|
||||
}
|
||||
let position = self.parse_expr()?;
|
||||
Some(Stmt::SeekStmt { file, position, pos })
|
||||
Some(Stmt::SeekStmt {
|
||||
file,
|
||||
position,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::Kw(Kw::Common) => {
|
||||
self.advance();
|
||||
@@ -654,7 +701,12 @@ impl<'a> P<'a> {
|
||||
}
|
||||
}
|
||||
let decls = self.parse_var_decls();
|
||||
Some(Stmt::CommonDecl { shared, block, decls, pos })
|
||||
Some(Stmt::CommonDecl {
|
||||
shared,
|
||||
block,
|
||||
decls,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::Kw(Kw::Shared) => {
|
||||
self.advance();
|
||||
@@ -681,19 +733,20 @@ impl<'a> P<'a> {
|
||||
}
|
||||
Some(Stmt::ViewPrint { top, bottom, pos })
|
||||
}
|
||||
TokenKind::Ident { ref name, suffix: None }
|
||||
if name == "NAME" && self.k_at(1) != TokenKind::Eq =>
|
||||
{
|
||||
TokenKind::Ident {
|
||||
ref name,
|
||||
suffix: None,
|
||||
} if name == "NAME" && self.k_at(1) != TokenKind::Eq => {
|
||||
self.advance();
|
||||
let old = self.parse_expr()?;
|
||||
self.expect_kw(Kw::As, "AS");
|
||||
let new = self.parse_expr()?;
|
||||
Some(Stmt::NameStmt { old, new, pos })
|
||||
}
|
||||
TokenKind::Ident { ref name, suffix: None }
|
||||
if (name == "LOCK" || name == "UNLOCK")
|
||||
&& self.k_at(1) != TokenKind::Eq =>
|
||||
{
|
||||
TokenKind::Ident {
|
||||
ref name,
|
||||
suffix: None,
|
||||
} if (name == "LOCK" || name == "UNLOCK") && self.k_at(1) != TokenKind::Eq => {
|
||||
let unlock = name == "UNLOCK";
|
||||
self.advance();
|
||||
self.eat(&TokenKind::Hash);
|
||||
@@ -707,7 +760,13 @@ impl<'a> P<'a> {
|
||||
to = Some(self.parse_expr()?);
|
||||
}
|
||||
}
|
||||
Some(Stmt::LockStmt { unlock, file, from, to, pos })
|
||||
Some(Stmt::LockStmt {
|
||||
unlock,
|
||||
file,
|
||||
from,
|
||||
to,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
// ISAM-Anweisungen: `NAME [#]n [, arg …]`. Sie unterscheiden sich
|
||||
// von einem gewöhnlichen impliziten Aufruf nur durch das
|
||||
@@ -716,10 +775,12 @@ impl<'a> P<'a> {
|
||||
// `=` und `(` schließen die Anweisungsdeutung aus: dann ist der
|
||||
// Name eine Variable bzw. ein Array (`DELETE = 1`, `DELETE(2) = 1`).
|
||||
// Keine ISAM-Syntaxform beginnt mit einer Klammer.
|
||||
TokenKind::Ident { ref name, suffix: None }
|
||||
if ist_isam_anweisung(name)
|
||||
&& self.k_at(1) != TokenKind::Eq
|
||||
&& self.k_at(1) != TokenKind::LParen =>
|
||||
TokenKind::Ident {
|
||||
ref name,
|
||||
suffix: None,
|
||||
} if ist_isam_anweisung(name)
|
||||
&& self.k_at(1) != TokenKind::Eq
|
||||
&& self.k_at(1) != TokenKind::LParen =>
|
||||
{
|
||||
self.parse_isam(name.clone(), pos)
|
||||
}
|
||||
@@ -740,11 +801,20 @@ impl<'a> P<'a> {
|
||||
// Es senkt auf die Sentinel-Kennung ab, damit `ROLLBACK` eine
|
||||
// einzige Signatur behält.
|
||||
if name == "ROLLBACK" {
|
||||
if let TokenKind::Ident { name: ref w, suffix: None } = self.k() {
|
||||
if let TokenKind::Ident {
|
||||
name: ref w,
|
||||
suffix: None,
|
||||
} = self.k()
|
||||
{
|
||||
if w == "ALL" {
|
||||
self.advance();
|
||||
let args = vec![Expr::LongLit(ROLLBACK_ALL)];
|
||||
return Some(Stmt::Call { name, suffix: None, args, pos });
|
||||
return Some(Stmt::Call {
|
||||
name,
|
||||
suffix: None,
|
||||
args,
|
||||
pos,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -754,7 +824,12 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
self.parse_arg_list_to_stmt_end()
|
||||
};
|
||||
Some(Stmt::Call { name, suffix: None, args, pos })
|
||||
Some(Stmt::Call {
|
||||
name,
|
||||
suffix: None,
|
||||
args,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_print(&mut self, pos: SourcePos, printer: bool) -> Option<Stmt> {
|
||||
@@ -793,7 +868,13 @@ impl<'a> P<'a> {
|
||||
},
|
||||
}
|
||||
}
|
||||
Some(Stmt::Print { printer, file, using, items, pos })
|
||||
Some(Stmt::Print {
|
||||
printer,
|
||||
file,
|
||||
using,
|
||||
items,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
/// `OPEN` in beiden Syntaxformen (FOR-Klausel und Kurzform).
|
||||
@@ -813,7 +894,13 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Some(Stmt::OpenLegacy { mode: first, number, file, len, pos });
|
||||
return Some(Stmt::OpenLegacy {
|
||||
mode: first,
|
||||
number,
|
||||
file,
|
||||
len,
|
||||
pos,
|
||||
});
|
||||
}
|
||||
let mut mode = None;
|
||||
let mut isam = None;
|
||||
@@ -823,7 +910,10 @@ impl<'a> P<'a> {
|
||||
self.advance();
|
||||
OpenMode::Input
|
||||
}
|
||||
TokenKind::Ident { ref name, suffix: None } => {
|
||||
TokenKind::Ident {
|
||||
ref name,
|
||||
suffix: None,
|
||||
} => {
|
||||
let m = match name.as_str() {
|
||||
"OUTPUT" => OpenMode::Output,
|
||||
"APPEND" => OpenMode::Append,
|
||||
@@ -873,7 +963,11 @@ impl<'a> P<'a> {
|
||||
}
|
||||
}
|
||||
let mut access = None;
|
||||
if let TokenKind::Ident { ref name, suffix: None } = self.k() {
|
||||
if let TokenKind::Ident {
|
||||
ref name,
|
||||
suffix: None,
|
||||
} = self.k()
|
||||
{
|
||||
if name == "ACCESS" {
|
||||
self.advance();
|
||||
let read = self.eat_kw(Kw::Read);
|
||||
@@ -892,7 +986,11 @@ impl<'a> P<'a> {
|
||||
let mut lock = None;
|
||||
if self.eat_kw(Kw::Shared) {
|
||||
lock = Some(LockClause::Shared);
|
||||
} else if let TokenKind::Ident { ref name, suffix: None } = self.k() {
|
||||
} else if let TokenKind::Ident {
|
||||
ref name,
|
||||
suffix: None,
|
||||
} = self.k()
|
||||
{
|
||||
if name == "LOCK" {
|
||||
self.advance();
|
||||
let read = self.eat_kw(Kw::Read);
|
||||
@@ -912,7 +1010,11 @@ impl<'a> P<'a> {
|
||||
self.eat(&TokenKind::Hash);
|
||||
let number = self.parse_expr()?;
|
||||
let mut len = None;
|
||||
if let TokenKind::Ident { ref name, suffix: None } = self.k() {
|
||||
if let TokenKind::Ident {
|
||||
ref name,
|
||||
suffix: None,
|
||||
} = self.k()
|
||||
{
|
||||
if name == "LEN" {
|
||||
self.advance();
|
||||
if !self.eat(&TokenKind::Eq) {
|
||||
@@ -921,7 +1023,16 @@ impl<'a> P<'a> {
|
||||
len = Some(self.parse_expr()?);
|
||||
}
|
||||
}
|
||||
Some(Stmt::Open { file: first, mode, isam, access, lock, number, len, pos })
|
||||
Some(Stmt::Open {
|
||||
file: first,
|
||||
mode,
|
||||
isam,
|
||||
access,
|
||||
lock,
|
||||
number,
|
||||
len,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_input(&mut self, line: bool, pos: SourcePos) -> Option<Stmt> {
|
||||
@@ -936,8 +1047,7 @@ impl<'a> P<'a> {
|
||||
let mut prompt = None;
|
||||
if file.is_none() {
|
||||
if let TokenKind::Str(s) = self.k() {
|
||||
if matches!(self.k_at(1), TokenKind::Semicolon | TokenKind::Comma)
|
||||
{
|
||||
if matches!(self.k_at(1), TokenKind::Semicolon | TokenKind::Comma) {
|
||||
self.advance();
|
||||
let with_question = self.k() == TokenKind::Semicolon;
|
||||
self.advance();
|
||||
@@ -959,7 +1069,14 @@ impl<'a> P<'a> {
|
||||
if vars.is_empty() {
|
||||
self.err("Expected: variable");
|
||||
}
|
||||
Some(Stmt::Input { line, file, keep_cursor, prompt, vars, pos })
|
||||
Some(Stmt::Input {
|
||||
line,
|
||||
file,
|
||||
keep_cursor,
|
||||
prompt,
|
||||
vars,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_if(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||
@@ -991,7 +1108,13 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
self.err("Expected: END IF");
|
||||
}
|
||||
Some(Stmt::If { cond, then_body, elseifs, else_body, pos })
|
||||
Some(Stmt::If {
|
||||
cond,
|
||||
then_body,
|
||||
elseifs,
|
||||
else_body,
|
||||
pos,
|
||||
})
|
||||
} else {
|
||||
// Einzeilig
|
||||
let then_body = if let Some(n) = self.line_number_target() {
|
||||
@@ -1008,7 +1131,13 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(Stmt::If { cond, then_body, elseifs: Vec::new(), else_body, pos })
|
||||
Some(Stmt::If {
|
||||
cond,
|
||||
then_body,
|
||||
elseifs: Vec::new(),
|
||||
else_body,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,7 +1241,14 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
self.err("FOR without NEXT");
|
||||
}
|
||||
Some(Stmt::For { var, from, to, step, body, pos })
|
||||
Some(Stmt::For {
|
||||
var,
|
||||
from,
|
||||
to,
|
||||
step,
|
||||
body,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_do(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||
@@ -1135,7 +1271,12 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
self.err("DO without LOOP");
|
||||
}
|
||||
Some(Stmt::DoLoop { pre, post, body, pos })
|
||||
Some(Stmt::DoLoop {
|
||||
pre,
|
||||
post,
|
||||
body,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_while(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||
@@ -1252,13 +1393,23 @@ impl<'a> P<'a> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(Stmt::OnGoto { expr, targets, gosub, pos })
|
||||
Some(Stmt::OnGoto {
|
||||
expr,
|
||||
targets,
|
||||
gosub,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_dim(&mut self, redim: bool, pos: SourcePos) -> Option<Stmt> {
|
||||
let shared = self.eat_kw(Kw::Shared);
|
||||
let decls = self.parse_var_decls();
|
||||
Some(Stmt::Dim { shared, redim, decls, pos })
|
||||
Some(Stmt::Dim {
|
||||
shared,
|
||||
redim,
|
||||
decls,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
/// Deklarationsliste `name[suffix][(dims)] [AS typ], …` — gemeinsame
|
||||
@@ -1309,7 +1460,13 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
decls.push(VarDecl { name, suffix, dims, as_type, pos: dpos });
|
||||
decls.push(VarDecl {
|
||||
name,
|
||||
suffix,
|
||||
dims,
|
||||
as_type,
|
||||
pos: dpos,
|
||||
});
|
||||
if !self.eat(&TokenKind::Comma) {
|
||||
break;
|
||||
}
|
||||
@@ -1362,7 +1519,11 @@ impl<'a> P<'a> {
|
||||
}
|
||||
TokenKind::Ident { name, suffix: None } => {
|
||||
self.advance();
|
||||
Some(TypeName::Udt(name))
|
||||
Some(match name.as_str() {
|
||||
"FORM" => TypeName::Form,
|
||||
"CONTROL" => TypeName::Control,
|
||||
_ => TypeName::Udt(name),
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
self.err("Expected: type");
|
||||
@@ -1397,7 +1558,10 @@ impl<'a> P<'a> {
|
||||
break;
|
||||
}
|
||||
match self.k() {
|
||||
TokenKind::Ident { name: fname, suffix: None } => {
|
||||
TokenKind::Ident {
|
||||
name: fname,
|
||||
suffix: None,
|
||||
} => {
|
||||
self.advance();
|
||||
if self.expect_kw(Kw::As, "AS") {
|
||||
if let Some(t) = self.parse_type_name() {
|
||||
@@ -1442,7 +1606,10 @@ impl<'a> P<'a> {
|
||||
if !self.eat(&TokenKind::RParen) {
|
||||
loop {
|
||||
match self.k() {
|
||||
TokenKind::Ident { name: pname, suffix: psfx } => {
|
||||
TokenKind::Ident {
|
||||
name: pname,
|
||||
suffix: psfx,
|
||||
} => {
|
||||
self.advance();
|
||||
let array = if self.eat(&TokenKind::LParen) {
|
||||
self.eat(&TokenKind::RParen);
|
||||
@@ -1474,7 +1641,12 @@ impl<'a> P<'a> {
|
||||
self.eat(&TokenKind::RParen);
|
||||
}
|
||||
}
|
||||
Some(ProcSig { kind, name, suffix, params })
|
||||
Some(ProcSig {
|
||||
kind,
|
||||
name,
|
||||
suffix,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_proc(&mut self) -> Option<Proc> {
|
||||
@@ -1495,14 +1667,19 @@ impl<'a> P<'a> {
|
||||
ProcKind::Function => "Expected: END FUNCTION",
|
||||
});
|
||||
}
|
||||
Some(Proc { sig, is_static, body, pos })
|
||||
Some(Proc {
|
||||
sig,
|
||||
is_static,
|
||||
body,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_def_fn(&mut self, pos: SourcePos) -> Option<Stmt> {
|
||||
self.advance(); // DEF
|
||||
// `DEF SEG` ist Segmentadressierung und damit deklariertes
|
||||
// Non-Feature — an der Syntax erkennbar, weil `DEF` sonst nur
|
||||
// `DEF FNname` einleitet.
|
||||
// `DEF SEG` ist Segmentadressierung und damit deklariertes
|
||||
// Non-Feature — an der Syntax erkennbar, weil `DEF` sonst nur
|
||||
// `DEF FNname` einleitet.
|
||||
if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "SEG") {
|
||||
self.err("Feature unavailable");
|
||||
self.sync();
|
||||
@@ -1524,7 +1701,10 @@ impl<'a> P<'a> {
|
||||
if !self.eat(&TokenKind::RParen) {
|
||||
loop {
|
||||
match self.k() {
|
||||
TokenKind::Ident { name: pname, suffix: psfx } => {
|
||||
TokenKind::Ident {
|
||||
name: pname,
|
||||
suffix: psfx,
|
||||
} => {
|
||||
self.advance();
|
||||
params.push(Param {
|
||||
name: pname,
|
||||
@@ -1547,7 +1727,13 @@ impl<'a> P<'a> {
|
||||
}
|
||||
if self.eat(&TokenKind::Eq) {
|
||||
let body = self.parse_expr()?;
|
||||
Some(Stmt::DefFn { name, suffix, params, body, pos })
|
||||
Some(Stmt::DefFn {
|
||||
name,
|
||||
suffix,
|
||||
params,
|
||||
body,
|
||||
pos,
|
||||
})
|
||||
} else {
|
||||
// Blockform: DEF FNname(...) … [EXIT DEF] … END DEF
|
||||
let body = self.parse_stmt_list(|p: &P| p.at_end_pair(Kw::Def));
|
||||
@@ -1557,7 +1743,13 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
self.err("Expected: END DEF");
|
||||
}
|
||||
Some(Stmt::DefFnBlock { name, suffix, params, body, pos })
|
||||
Some(Stmt::DefFnBlock {
|
||||
name,
|
||||
suffix,
|
||||
params,
|
||||
body,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1612,7 +1804,10 @@ impl<'a> P<'a> {
|
||||
}
|
||||
}
|
||||
// Impliziter Aufruf: `name [arg [, arg …]]`
|
||||
if let Expr::Name { name, suffix, args, .. } = target {
|
||||
if let Expr::Name {
|
||||
name, suffix, args, ..
|
||||
} = target
|
||||
{
|
||||
// Ohne CALL-Keyword sind Klammern Wert-Klammern (BYVAL), keine
|
||||
// Argumentlisten-Klammern: `Foo (n%)` übergibt `(n%)`.
|
||||
let mut call_args: Vec<Expr> = args
|
||||
@@ -1626,7 +1821,12 @@ impl<'a> P<'a> {
|
||||
if !self.at_stmt_end() && call_args.is_empty() {
|
||||
call_args = self.parse_arg_list_to_stmt_end();
|
||||
}
|
||||
Some(Stmt::Call { name, suffix, args: call_args, pos })
|
||||
Some(Stmt::Call {
|
||||
name,
|
||||
suffix,
|
||||
args: call_args,
|
||||
pos,
|
||||
})
|
||||
} else {
|
||||
self.err("Syntax error");
|
||||
self.sync();
|
||||
@@ -1640,7 +1840,7 @@ impl<'a> P<'a> {
|
||||
fn parse_name_ref(&mut self) -> Option<Expr> {
|
||||
let pos = self.pos();
|
||||
match self.k() {
|
||||
TokenKind::Ident { name, suffix } => {
|
||||
TokenKind::Ident { mut name, suffix } => {
|
||||
self.advance();
|
||||
let args = if self.eat(&TokenKind::LParen) {
|
||||
let a = self.parse_arg_list(&TokenKind::RParen);
|
||||
@@ -1649,7 +1849,25 @@ impl<'a> P<'a> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(Expr::Name { name, suffix, args, pos })
|
||||
if self.eat(&TokenKind::Dot) {
|
||||
match self.k() {
|
||||
TokenKind::Ident {
|
||||
name: member,
|
||||
suffix: None,
|
||||
} => {
|
||||
name.push('.');
|
||||
name.push_str(&member);
|
||||
self.advance();
|
||||
}
|
||||
_ => self.err("Expected: property"),
|
||||
}
|
||||
}
|
||||
Some(Expr::Name {
|
||||
name,
|
||||
suffix,
|
||||
args,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
self.err("Expected: variable");
|
||||
@@ -1665,7 +1883,12 @@ impl<'a> P<'a> {
|
||||
match self.k() {
|
||||
TokenKind::Ident { name, suffix } => {
|
||||
self.advance();
|
||||
Some(Expr::Name { name, suffix, args: None, pos })
|
||||
Some(Expr::Name {
|
||||
name,
|
||||
suffix,
|
||||
args: None,
|
||||
pos,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
self.err("Expected: variable");
|
||||
@@ -1773,7 +1996,12 @@ impl<'a> P<'a> {
|
||||
let pos = self.pos();
|
||||
self.advance();
|
||||
let rhs = self.parse_bin(prec + 1)?;
|
||||
lhs = Expr::Binary { op, lhs: Box::new(lhs), rhs: Box::new(rhs), pos };
|
||||
lhs = Expr::Binary {
|
||||
op,
|
||||
lhs: Box::new(lhs),
|
||||
rhs: Box::new(rhs),
|
||||
pos,
|
||||
};
|
||||
}
|
||||
Some(lhs)
|
||||
}
|
||||
@@ -1809,7 +2037,11 @@ impl<'a> P<'a> {
|
||||
TokenKind::Minus => {
|
||||
self.advance();
|
||||
let operand = self.parse_bin(12)?;
|
||||
Some(Expr::Unary { op: UnOp::Neg, operand: Box::new(operand), pos })
|
||||
Some(Expr::Unary {
|
||||
op: UnOp::Neg,
|
||||
operand: Box::new(operand),
|
||||
pos,
|
||||
})
|
||||
}
|
||||
TokenKind::Plus => {
|
||||
self.advance();
|
||||
@@ -1818,7 +2050,11 @@ impl<'a> P<'a> {
|
||||
TokenKind::Kw(Kw::Not) => {
|
||||
self.advance();
|
||||
let operand = self.parse_bin(6)?;
|
||||
Some(Expr::Unary { op: UnOp::Not, operand: Box::new(operand), pos })
|
||||
Some(Expr::Unary {
|
||||
op: UnOp::Not,
|
||||
operand: Box::new(operand),
|
||||
pos,
|
||||
})
|
||||
}
|
||||
_ => self.parse_primary(),
|
||||
}
|
||||
@@ -1842,6 +2078,30 @@ impl<'a> P<'a> {
|
||||
pos,
|
||||
});
|
||||
}
|
||||
if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "TYPEOF") {
|
||||
let pos = self.pos();
|
||||
self.advance();
|
||||
let value = self.parse_primary()?;
|
||||
if !self.eat_kw(Kw::Is) {
|
||||
self.err("Expected: IS");
|
||||
return None;
|
||||
}
|
||||
let class = match self.k() {
|
||||
TokenKind::Ident { name, suffix: None } => {
|
||||
self.advance();
|
||||
name
|
||||
}
|
||||
_ => {
|
||||
self.err("Expected: control class");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
return Some(Expr::TypeOf {
|
||||
value: Box::new(value),
|
||||
class,
|
||||
pos,
|
||||
});
|
||||
}
|
||||
match self.k() {
|
||||
TokenKind::Num(n) => {
|
||||
self.advance();
|
||||
@@ -1913,13 +2173,21 @@ mod tests {
|
||||
"IF a > 1 THEN\nPRINT 1\nELSEIF a > 0 THEN\nPRINT 2\nELSE\nPRINT 3\nEND IF\nIF b THEN PRINT 4 ELSE PRINT 5",
|
||||
);
|
||||
match &m.body[0] {
|
||||
Stmt::If { elseifs, else_body, .. } => {
|
||||
Stmt::If {
|
||||
elseifs, else_body, ..
|
||||
} => {
|
||||
assert_eq!(elseifs.len(), 1);
|
||||
assert!(else_body.is_some());
|
||||
}
|
||||
other => panic!("erwartet IF, war {other:?}"),
|
||||
}
|
||||
assert!(matches!(&m.body[1], Stmt::If { else_body: Some(_), .. }));
|
||||
assert!(matches!(
|
||||
&m.body[1],
|
||||
Stmt::If {
|
||||
else_body: Some(_),
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1943,8 +2211,22 @@ mod tests {
|
||||
"FOR i = 1 TO 10 STEP 2\nNEXT i\nDO WHILE x\nLOOP\nDO\nLOOP UNTIL x\nWHILE x\nWEND",
|
||||
);
|
||||
assert!(matches!(&m.body[0], Stmt::For { step: Some(_), .. }));
|
||||
assert!(matches!(&m.body[1], Stmt::DoLoop { pre: Some((false, _)), post: None, .. }));
|
||||
assert!(matches!(&m.body[2], Stmt::DoLoop { pre: None, post: Some((true, _)), .. }));
|
||||
assert!(matches!(
|
||||
&m.body[1],
|
||||
Stmt::DoLoop {
|
||||
pre: Some((false, _)),
|
||||
post: None,
|
||||
..
|
||||
}
|
||||
));
|
||||
assert!(matches!(
|
||||
&m.body[2],
|
||||
Stmt::DoLoop {
|
||||
pre: None,
|
||||
post: Some((true, _)),
|
||||
..
|
||||
}
|
||||
));
|
||||
assert!(matches!(&m.body[3], Stmt::While { .. }));
|
||||
}
|
||||
|
||||
@@ -1963,9 +2245,29 @@ mod tests {
|
||||
let m = parse_ok(
|
||||
"ON ERROR GOTO Handler\nON LOCAL ERROR RESUME NEXT\nON ERROR GOTO 0\nHandler:\nRESUME NEXT",
|
||||
);
|
||||
assert!(matches!(&m.body[0], Stmt::OnError { local: false, action: OnErrorAction::Goto(_), .. }));
|
||||
assert!(matches!(&m.body[1], Stmt::OnError { local: true, action: OnErrorAction::ResumeNext, .. }));
|
||||
assert!(matches!(&m.body[2], Stmt::OnError { action: OnErrorAction::Disable, .. }));
|
||||
assert!(matches!(
|
||||
&m.body[0],
|
||||
Stmt::OnError {
|
||||
local: false,
|
||||
action: OnErrorAction::Goto(_),
|
||||
..
|
||||
}
|
||||
));
|
||||
assert!(matches!(
|
||||
&m.body[1],
|
||||
Stmt::OnError {
|
||||
local: true,
|
||||
action: OnErrorAction::ResumeNext,
|
||||
..
|
||||
}
|
||||
));
|
||||
assert!(matches!(
|
||||
&m.body[2],
|
||||
Stmt::OnError {
|
||||
action: OnErrorAction::Disable,
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1973,7 +2275,15 @@ mod tests {
|
||||
let m = parse_ok("x = 1 + 2 * 3 ^ 2");
|
||||
// 1 + (2 * (3 ^ 2))
|
||||
match &m.body[0] {
|
||||
Stmt::Assign { value: Expr::Binary { op: BinOp::Add, rhs, .. }, .. } => {
|
||||
Stmt::Assign {
|
||||
value:
|
||||
Expr::Binary {
|
||||
op: BinOp::Add,
|
||||
rhs,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
assert!(matches!(**rhs, Expr::Binary { op: BinOp::Mul, .. }));
|
||||
}
|
||||
other => panic!("unerwartet: {other:?}"),
|
||||
@@ -1989,4 +2299,18 @@ mod tests {
|
||||
assert!(matches!(&m.body[1], Stmt::Dim { shared: true, decls, .. } if decls.len() == 2));
|
||||
assert!(matches!(&m.body[3], Stmt::DefType { ranges, .. } if ranges.len() == 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forms_syntax() {
|
||||
let m = parse_ok(
|
||||
"'$FORM\nDIM f AS FORM, c AS CONTROL\nIF TYPEOF c IS CommandButton THEN PRINT 1",
|
||||
);
|
||||
assert!(matches!(m.body[0], Stmt::MetaForm { .. }));
|
||||
assert!(matches!(&m.body[1], Stmt::Dim { decls, .. }
|
||||
if decls[0].as_type == Some(TypeName::Form) && decls[1].as_type == Some(TypeName::Control)));
|
||||
assert!(
|
||||
matches!(&m.body[2], Stmt::If { cond: Expr::TypeOf { class, .. }, .. }
|
||||
if class == "COMMANDBUTTON")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user