change: action handler
Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
41cc051b71
commit
63b809dcf3
4 changed files with 4838 additions and 12 deletions
|
|
@ -34,6 +34,7 @@ Stmt: Statement = {
|
|||
},
|
||||
"DEBUGVAR" <name:Ident> => Statement::DebugVar { name },
|
||||
"Open" <filename:STRING> => Statement::Open { filename },
|
||||
<name:Ident> "(" <args:Comma<Expr>?> ")" => Statement::Call { name, args: args.unwrap_or_default() },
|
||||
};
|
||||
|
||||
Expr: Expression = {
|
||||
|
|
@ -61,6 +62,7 @@ AndExpr: Expression = {
|
|||
CmpExpr: Expression = {
|
||||
AddExpr,
|
||||
<left:CmpExpr> "=" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Eq, right: Box::new(right) },
|
||||
<left:CmpExpr> "!=" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Neq, right: Box::new(right) },
|
||||
<left:CmpExpr> "<=" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Lte, right: Box::new(right) },
|
||||
<left:CmpExpr> ">=" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Gte, right: Box::new(right) },
|
||||
<left:CmpExpr> "<" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Lt, right: Box::new(right) },
|
||||
|
|
@ -91,9 +93,18 @@ AtomExpr: Expression = {
|
|||
"True" => Expression::Literal(LiteralValue::Bool(true)),
|
||||
"False" => Expression::Literal(LiteralValue::Bool(false)),
|
||||
<name:Ident> "(" <args:Comma<Expr>?> ")" => Expression::FunctionCall { name, args: args.unwrap_or_default() },
|
||||
"$" <name:Ident> "." <prop:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name: format!("{}.{}", name, prop), is_negative: false }) },
|
||||
"$" <s:STRING> "." <prop:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name: format!("{}.{}", s, prop), is_negative: false }) },
|
||||
"$" "-" <name:Ident> "." <prop:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name: format!("{}.{}", name, prop), is_negative: true }) },
|
||||
"$" "-" <s:STRING> "." <prop:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name: format!("{}.{}", s, prop), is_negative: true }) },
|
||||
"$" <name:Ident> => Expression::SpecialVar { name, is_negative: false },
|
||||
"$" <s:STRING> => Expression::SpecialVar { name: s, is_negative: false },
|
||||
"$" "-" <name:Ident> => Expression::SpecialVar { name, is_negative: true },
|
||||
"$" "-" <s:STRING> => Expression::SpecialVar { name: s, is_negative: true },
|
||||
"@" <name:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name, is_negative: false }) },
|
||||
"@" <name:Ident> "." <prop:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name: format!("{}.{}", name, prop), is_negative: false }) },
|
||||
"@" <s:STRING> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name: s, is_negative: false }) },
|
||||
"@" <s:STRING> "." <prop:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name: format!("{}.{}", s, prop), is_negative: false }) },
|
||||
<name:Ident> => Expression::Identifier(name),
|
||||
"(" <e:Expr> ")" => e,
|
||||
};
|
||||
|
|
@ -107,7 +118,7 @@ Comma<T>: Vec<T> = {
|
|||
};
|
||||
|
||||
Stmts: Vec<Statement> = {
|
||||
<stmts:Stmt+> => stmts,
|
||||
<stmts:Stmt*> => stmts,
|
||||
};
|
||||
|
||||
STRING: String = {
|
||||
|
|
@ -119,11 +130,15 @@ NUM: f64 = {
|
|||
};
|
||||
|
||||
Ident: String = {
|
||||
<s:r"[a-zA-Z_][a-zA-Z0-9_]*"> => s.to_string()
|
||||
<s:r"[a-zA-Z_][a-zA-Z0-9_#]*"> => s.to_string()
|
||||
};
|
||||
|
||||
match {
|
||||
r"\s+" => {},
|
||||
r"\s+",
|
||||
} else {
|
||||
r#""[^"]*""#,
|
||||
r"[0-9]+(\.[0-9]+)?",
|
||||
} else {
|
||||
"Var",
|
||||
"If",
|
||||
"Then",
|
||||
|
|
@ -136,10 +151,13 @@ match {
|
|||
"False",
|
||||
"DEBUGVAR",
|
||||
"Open",
|
||||
"assigned",
|
||||
} else {
|
||||
"<=",
|
||||
">=",
|
||||
"&&",
|
||||
"||",
|
||||
"!=",
|
||||
"!",
|
||||
"$",
|
||||
"@",
|
||||
|
|
@ -152,10 +170,8 @@ match {
|
|||
"/",
|
||||
"<",
|
||||
">",
|
||||
".",
|
||||
"=",
|
||||
"assigned",
|
||||
} else {
|
||||
r#""[^"]*""#,
|
||||
r"[0-9]+(\.[0-9]+)?",
|
||||
r"[a-zA-Z_][a-zA-Z0-9_]*",
|
||||
r"[a-zA-Z_][a-zA-Z0-9_#]*",
|
||||
}
|
||||
4295
src/xml/parser/grammar.rs
Normal file
4295
src/xml/parser/grammar.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,5 @@
|
|||
pub mod preprocess;
|
||||
|
||||
use crate::xml::error::ParserResult;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
|
|
@ -5,18 +7,19 @@ pub mod grammar {
|
|||
include!(concat!(env!("OUT_DIR"), "/xml/parser/grammar.rs"));
|
||||
}
|
||||
|
||||
pub use crate::xml::ast::nodes::{AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind};
|
||||
|
||||
use grammar::ProgramParser;
|
||||
|
||||
pub use crate::xml::ast::nodes::{AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind};
|
||||
|
||||
pub struct ScriptParser;
|
||||
|
||||
impl ScriptParser {
|
||||
pub fn parse(source: &str) -> ParserResult<Program> {
|
||||
let preprocessed = preprocess::preprocess(source);
|
||||
let parser = ProgramParser::new();
|
||||
match parser.parse(source) {
|
||||
match parser.parse(&preprocessed) {
|
||||
Ok(program) => Ok(program),
|
||||
Err(e) => Err(crate::xml::error::ParseError::from_lalrpop_error(source, e)),
|
||||
Err(e) => Err(crate::xml::error::ParseError::from_lalrpop_error(&preprocessed, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,11 +155,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_comparison() {
|
||||
fn test_comparison_eq() {
|
||||
let result = ScriptParser::parse("If x = 5 Then Var y = 10 EndIf");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comparison_neq() {
|
||||
let result = ScriptParser::parse("If x != 5 Then Var y = 10 EndIf");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logical_and() {
|
||||
let result = ScriptParser::parse("If x = 5 && y = 3 Then Var z = 1 EndIf");
|
||||
|
|
@ -193,6 +202,45 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_statement() {
|
||||
let result = ScriptParser::parse("MACHINE_SET_IDLE(3)");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
if let Statement::Call { name, args } = &program.statements[0] {
|
||||
assert_eq!(name, "MACHINE_SET_IDLE");
|
||||
assert_eq!(args.len(), 1);
|
||||
} else {
|
||||
panic!("Expected Call statement");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_with_multiple_args() {
|
||||
let result = ScriptParser::parse(r#"STRCONTAIN("9501", MaterialAvailable, TaobinOnline)"#);
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
if let Statement::Call { name, args } = &program.statements[0] {
|
||||
assert_eq!(name, "STRCONTAIN");
|
||||
assert_eq!(args.len(), 3);
|
||||
} else {
|
||||
panic!("Expected Call statement");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_no_args() {
|
||||
let result = ScriptParser::parse("RefreshAll()");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
if let Statement::Call { name, args } = &program.statements[0] {
|
||||
assert_eq!(name, "RefreshAll");
|
||||
assert!(args.is_empty());
|
||||
} else {
|
||||
panic!("Expected Call statement");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_for_loop() {
|
||||
let result = ScriptParser::parse("For i In items Var x = i EndFor");
|
||||
|
|
@ -212,4 +260,110 @@ mod tests {
|
|||
let result = ScriptParser::parse("Var x = (1 + 2) * 3");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocessor_action_call() {
|
||||
let result = ScriptParser::parse("MACHINE_SET_IDLE 3");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
if let Statement::Call { name, args } = &program.statements[0] {
|
||||
assert_eq!(name, "MACHINE_SET_IDLE");
|
||||
assert_eq!(args.len(), 1);
|
||||
} else {
|
||||
panic!("Expected Call statement, got {:?}", program.statements[0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocessor_strip_comment() {
|
||||
let result = ScriptParser::parse("; this is a comment\nVar x = 5");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
assert_eq!(program.statements.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocessor_var_not_assigned_with_value() {
|
||||
let result = ScriptParser::parse("Var x !assigned StringFmt($count, DisplayFormat, PreScaleConvertShow)");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
if let Statement::VarDecl { name, value, assignment_type } = &program.statements[0] {
|
||||
assert_eq!(name, "x");
|
||||
assert_eq!(*assignment_type, AssignmentType::Equals);
|
||||
assert!(value.is_some());
|
||||
} else {
|
||||
panic!("Expected VarDecl");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocessor_unknown_action() {
|
||||
let result = ScriptParser::parse("CacheVarStr(\"get\", XMLProfile)");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
if let Statement::Call { name, args } = &program.statements[0] {
|
||||
assert_eq!(name, "CacheVarStr");
|
||||
assert_eq!(args.len(), 2);
|
||||
} else {
|
||||
panic!("Expected Call statement");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dollar_var_with_property() {
|
||||
let result = ScriptParser::parse("Var x = @\"12-01-01-0003\".Price");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_in_identifier() {
|
||||
let result = ScriptParser::parse("DEBUGVAR Not#LanguageLoaded");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocess_hyphenated_dollar_var() {
|
||||
let preprocessed = preprocess::preprocess("If $12-01-01-0003.Price = -1 Then Var x = 1 EndIf");
|
||||
assert!(preprocessed.contains("@\"12-01-01-0003\".Price"), "preprocessed: {}", preprocessed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_real_script_snippet() {
|
||||
let script = r#"
|
||||
MACHINE_SET_IDLE 3
|
||||
Var CountryName = "Thailand"
|
||||
If CountryName = "Thailand" Then
|
||||
Var TaobinPremiumEnable = 1
|
||||
Else
|
||||
Var TaobinPremiumEnable = 0
|
||||
EndIf
|
||||
DEBUGVAR LangProcess
|
||||
Var ToggleAfterEventProfileOff = 0
|
||||
Var credit_card_enable = ""
|
||||
READ_FILE "/mnt/sdcard/credit_card_enable" credit_card_enable
|
||||
CacheVarStr "get" XMLProfile
|
||||
"#;
|
||||
let result = ScriptParser::parse(script);
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
let program = result.unwrap();
|
||||
assert!(program.statements.len() >= 6, "expected at least 6 statements, got {}", program.statements.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neq_in_condition() {
|
||||
let result = ScriptParser::parse("If x != \"\" Then Var y = 1 EndIf");
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_page_board_event_open() {
|
||||
let content = std::fs::read_to_string("page_board.xml").unwrap();
|
||||
let start = content.find("<EventOpen>").unwrap();
|
||||
let end = content.find("</EventOpen>").unwrap();
|
||||
let script = &content[start + 12..end].trim();
|
||||
|
||||
let result = ScriptParser::parse(script);
|
||||
assert!(result.is_ok(), "parse failed: {:?}", result.err());
|
||||
}
|
||||
}
|
||||
361
src/xml/parser/preprocess.rs
Normal file
361
src/xml/parser/preprocess.rs
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
const KEYWORDS: &[&str] = &[
|
||||
"Var", "If", "Then", "Else", "EndIf", "For", "In", "EndFor",
|
||||
"True", "False", "DEBUGVAR", "Open", "assigned",
|
||||
];
|
||||
|
||||
fn is_keyword(word: &str) -> bool {
|
||||
KEYWORDS.contains(&word)
|
||||
}
|
||||
|
||||
fn strip_comments(source: &str) -> String {
|
||||
let mut result = String::new();
|
||||
for line in source.lines() {
|
||||
let stripped = strip_comment_from_line(line);
|
||||
if !stripped.trim().is_empty() {
|
||||
result.push_str(stripped.trim());
|
||||
result.push('\n');
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn strip_comment_from_line(line: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let mut in_string = false;
|
||||
let mut chars = line.chars().peekable();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
if ch == '"' {
|
||||
in_string = !in_string;
|
||||
result.push(ch);
|
||||
} else if ch == '\\' && in_string {
|
||||
result.push(ch);
|
||||
if let Some(next) = chars.next() {
|
||||
result.push(next);
|
||||
}
|
||||
} else if ch == ';' && !in_string {
|
||||
break;
|
||||
} else {
|
||||
result.push(ch);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn needs_quoting(name: &str) -> bool {
|
||||
name.contains('-') || name.starts_with(|c: char| c.is_ascii_digit())
|
||||
}
|
||||
|
||||
fn normalize_dollar_vars(text: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let chars: Vec<char> = text.chars().collect();
|
||||
let mut i = 0;
|
||||
|
||||
while i < chars.len() {
|
||||
if chars[i] == '$' {
|
||||
i += 1;
|
||||
let neg = i < chars.len() && chars[i] == '-';
|
||||
if neg {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let name_start = i;
|
||||
while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '-' || chars[i] == '_' || chars[i] == '#') {
|
||||
i += 1;
|
||||
}
|
||||
let name: String = chars[name_start..i].iter().collect();
|
||||
|
||||
if i < chars.len() && chars[i] == '.' {
|
||||
i += 1;
|
||||
let prop_start = i;
|
||||
while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_' || chars[i] == '#') {
|
||||
i += 1;
|
||||
}
|
||||
let prop: String = chars[prop_start..i].iter().collect();
|
||||
|
||||
if neg {
|
||||
write!(result, "$-\"{}\".{}", name, prop).unwrap();
|
||||
} else {
|
||||
write!(result, "@\"{}\".{}", name, prop).unwrap();
|
||||
}
|
||||
} else if needs_quoting(&name) {
|
||||
if neg {
|
||||
write!(result, "$-\"{}\"", name).unwrap();
|
||||
} else {
|
||||
write!(result, "$\"{}\"", name).unwrap();
|
||||
}
|
||||
} else if neg {
|
||||
write!(result, "$-{}", name).unwrap();
|
||||
} else {
|
||||
write!(result, "${}", name).unwrap();
|
||||
}
|
||||
} else {
|
||||
result.push(chars[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn is_action_start(word: &str) -> bool {
|
||||
!is_keyword(word) && !word.is_empty()
|
||||
}
|
||||
|
||||
fn tokenize_action_args(rest: &str) -> Vec<String> {
|
||||
let mut args = Vec::new();
|
||||
let mut current = String::new();
|
||||
let mut in_string = false;
|
||||
let mut paren_depth = 0;
|
||||
|
||||
for ch in rest.chars() {
|
||||
if in_string {
|
||||
current.push(ch);
|
||||
if ch == '"' {
|
||||
in_string = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
match ch {
|
||||
'"' => {
|
||||
current.push(ch);
|
||||
in_string = true;
|
||||
}
|
||||
'(' => {
|
||||
current.push(ch);
|
||||
paren_depth += 1;
|
||||
}
|
||||
')' => {
|
||||
current.push(ch);
|
||||
if paren_depth > 0 {
|
||||
paren_depth -= 1;
|
||||
}
|
||||
if paren_depth == 0 && !current.trim().is_empty() {
|
||||
args.push(current.trim().to_string());
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
' ' | '\t' if paren_depth == 0 => {
|
||||
if !current.is_empty() {
|
||||
args.push(current.trim().to_string());
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
',' if paren_depth == 0 => {
|
||||
if !current.is_empty() {
|
||||
args.push(current.trim().to_string());
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
current.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !current.trim().is_empty() {
|
||||
args.push(current.trim().to_string());
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
fn handle_var_line(line: &str) -> String {
|
||||
let line = normalize_dollar_vars(line);
|
||||
let trimmed = line.trim();
|
||||
|
||||
if let Some(rest) = trimmed.strip_prefix("Var ") {
|
||||
let rest = rest.trim_start();
|
||||
|
||||
if let Some(_rest2) = rest.strip_prefix("!assigned") {
|
||||
return line.to_string();
|
||||
}
|
||||
|
||||
let name_end = rest.find(|c: char| c.is_whitespace() || c == '=' || c == '!')
|
||||
.unwrap_or(rest.len());
|
||||
let name = &rest[..name_end];
|
||||
let after_name = rest[name_end..].trim_start();
|
||||
|
||||
if after_name.starts_with('=') {
|
||||
return line.to_string();
|
||||
}
|
||||
|
||||
if after_name.starts_with('!') {
|
||||
if let Some(without_bang) = after_name.strip_prefix("!assigned") {
|
||||
if without_bang.trim().is_empty() {
|
||||
return line.to_string();
|
||||
}
|
||||
let val = without_bang.trim();
|
||||
return format!("Var {} = {}", name, val);
|
||||
}
|
||||
}
|
||||
|
||||
if after_name.starts_with('"') || after_name.chars().next().map_or(false, |c| c.is_ascii_digit()) {
|
||||
return format!("Var {} = {}", name, after_name);
|
||||
}
|
||||
|
||||
if !name.is_empty() && !after_name.is_empty() {
|
||||
return format!("Var {} = {}", name, after_name);
|
||||
}
|
||||
}
|
||||
|
||||
line.to_string()
|
||||
}
|
||||
|
||||
fn normalize_line(line: &str) -> String {
|
||||
let line = normalize_dollar_vars(line);
|
||||
let trimmed = line.trim();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let first_word_end = trimmed.find(|c: char| c.is_whitespace() || c == '(' || c == '=')
|
||||
.unwrap_or(trimmed.len());
|
||||
let first_word = &trimmed[..first_word_end];
|
||||
|
||||
if first_word == "Var" {
|
||||
return handle_var_line(&line);
|
||||
}
|
||||
|
||||
if first_word == "If" || first_word == "Else" || first_word == "EndIf"
|
||||
|| first_word == "For" || first_word == "EndFor"
|
||||
|| first_word == "DEBUGVAR"
|
||||
|| first_word == "Open"
|
||||
{
|
||||
return line.trim().to_string();
|
||||
}
|
||||
|
||||
if is_keyword(first_word) {
|
||||
return line.trim().to_string();
|
||||
}
|
||||
|
||||
let rest = trimmed[first_word_end..].trim_start();
|
||||
let rest = rest.trim_end();
|
||||
|
||||
if rest.is_empty() {
|
||||
return format!("{}()", first_word);
|
||||
}
|
||||
|
||||
if rest.starts_with('(') {
|
||||
return trimmed.to_string();
|
||||
}
|
||||
|
||||
let args = tokenize_action_args(rest);
|
||||
let args_str = args.join(", ");
|
||||
format!("{}({})", first_word, args_str)
|
||||
}
|
||||
|
||||
pub fn preprocess(source: &str) -> String {
|
||||
let no_comments = strip_comments(source);
|
||||
let mut result = String::new();
|
||||
for line in no_comments.lines() {
|
||||
let normalized = normalize_line(line);
|
||||
if !normalized.is_empty() {
|
||||
result.push_str(&normalized);
|
||||
result.push('\n');
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_strip_comments() {
|
||||
assert_eq!(strip_comments("; this is a comment\n"), "");
|
||||
assert_eq!(strip_comments("Var x = 5 ; inline\n"), "Var x = 5\n");
|
||||
assert_eq!(strip_comments(r#"Var x = "hello ; here""#), "Var x = \"hello ; here\"\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_action_call() {
|
||||
assert_eq!(normalize_line("MACHINE_SET_IDLE 3"), "MACHINE_SET_IDLE(3)");
|
||||
assert_eq!(normalize_line("CacheVarStr \"get\" XMLProfile"), "CacheVarStr(\"get\", XMLProfile)");
|
||||
assert_eq!(normalize_line("StopLongPlay"), "StopLongPlay()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_var_with_equals() {
|
||||
assert_eq!(normalize_line("Var x = 5"), "Var x = 5");
|
||||
assert_eq!(normalize_line("Var x = \"hello\""), "Var x = \"hello\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_var_without_equals() {
|
||||
assert_eq!(normalize_line("Var return \"\""), "Var return = \"\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_var_not_assigned() {
|
||||
assert_eq!(normalize_line("Var x !assigned"), "Var x !assigned");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_var_not_assigned_with_value() {
|
||||
assert_eq!(
|
||||
normalize_line("Var x !assigned StringFmt($12-01-01-0003.Price, DisplayFormat, PreScaleConvertShow)"),
|
||||
"Var x = StringFmt(@\"12-01-01-0003\".Price, DisplayFormat, PreScaleConvertShow)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_keyword_lines_unchanged() {
|
||||
assert_eq!(normalize_line("If x = 5 Then"), "If x = 5 Then");
|
||||
assert_eq!(normalize_line("DEBUGVAR myVar"), "DEBUGVAR myVar");
|
||||
assert_eq!(normalize_line("Open \"file.xml\""), "Open \"file.xml\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_dollar_var_property() {
|
||||
assert_eq!(normalize_dollar_vars("$myVar"), "$myVar");
|
||||
assert_eq!(normalize_dollar_vars("$-count"), "$-count");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocess_multiline() {
|
||||
let input = "; comment\nVar x = 5\nMACHINE_SET_IDLE 3\n";
|
||||
let result = preprocess(input);
|
||||
assert!(result.contains("Var x = 5"));
|
||||
assert!(result.contains("MACHINE_SET_IDLE(3)"));
|
||||
assert!(!result.contains("comment"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_action_with_string_and_ident_args() {
|
||||
assert_eq!(
|
||||
normalize_line("STRCONTAIN \"9501\" MaterialAvailable TaobinOnline"),
|
||||
"STRCONTAIN(\"9501\", MaterialAvailable, TaobinOnline)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_action_with_number_and_string() {
|
||||
assert_eq!(
|
||||
normalize_line("OpenInst 0 \"/mnt/sdcard/file.xml\""),
|
||||
"OpenInst(0, \"/mnt/sdcard/file.xml\")"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neq_in_condition() {
|
||||
let line = "If x != \"\" Then";
|
||||
assert_eq!(normalize_line(line), "If x != \"\" Then");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dollar_var_with_hyphen() {
|
||||
assert_eq!(normalize_dollar_vars("$12-01-01-0003"), "$\"12-01-01-0003\"");
|
||||
assert_eq!(normalize_dollar_vars("$12-01-01-0003.Discount"), "@\"12-01-01-0003\".Discount");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dollar_var_negative_with_property() {
|
||||
assert_eq!(normalize_dollar_vars("$-12-01-01-0003.Price"), "$-\"12-01-01-0003\".Price");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue