Phase 4: Formularmodell und Objektsprache
This commit is contained in:
@@ -62,43 +62,169 @@ pub enum NumValue {
|
||||
/// und -anweisungen sind KEINE Keywords, sondern Builtins der Semantik).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Kw {
|
||||
And, As, Call, Case, Common, Const, Data, Declare, Def, DefCur, DefDbl, DefInt,
|
||||
DefLng, DefSng, DefStr, Dim, Do, Double, Else, ElseIf, End, Eqv, Erase,
|
||||
Error, Exit, For, Function, Gosub, Goto, If, Imp, Input, Integer, Is,
|
||||
Let, Line, Local, Long, Loop, Mod, Next, Not, On, Option, Or, Print,
|
||||
Read, ReDim, Rem, Restore, Resume, Return, Select, Shared, Single,
|
||||
Static, Step, Stop, String, Sub, System, Then, To, Type, Until, Using,
|
||||
Wend, While, Xor, Currency,
|
||||
And,
|
||||
As,
|
||||
Call,
|
||||
Case,
|
||||
Common,
|
||||
Const,
|
||||
Data,
|
||||
Declare,
|
||||
Def,
|
||||
DefCur,
|
||||
DefDbl,
|
||||
DefInt,
|
||||
DefLng,
|
||||
DefSng,
|
||||
DefStr,
|
||||
Dim,
|
||||
Do,
|
||||
Double,
|
||||
Else,
|
||||
ElseIf,
|
||||
End,
|
||||
Eqv,
|
||||
Erase,
|
||||
Error,
|
||||
Exit,
|
||||
For,
|
||||
Function,
|
||||
Gosub,
|
||||
Goto,
|
||||
If,
|
||||
Imp,
|
||||
Input,
|
||||
Integer,
|
||||
Is,
|
||||
Let,
|
||||
Line,
|
||||
Local,
|
||||
Long,
|
||||
Loop,
|
||||
Mod,
|
||||
Next,
|
||||
Not,
|
||||
On,
|
||||
Option,
|
||||
Or,
|
||||
Print,
|
||||
Read,
|
||||
ReDim,
|
||||
Rem,
|
||||
Restore,
|
||||
Resume,
|
||||
Return,
|
||||
Select,
|
||||
Shared,
|
||||
Single,
|
||||
Static,
|
||||
Step,
|
||||
Stop,
|
||||
String,
|
||||
Sub,
|
||||
System,
|
||||
Then,
|
||||
To,
|
||||
Type,
|
||||
Until,
|
||||
Using,
|
||||
Wend,
|
||||
While,
|
||||
Xor,
|
||||
Currency,
|
||||
// Datei-E/A-Keywords: werden geparst, aber erst in Phase 3 implementiert
|
||||
Open, Close, Write, Field, Get, Put, Seek, Lset, Rset,
|
||||
Open,
|
||||
Close,
|
||||
Write,
|
||||
Field,
|
||||
Get,
|
||||
Put,
|
||||
Seek,
|
||||
Lset,
|
||||
Rset,
|
||||
}
|
||||
|
||||
fn keyword(upper: &str) -> Option<Kw> {
|
||||
use Kw::*;
|
||||
Some(match upper {
|
||||
"AND" => And, "AS" => As, "CALL" => Call, "CASE" => Case,
|
||||
"COMMON" => Common, "CONST" => Const, "CURRENCY" => Currency,
|
||||
"AND" => And,
|
||||
"AS" => As,
|
||||
"CALL" => Call,
|
||||
"CASE" => Case,
|
||||
"COMMON" => Common,
|
||||
"CONST" => Const,
|
||||
"CURRENCY" => Currency,
|
||||
"DATA" => Data,
|
||||
"DECLARE" => Declare, "DEF" => Def, "DEFCUR" => DefCur,
|
||||
"DEFDBL" => DefDbl, "DEFINT" => DefInt, "DEFLNG" => DefLng,
|
||||
"DEFSNG" => DefSng, "DEFSTR" => DefStr, "DIM" => Dim, "DO" => Do,
|
||||
"DOUBLE" => Double, "ELSE" => Else, "ELSEIF" => ElseIf, "END" => End,
|
||||
"EQV" => Eqv, "ERASE" => Erase, "ERROR" => Error, "EXIT" => Exit,
|
||||
"FIELD" => Field, "FOR" => For, "FUNCTION" => Function,
|
||||
"GET" => Get, "GOSUB" => Gosub, "GOTO" => Goto, "IF" => If,
|
||||
"IMP" => Imp, "INPUT" => Input, "INTEGER" => Integer, "IS" => Is,
|
||||
"LET" => Let, "LINE" => Line, "LOCAL" => Local, "LONG" => Long,
|
||||
"LOOP" => Loop, "LSET" => Lset, "MOD" => Mod, "NEXT" => Next,
|
||||
"NOT" => Not, "ON" => On, "OPEN" => Open, "OPTION" => Option,
|
||||
"OR" => Or, "PRINT" => Print, "PUT" => Put, "READ" => Read,
|
||||
"REDIM" => ReDim, "REM" => Rem, "RESTORE" => Restore,
|
||||
"RESUME" => Resume, "RETURN" => Return, "RSET" => Rset,
|
||||
"SEEK" => Seek, "SELECT" => Select, "SHARED" => Shared,
|
||||
"SINGLE" => Single, "STATIC" => Static, "STEP" => Step,
|
||||
"STOP" => Stop, "STRING" => String, "SUB" => Sub,
|
||||
"SYSTEM" => System, "THEN" => Then,
|
||||
"TO" => To, "TYPE" => Type, "UNTIL" => Until, "USING" => Using,
|
||||
"WEND" => Wend, "WHILE" => While, "WRITE" => Write, "XOR" => Xor,
|
||||
"DECLARE" => Declare,
|
||||
"DEF" => Def,
|
||||
"DEFCUR" => DefCur,
|
||||
"DEFDBL" => DefDbl,
|
||||
"DEFINT" => DefInt,
|
||||
"DEFLNG" => DefLng,
|
||||
"DEFSNG" => DefSng,
|
||||
"DEFSTR" => DefStr,
|
||||
"DIM" => Dim,
|
||||
"DO" => Do,
|
||||
"DOUBLE" => Double,
|
||||
"ELSE" => Else,
|
||||
"ELSEIF" => ElseIf,
|
||||
"END" => End,
|
||||
"EQV" => Eqv,
|
||||
"ERASE" => Erase,
|
||||
"ERROR" => Error,
|
||||
"EXIT" => Exit,
|
||||
"FIELD" => Field,
|
||||
"FOR" => For,
|
||||
"FUNCTION" => Function,
|
||||
"GET" => Get,
|
||||
"GOSUB" => Gosub,
|
||||
"GOTO" => Goto,
|
||||
"IF" => If,
|
||||
"IMP" => Imp,
|
||||
"INPUT" => Input,
|
||||
"INTEGER" => Integer,
|
||||
"IS" => Is,
|
||||
"LET" => Let,
|
||||
"LINE" => Line,
|
||||
"LOCAL" => Local,
|
||||
"LONG" => Long,
|
||||
"LOOP" => Loop,
|
||||
"LSET" => Lset,
|
||||
"MOD" => Mod,
|
||||
"NEXT" => Next,
|
||||
"NOT" => Not,
|
||||
"ON" => On,
|
||||
"OPEN" => Open,
|
||||
"OPTION" => Option,
|
||||
"OR" => Or,
|
||||
"PRINT" => Print,
|
||||
"PUT" => Put,
|
||||
"READ" => Read,
|
||||
"REDIM" => ReDim,
|
||||
"REM" => Rem,
|
||||
"RESTORE" => Restore,
|
||||
"RESUME" => Resume,
|
||||
"RETURN" => Return,
|
||||
"RSET" => Rset,
|
||||
"SEEK" => Seek,
|
||||
"SELECT" => Select,
|
||||
"SHARED" => Shared,
|
||||
"SINGLE" => Single,
|
||||
"STATIC" => Static,
|
||||
"STEP" => Step,
|
||||
"STOP" => Stop,
|
||||
"STRING" => String,
|
||||
"SUB" => Sub,
|
||||
"SYSTEM" => System,
|
||||
"THEN" => Then,
|
||||
"TO" => To,
|
||||
"TYPE" => Type,
|
||||
"UNTIL" => Until,
|
||||
"USING" => Using,
|
||||
"WEND" => Wend,
|
||||
"WHILE" => While,
|
||||
"WRITE" => Write,
|
||||
"XOR" => Xor,
|
||||
"CLOSE" => Close,
|
||||
_ => return None,
|
||||
})
|
||||
@@ -107,18 +233,39 @@ fn keyword(upper: &str) -> Option<Kw> {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TokenKind {
|
||||
/// Bezeichner; `name` ist bereits in Großschreibung normalisiert.
|
||||
Ident { name: String, suffix: Option<Suffix> },
|
||||
Ident {
|
||||
name: String,
|
||||
suffix: Option<Suffix>,
|
||||
},
|
||||
Kw(Kw),
|
||||
Num(NumValue),
|
||||
Str(String),
|
||||
Plus, Minus, Star, Slash, Backslash, Caret,
|
||||
Eq, Ne, Lt, Le, Gt, Ge,
|
||||
LParen, RParen, Comma, Semicolon, Colon, Hash,
|
||||
Plus,
|
||||
Minus,
|
||||
Star,
|
||||
Slash,
|
||||
Backslash,
|
||||
Caret,
|
||||
Eq,
|
||||
Ne,
|
||||
Lt,
|
||||
Le,
|
||||
Gt,
|
||||
Ge,
|
||||
LParen,
|
||||
RParen,
|
||||
Comma,
|
||||
Semicolon,
|
||||
Colon,
|
||||
Hash,
|
||||
Dot,
|
||||
/// Metabefehl `'$INCLUDE: 'datei''` (Pfad; leer = fehlerhafte Syntax).
|
||||
MetaInclude(String),
|
||||
/// Metabefehle `'$STATIC` / `'$DYNAMIC`.
|
||||
MetaStatic,
|
||||
MetaDynamic,
|
||||
/// Metabefehl `'$FORM`: das Modul ist ein Formularmodul.
|
||||
MetaForm,
|
||||
/// Rohtext einer `DATA`-Anweisung bis zum Anweisungsende. Er wird
|
||||
/// bewusst **nicht** zerlegt oder normalisiert: unquotierte Elemente
|
||||
/// behalten ihre Groß- und Kleinschreibung und ihren inneren Leerraum.
|
||||
@@ -139,6 +286,9 @@ fn meta_token(rest: &[char]) -> Option<TokenKind> {
|
||||
if up.starts_with("$DYNAMIC") {
|
||||
return Some(TokenKind::MetaDynamic);
|
||||
}
|
||||
if up.starts_with("$FORM") {
|
||||
return Some(TokenKind::MetaForm);
|
||||
}
|
||||
if up.starts_with("$INCLUDE") {
|
||||
if let Some(colon) = t.find(':') {
|
||||
let after = t[colon + 1..].trim();
|
||||
@@ -191,14 +341,15 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
break;
|
||||
}
|
||||
let start = i;
|
||||
let pos = SourcePos { line: line_no, column: (start + 1) as u32 };
|
||||
let pos = SourcePos {
|
||||
line: line_no,
|
||||
column: (start + 1) as u32,
|
||||
};
|
||||
let c = chars[i];
|
||||
|
||||
// Zeilenfortsetzung: `_` nach Leerraum, danach nur noch Leerraum
|
||||
if c == '_'
|
||||
&& (start == 0
|
||||
|| chars[start - 1] == ' '
|
||||
|| chars[start - 1] == '\t')
|
||||
&& (start == 0 || chars[start - 1] == ' ' || chars[start - 1] == '\t')
|
||||
&& chars[start + 1..].iter().all(|&ch| ch == ' ' || ch == '\t')
|
||||
{
|
||||
line_continued = true;
|
||||
@@ -236,17 +387,16 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
// Das Vorbild toleriert fehlende schließende
|
||||
// Anführungszeichen am Zeilenende.
|
||||
}
|
||||
tokens.push(Token { kind: TokenKind::Str(s), pos });
|
||||
tokens.push(Token {
|
||||
kind: TokenKind::Str(s),
|
||||
pos,
|
||||
});
|
||||
}
|
||||
'&' if i + 1 < chars.len()
|
||||
&& matches!(chars[i + 1], 'h' | 'H' | 'o' | 'O') =>
|
||||
{
|
||||
'&' if i + 1 < chars.len() && matches!(chars[i + 1], 'h' | 'H' | 'o' | 'O') => {
|
||||
let hex = matches!(chars[i + 1], 'h' | 'H');
|
||||
i += 2;
|
||||
let digit_start = i;
|
||||
while i < chars.len()
|
||||
&& chars[i].is_ascii_alphanumeric()
|
||||
{
|
||||
while i < chars.len() && chars[i].is_ascii_alphanumeric() {
|
||||
i += 1;
|
||||
}
|
||||
let digits: String = chars[digit_start..i].iter().collect();
|
||||
@@ -276,8 +426,8 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
}),
|
||||
}
|
||||
}
|
||||
'0'..='9' | '.' if c != '.'
|
||||
|| (i + 1 < chars.len() && chars[i + 1].is_ascii_digit()) =>
|
||||
'0'..='9' | '.'
|
||||
if c != '.' || (i + 1 < chars.len() && chars[i + 1].is_ascii_digit()) =>
|
||||
{
|
||||
let int_start = i;
|
||||
while i < chars.len() && chars[i].is_ascii_digit() {
|
||||
@@ -298,9 +448,7 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
// Exponent E/D
|
||||
let mut exp_kind: Option<char> = None;
|
||||
let mut exp_str = String::new();
|
||||
if i < chars.len()
|
||||
&& matches!(chars[i], 'e' | 'E' | 'd' | 'D')
|
||||
{
|
||||
if i < chars.len() && matches!(chars[i], 'e' | 'E' | 'd' | 'D') {
|
||||
let save = i;
|
||||
let k = chars[i].to_ascii_uppercase();
|
||||
let mut j = i + 1;
|
||||
@@ -403,22 +551,25 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
}
|
||||
}
|
||||
};
|
||||
tokens.push(Token { kind: TokenKind::Num(value), pos });
|
||||
tokens.push(Token {
|
||||
kind: TokenKind::Num(value),
|
||||
pos,
|
||||
});
|
||||
}
|
||||
c if c.is_alphabetic() => {
|
||||
i += 1;
|
||||
while i < chars.len()
|
||||
&& (chars[i].is_alphanumeric()
|
||||
|| chars[i] == '.'
|
||||
|| chars[i] == '_')
|
||||
&& (chars[i].is_alphanumeric() || chars[i] == '.' || chars[i] == '_')
|
||||
{
|
||||
i += 1;
|
||||
}
|
||||
let mut name: String = chars[start..i]
|
||||
.iter()
|
||||
.collect::<String>()
|
||||
.to_uppercase();
|
||||
let suffix = if i < chars.len() {
|
||||
let mut name: String =
|
||||
chars[start..i].iter().collect::<String>().to_uppercase();
|
||||
let container_bang = i < chars.len()
|
||||
&& chars[i] == '!'
|
||||
&& i + 1 < chars.len()
|
||||
&& chars[i + 1].is_alphabetic();
|
||||
let suffix = if i < chars.len() && !container_bang {
|
||||
Suffix::from_char(chars[i])
|
||||
} else {
|
||||
None
|
||||
@@ -426,6 +577,16 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
if suffix.is_some() {
|
||||
i += 1;
|
||||
}
|
||||
if container_bang {
|
||||
name.push('!');
|
||||
i += 1;
|
||||
while i < chars.len()
|
||||
&& (chars[i].is_alphanumeric() || chars[i] == '.' || chars[i] == '_')
|
||||
{
|
||||
name.push(chars[i].to_ascii_uppercase());
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if suffix.is_none() {
|
||||
if let Some(kw) = keyword(&name) {
|
||||
if kw == Kw::Rem {
|
||||
@@ -435,7 +596,10 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
}
|
||||
break 'line;
|
||||
}
|
||||
tokens.push(Token { kind: TokenKind::Kw(kw), pos });
|
||||
tokens.push(Token {
|
||||
kind: TokenKind::Kw(kw),
|
||||
pos,
|
||||
});
|
||||
if kw == Kw::Data {
|
||||
// Der Rest der Anweisung ist Rohtext: bis zum
|
||||
// `:` außerhalb von Anführungszeichen oder bis
|
||||
@@ -486,8 +650,9 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
';' => TokenKind::Semicolon,
|
||||
':' => TokenKind::Colon,
|
||||
'#' => TokenKind::Hash,
|
||||
'.' => TokenKind::Dot,
|
||||
'?' => TokenKind::Kw(Kw::Print), // Editor-Kurzform
|
||||
'&' => TokenKind::Kw(Kw::Long), // isoliertes & (selten)
|
||||
'&' => TokenKind::Kw(Kw::Long), // isoliertes & (selten)
|
||||
'<' => {
|
||||
if i < chars.len() && chars[i] == '=' {
|
||||
i += 1;
|
||||
@@ -538,9 +703,15 @@ pub fn lex(source: &str) -> LexOutput {
|
||||
|
||||
tokens.push(Token {
|
||||
kind: TokenKind::Eof,
|
||||
pos: SourcePos { line: (source.lines().count() + 1) as u32, column: 1 },
|
||||
pos: SourcePos {
|
||||
line: (source.lines().count() + 1) as u32,
|
||||
column: 1,
|
||||
},
|
||||
});
|
||||
LexOutput { tokens, diagnostics }
|
||||
LexOutput {
|
||||
tokens,
|
||||
diagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -565,7 +736,10 @@ mod tests {
|
||||
let k = kinds("STRING$ STRING");
|
||||
assert_eq!(
|
||||
k[0],
|
||||
TokenKind::Ident { name: "STRING".into(), suffix: Some(Suffix::Str) }
|
||||
TokenKind::Ident {
|
||||
name: "STRING".into(),
|
||||
suffix: Some(Suffix::Str)
|
||||
}
|
||||
);
|
||||
assert_eq!(k[1], TokenKind::Kw(Kw::String));
|
||||
}
|
||||
@@ -620,4 +794,23 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bang_ist_suffix_oder_containeroperator_nach_folgetoken() {
|
||||
let k = kinds("Wert! = 1.5\nForm1!Text1.Text = \"a\"");
|
||||
assert_eq!(
|
||||
k[0],
|
||||
TokenKind::Ident {
|
||||
name: "WERT".into(),
|
||||
suffix: Some(Suffix::Single)
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
k[4],
|
||||
TokenKind::Ident {
|
||||
name: "FORM1!TEXT1.TEXT".into(),
|
||||
suffix: None
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user