
ParserTest.pudu
Pudu78 lines6.5 KB
1/** @Test.Domain.Parser.Suite — template tokens, holes, and literal fallbacks */2module PuduLangLog.Domain.ParserTest34import Std.Io as Io5import Std.Test as Test6import PuduLangLog.Domain.Parser as Parser7import PuduLangLog as Log8910fn hole(name: Str, raw: Str) -> Log.Hole {11 Log.Hole{name: name, raw: raw, format: None, alignment: None, hint: Log.Default, position: None}12}131415fn tokens(text: Str) -> Array[Log.Token] { Parser.parse(text).tokens }161718fn literal(text: Str) -> Array[Log.Token] { [Log.Literal(text)] }192021fn main() -> Int {22 let greeting = Parser.parse("Hello, \{Name\}!")23 let checks = Test.suite("Domain.Parser", &[24 Test.equals("empty text is one empty literal", &tokens(""), &[Log.Literal("")]),25 Test.equals("empty text binds nothing", &Parser.parse("").binding, &Log.Unbound),26 Test.equals("plain text is one literal", &tokens("Hello"), &literal("Hello")),27 Test.equals("a hole between literals", &greeting.tokens, &[Log.Literal("Hello, "), Log.Placeholder(hole("Name", "\{Name\}")), Log.Literal("!")]),28 Test.equals("the text is kept", &greeting.text, &"Hello, \{Name\}!"),29 Test.equals("the holes are listed", &greeting.holes, &[hole("Name", "\{Name\}")]),30 Test.equals("named holes bind by name", &greeting.binding, &Log.Named),31 Test.equals("a doubled brace is one brace", &tokens("\{\{literal\}\}"), &literal("\{literal\}")),32 Test.equals("tripled braces around a hole", &tokens("\{\{\{Name\}\}\}"), &[Log.Literal("\{"), Log.Placeholder(hole("Name", "\{Name\}")), Log.Literal("\}")]),33 Test.equals("a lone closing brace is text", &tokens("a\}b"), &literal("a\}b")),34 Test.equals("a doubled closing brace is one", &tokens("a\}\}b"), &literal("a\}b")),35 Test.equals("an unclosed brace is text", &tokens("\{Name"), &literal("\{Name")),36 Test.equals("a lone opening brace is text", &tokens("\{"), &literal("\{")),37 Test.equals("text before an unclosed brace", &tokens("a \{b"), &[Log.Literal("a "), Log.Literal("\{b")]),38 Test.equals("empty braces are text", &tokens("\{\}"), &literal("\{\}")),39 Test.equals("destructuring hint", &tokens("\{@User\}"), &[Log.Placeholder(Log.Hole{..hole("User", "\{@User\}"), hint: Log.Destructure})]),40 Test.equals("stringify hint", &tokens("\{$Data\}"), &[Log.Placeholder(Log.Hole{..hole("Data", "\{$Data\}"), hint: Log.Stringify})]),41 Test.equals("a hint without a name is text", &tokens("\{@\}"), &literal("\{@\}")),42 Test.equals("a format", &tokens("\{Count:000\}"), &[Log.Placeholder(Log.Hole{..hole("Count", "\{Count:000\}"), format: Some("000")})]),43 Test.equals("an empty format is none", &tokens("\{Count:\}"), &[Log.Placeholder(hole("Count", "\{Count:\}"))]),44 Test.equals("a format keeps later colons", &tokens("\{T:HH:mm\}"), &[Log.Placeholder(Log.Hole{..hole("T", "\{T:HH:mm\}"), format: Some("HH:mm")})]),45 Test.equals("right alignment", &tokens("\{Name,10\}"), &[Log.Placeholder(Log.Hole{..hole("Name", "\{Name,10\}"), alignment: Some(Log.Alignment{left: false, width: 10})})]),46 Test.equals("left alignment", &tokens("\{Name,-10\}"), &[Log.Placeholder(Log.Hole{..hole("Name", "\{Name,-10\}"), alignment: Some(Log.Alignment{left: true, width: 10})})]),47 Test.equals("alignment then format", &tokens("\{Name,-3:u\}"), &[Log.Placeholder(Log.Hole{..hole("Name", "\{Name,-3:u\}"), alignment: Some(Log.Alignment{left: true, width: 3}), format: Some("u")})]),48 Test.equals("a comma after the colon belongs to the format", &tokens("\{Name:u,3\}"), &[Log.Placeholder(Log.Hole{..hole("Name", "\{Name:u,3\}"), format: Some("u,3")})]),49 Test.equals("an empty alignment is text", &(tokens("\{Name,\}"), tokens("\{Name,:u\}")), &(literal("\{Name,\}"), literal("\{Name,:u\}"))),50 Test.equals("a plus sign is not an alignment", &tokens("\{Name,+5\}"), &literal("\{Name,+5\}")),51 Test.equals("an alignment that is not a number is text", &(tokens("\{Name,a\}"), tokens("\{Name,-\}"), tokens("\{Name,--5\}"), tokens("\{Name, 5\}")), &(literal("\{Name,a\}"), literal("\{Name,-\}"), literal("\{Name,--5\}"), literal("\{Name, 5\}"))),52 Test.equals("the largest width is an alignment", &tokens("\{Name,2147483647\}"), &[Log.Placeholder(Log.Hole{..hole("Name", "\{Name,2147483647\}"), alignment: Some(Log.Alignment{left: false, width: 2147483647})})]),53 Test.equals("an alignment past the largest width is text", &tokens("\{Name,2147483648\}"), &literal("\{Name,2147483648\}")),54 Test.equals("numbered holes bind by position", &Parser.parse("\{0\} \{1\}").binding, &Log.Positional),55 Test.equals("a numbered hole records its position", &Parser.parse("\{12\}").holes, &[Log.Hole{..hole("12", "\{12\}"), position: Some(12)}]),56 Test.equals("mixed holes bind by name", &Parser.parse("\{0\} \{Name\}").binding, &Log.Named),57 Test.equals("a number too large for a position is a name", &Parser.parse("\{99999999999\}").holes, &[hole("99999999999", "\{99999999999\}")]),58 Test.equals("digits then letters are text", &tokens("\{9x\}"), &literal("\{9x\}")),59 Test.equals("a decimal number is text", &tokens("\{1.5\}"), &literal("\{1.5\}")),60 Test.equals("spaces are text", &tokens("\{Na me\}"), &literal("\{Na me\}")),61 Test.equals("an underscore starts a name", &Parser.parse("\{_x1\}").holes, &[hole("_x1", "\{_x1\}")]),62 Test.equals("dotted names", &Parser.parse("\{User.Name\}").holes, &[hole("User.Name", "\{User.Name\}")]),63 Test.equals("misplaced dots are text", &(tokens("\{a.\}"), tokens("\{.a\}"), tokens("\{a..b\}"), tokens("\{a.1\}")), &(literal("\{a.\}"), literal("\{.a\}"), literal("\{a..b\}"), literal("\{a.1\}"))),64 Test.equals("a name may use letters beyond ASCII", &Parser.parse("\{Größe\}").holes, &[hole("Größe", "\{Größe\}")]),65 Test.equals("punctuation in a name is text", &(tokens("\{a-b\}"), tokens("\{a@b\}")), &(literal("\{a-b\}"), literal("\{a@b\}"))),66 Test.equals("a brace inside a hole ends it as text", &tokens("\{a\{b\}c"), &[Log.Literal("\{a\{b\}"), Log.Literal("c")]),67 Test.equals("adjacent holes", &Parser.parse("\{A\}\{B\}").holes, &[hole("A", "\{A\}"), hole("B", "\{B\}")]),68 Test.equals("a repeated name is two holes", &Parser.parse("\{A\} \{A\}").holes.length(), &2),69 Test.that("a blank name is invalid", !Parser.isValidName(" ") && !Parser.isValidName("")),70 Test.that("any other name is valid", Parser.isValidName("with space") && Parser.isValidName("x"))71 ])72 let ran = Test.run(&checks)73 for failure in Test.failuresOf(&ran) {74 let _reported = Io.writeErrorLine(failure)75 }76 Test.report(&ran)77}78