Transpiler for HTML-in-PowerShell, PSX (like JSX)
1param(
2 [Int[]] $DumpTest,
3 [Switch] $DumpAll
4)
5
6. ./Compiler.ps1
7
8$OPEN_ELEMENT_START = [Token]::new( [TokenType]::OPEN_ELEMENT_START, '<' )
9$EQUALS = [Token]::new( [TokenType]::EQUAL, '=' )
10$CLOSE_ELEMENT = [Token]::new( [TokenType]::CLOSE_ELEMENT, '>' )
11$VOID_ELEMENT_CLOSE = [Token]::new( [TokenType]::VOID_ELEMENT_CLOSE, '/>' )
12$OPEN_ELEMENT_END = [Token]::new( [TokenType]::OPEN_ELEMENT_END, '</' )
13$EOF = [Token]::new( [TokenType]::EOF, '' )
14
15function _tag_token($tag) {
16 return [Token]::new( [TokenType]::TAG_NAME, $tag )
17}
18
19$Tests = @(
20 @{
21 Name = "OPEN_ELEMENT_START"
22 Input = '<'
23 Expect = @(
24 $OPEN_ELEMENT_START,
25 $EOF
26 )
27 },
28 @{
29 Name = "Opener and tag name"
30 Input = "<div"
31 Expect = @(
32 $OPEN_ELEMENT_START,
33 [Token]::new(
34 [TokenType]::TAG_NAME,
35 'div'
36 ),
37 $EOF
38 )
39 },
40 @{
41 Name = "Empty element"
42 Input = "<div></div>"
43 Expect = @(
44 $OPEN_ELEMENT_START,
45 (_tag_token div),
46 $CLOSE_ELEMENT,
47 $OPEN_ELEMENT_END,
48 (_tag_token div),
49 $CLOSE_ELEMENT,
50 $EOF
51 )
52 },
53 @{
54 Name = "Empty void element"
55 Input = "<img/>"
56 Expect = @(
57 $OPEN_ELEMENT_START,
58 [Token]::new(
59 [TokenType]::TAG_NAME,
60 'img'
61 ),
62 $VOID_ELEMENT_CLOSE,
63 $EOF
64 )
65 }
66 @{
67 Name = "Ignore extra whitespace (empty element)"
68 Input = "<`n`n div `t`n ></ div `n `t`r >"
69 Expect = @(
70 $OPEN_ELEMENT_START,
71 [Token]::new(
72 [TokenType]::TAG_NAME,
73 'div'
74 ),
75 $CLOSE_ELEMENT,
76 $OPEN_ELEMENT_END,
77 [Token]::new(
78 [TokenType]::TAG_NAME,
79 'div'
80 ),
81 $CLOSE_ELEMENT,
82 $EOF
83 )
84 },
85 @{
86 Name = "Empty element with attributes"
87 Input = "<span attr1=`"bau bau`" attr2=awa></span>"
88 Expect = @(
89 $OPEN_ELEMENT_START,
90 (_tag_token span),
91 [Token]::new(
92 [TokenType]::ATTR_NAME,
93 'attr1'
94 ),
95 $EQUALS,
96 [Token]::new(
97 [TokenType]::ATTR_VALUE,
98 'bau bau'
99 ),
100 [Token]::new(
101 [TokenType]::ATTR_NAME,
102 'attr2'
103 ),
104 $EQUALS,
105 [Token]::new(
106 [TokenType]::ATTR_VALUE,
107 'awa'
108 ),
109 $CLOSE_ELEMENT,
110 $OPEN_ELEMENT_END,
111 (_tag_token span),
112 $CLOSE_ELEMENT,
113 $EOF
114 )
115 },
116 @{
117 Name = "Attribute with pure scriptblock"
118 Input = '<img attrx={ $_ ; ''hello'' "there" """wao"""; return "<baubau knrs>" } />'
119 Expect = @(
120 $OPEN_ELEMENT_START,
121 (_tag_token img),
122 [Token]::new(
123 [TokenType]::ATTR_NAME,
124 'attrx'
125 ),
126 $EQUALS,
127 [Token]::new(
128 [TokenType]::ATTR_VALUE_SCRIPTBLOCK,
129 ' $_ ; ''hello'' "there" """wao"""; return "<baubau knrs>" '
130 ),
131 $VOID_ELEMENT_CLOSE,
132 $EOF
133 )
134 },
135 @{
136 Name = "Attribute with embedded scriptblock"
137 Input = '<img attrx="bau $( $_ )$(gci Function: )uab" />'
138 Expect = @(
139 $OPEN_ELEMENT_START,
140 (_tag_token img),
141 [Token]::new(
142 [TokenType]::ATTR_NAME,
143 'attrx'
144 ),
145 $EQUALS,
146 [Token]::new(
147 [TokenType]::ATTR_VALUE,
148 'bau $( $_ )$(gci Function: )uab'
149 ),
150 $VOID_ELEMENT_CLOSE,
151 $EOF
152 )
153 },
154 @{
155 Name = "Splat"
156 Input = '<div attr1="one two" @Splat_Me three></div>'
157 Expect = @(
158 $OPEN_ELEMENT_START
159 (_tag_token div)
160 [Token]::new(
161 [TokenType]::ATTR_NAME,
162 'attr1'
163 ),
164 $EQUALS
165 [Token]::new(
166 [TokenType]::ATTR_VALUE,
167 'one two'
168 ),
169 [Token]::new(
170 [TokenType]::ATTR_SPLAT,
171 'Splat_Me'
172 ),
173 [Token]::new(
174 [TokenType]::ATTR_NAME,
175 'three'
176 ),
177 $CLOSE_ELEMENT,
178 $OPEN_ELEMENT_END,
179 (_tag_token div),
180 $CLOSE_ELEMENT,
181 $EOF
182 )
183 }
184)
185
186$t=0
187$Tests | % {
188 $Lexer = [Lexer]::new($_.Input)
189 $i=0
190
191 if ($t -in $DumpTest -or $DumpAll) {
192 Write-Host "Dumping for test $t ""$($_.Name)"""
193 Write-Host "Input: $($_.Input)"
194 }
195
196 for ($i = 0; $i -lt $_.Expect.Count; $i++) {
197 $Case = $_.Expect[$i]
198 $PrePos = $Lexer.Pos
199 $PreState = $Lexer.State
200 $Next = $Lexer.NextToken()
201
202 if ($t -in $DumpTest -or $DumpAll) {
203 Write-Host (@"
204 {0}
205 [ pre-pos: {2}, pre-state: {1} ]
206 [ pst-pos: {3}, pst-state: {4} ]
207 -> {5}
208 <- {6}
209"@ -f (
210 $i,
211 $PreState,
212 $PrePos,
213 $Lexer.Pos,
214 $Lexer.State,
215 $Case,
216 $Next
217))
218 }
219
220 if ($Case -ne $Next) {
221 throw ("Test ""{0}"" case {1} failed: expected {2}, got {3}" -f `
222 $_.Name,
223 $i,
224 "[ $Case ]",
225 "[ $Next ]"
226 )
227 }
228 }
229
230 $t++
231}
232
233Write-Host "All $($Tests.Count) test(s) passed"