use tb_frontend::{editing::*, lexer, parser}; #[test] fn normalization_protects_tokens_and_is_idempotent() { for source in [ " print Wort$; \"MiX Case\" ' Kommentar bleibt", "data MiXeD, frei Text,\"Z\": print 1", "if x% then print x%", "rem frei text", "print &Hff; 2.0#", ] { let normalized = normalize_line(source); assert_eq!(normalize_line(&normalized), normalized); let tokens = |s| { lexer::lex(s) .tokens .into_iter() .map(|t| t.kind) .collect::>() }; assert_eq!(tokens(source), tokens(&normalized)); } assert_eq!( normalize_line("print Wert$; \"MiX\" ' Kommentar"), "PRINT Wert$; \"MiX\" ' Kommentar" ); assert!(normalize_line("data MiXeD, frei Text").ends_with(" MiXeD, frei Text")); } #[test] fn incomplete_is_parser_context_not_a_second_grammar() { for source in ["SUB X\n", "IF 1 THEN\n", "FUNCTION X\n"] { assert!( parser::parse("M", &lexer::lex(source).tokens).incomplete, "{source}" ); } for source in ["PRINT )\n", "SUB X\nPRINT )\n", "PRINT 1\n"] { assert!( !parser::parse("M", &lexer::lex(source).tokens).incomplete, "{source}" ); } } #[test] fn declaration_maintenance_and_procedure_generation_are_transactional() { let source = "DEFINT A-Z\nSUB Work (x)\nx=x+1\nEND SUB\n"; let (updated, notice) = maintain_declarations(source); assert!(notice.is_none(), "{notice:?}"); assert!(updated.contains("DECLARE SUB WORK (X%)"), "{updated}"); assert_eq!(maintain_declarations(&updated), (updated.clone(), None)); for source in [ "SUB Draft\n", "PRINT )\n", "DECLARE SUB Work(x$)\nSUB Work(x%)\nEND SUB\n", ] { let (same, notice) = maintain_declarations(source); assert_eq!(same, source); assert!(notice.is_some(), "{source}"); } let manual = "DECLARE SUB Work(x AS INTEGER)\nSUB Work(x%)\nEND SUB\n"; assert_eq!(maintain_declarations(manual), (manual.into(), None)); let addition = new_procedure(source, source.len(), "Fresh", true).unwrap(); assert!(addition.contains("FUNCTION Fresh%")); assert!( tb_frontend::analyze_source("M", &format!("{source}{addition}")) .diagnostics .is_empty() ); for name in ["Work", "PRINT", "bad name", ""] { assert!(new_procedure(source, 0, name, false).is_err()); } } #[test] fn deftype_context_does_not_leak_from_another_procedure() { let source = "' Modulrumpf\nDEFINT A-Z\nSUB First\nDEFSTR A-Z\nx$=\"x\"\nEND SUB\nSUB Second\nEND SUB\n"; let inside = source.find("x$=").unwrap(); assert!(new_procedure(source, inside, "Fresh", true) .unwrap() .contains("FUNCTION Fresh$")); let second = source.find("SUB Second").unwrap(); assert!(new_procedure(source, second, "Fresh", true) .unwrap() .contains("FUNCTION Fresh%")); let before = new_procedure(source, 0, "Fresh", true).unwrap(); assert!(before.contains("FUNCTION Fresh!")); assert!(before.contains("DEFSNG A-Z")); for at in [inside, second, 0] { let addition = new_procedure(source, at, "Fresh", true).unwrap(); let diagnostics = tb_frontend::analyze_source("M", &format!("{source}{addition}")).diagnostics; assert!(diagnostics.is_empty(), "{diagnostics:?}"); } assert!(new_procedure(source, 0, "Foo ' comment", false).is_err()); }