slow.monster

STUDIO HAZE · topaz

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20 · 56 files

compiler/crates/topaz_self_frontend/topaz/layout.tpz

Topaz · 13600 B · Original file

1import types { LayoutResult, PreviewDiagnostic, PreviewToken }
3function token(kind: string, lo: int, hi: int, synthetic: bool) -> PreviewToken {
4 PreviewToken {
5 kind: kind,
6 lo: lo,
7 hi: hi,
8 stream: "layout",
9 synthetic: synthetic,
10 }
13function copyToken(value: PreviewToken) -> PreviewToken {
14 token(value.kind, value.lo, value.hi, value.synthetic)
17function diagnostic(code: string, message: string, lo: int, hi: int) -> PreviewDiagnostic {
18 PreviewDiagnostic { code: code, message: message, lo: lo, hi: hi, notes: [] }
21function lastTokenKind(values: Array<PreviewToken>) -> string {
22 values[values.length - 1].kind
25function text(bytes: Bytes, lo: int, hi: int) -> string {
26 match bytes.slice(lo, hi).decodeUtf8() {
27 case Ok(value) => value
28 case Err(_) => ""
29 }
32function isCloser(kind: string) -> bool {
33 kind == "right-paren" ||
34 kind == "right-bracket" ||
35 kind == "right-brace" ||
36 kind == "interpolation-end"
39function isKeyword(kind: string) -> bool {
40 kind.startsWith("keyword/")
43enum LineContinuation {
44 Leading,
45 Trailing,
46 Both,
49function lineContinuation(kind: string) -> Option<LineContinuation> {
50 if kind == "gt" || kind == "gt-gt" || kind == "keyword/else" {
51 Some(LineContinuation.Leading)
52 } else if kind == "plus" || kind == "minus" || kind == "bang" ||
53 kind == "tilde" || kind == "comma" || kind == "fat-arrow" ||
54 kind == "left-paren" || kind == "left-bracket" {
55 Some(LineContinuation.Trailing)
56 } else if kind == "star-star" || kind == "star" || kind == "slash" ||
57 kind == "percent" || kind == "dot-dot" || kind == "dot-dot-lt" ||
58 kind == "lt" || kind == "le" || kind == "ge" || kind == "eq-eq" ||
59 kind == "not-eq" || kind == "keyword/in" || kind == "and-and" ||
60 kind == "or-or" || kind == "question-question" || kind == "pipe-gt" ||
61 kind == "eq" || kind == "plus-eq" || kind == "minus-eq" ||
62 kind == "star-eq" || kind == "slash-eq" || kind == "percent-eq" ||
63 kind == "question-question-eq" || kind == "dot" ||
64 kind == "question-dot" || kind == "keyword/by" {
65 Some(LineContinuation.Both)
66 } else {
67 None
68 }
71function isTrailing(kind: string) -> bool {
72 match lineContinuation(kind) {
73 case Some(Trailing) => true
74 case Some(Both) => true
75 case _ => false
76 }
79function previousIs(previous: Option<string>, kind: string) -> bool {
80 match previous {
81 case Some(value) => value == kind
82 case None => false
83 }
86function previousIsTrailing(previous: Option<string>) -> bool {
87 match previous {
88 case Some(kind) => isTrailing(kind)
89 case None => false
90 }
93function isLeading(kind: string) -> bool {
94 match lineContinuation(kind) {
95 case Some(Leading) => true
96 case Some(Both) => true
97 case _ => false
98 }
101function startsRecordField(tokens: Array<PreviewToken>, start: int) -> bool {
102 let mut index = start
103 let mut first: Option<string> = None
104 while index < tokens.length {
105 let kind = tokens[index].kind
106 index += 1
107 if kind == "newline" {
108 continue
109 }
110 match first {
111 case Some(head) => return (head == "identifier" || isKeyword(head)) &&
112 kind == "colon"
113 case None => {
114 first = Some(kind)
115 }
116 }
117 }
118 false
121function keywordHasFieldRole(
122 state: LayoutState,
123 tokens: Array<PreviewToken>,
124 index: int,
125 current: PreviewToken,
126) -> bool {
127 if !isKeyword(current.kind) {
128 return false
129 }
130 if previousIs(state.previous, "dot") || previousIs(state.previous, "question-dot") {
131 return true
132 }
133 let mut next = index + 1
134 while next < tokens.length {
135 let kind = tokens[next].kind
136 next += 1
137 if kind != "newline" {
138 return kind == "colon"
139 }
140 }
141 false
144enum LayoutPatternKind {
145 Binding,
146 Case,
147 For,
150record LayoutPattern {
151 depth: int,
152 kind: LayoutPatternKind,
155function patternIsActive(pattern: Option<LayoutPattern>) -> bool {
156 match pattern {
157 case Some(_) => true
158 case None => false
159 }
162function patternContinuesAt(pattern: Option<LayoutPattern>, depth: int) -> bool {
163 match pattern {
164 case Some(value) => value.depth == depth
165 case None => false
166 }
169enum LayoutMode {
170 Separator,
171 Continuation,
174enum LayoutOpener {
175 Program,
176 Paren,
177 Bracket,
178 Brace,
179 Interpolation,
182function classifyBrace(
183 state: LayoutState,
184 tokens: Array<PreviewToken>,
185 index: int,
186 frameDepth: int,
187) -> LayoutMode {
188 if state.moduleItem || patternIsActive(state.pattern) {
189 return LayoutMode.Continuation
190 }
191 let mut concurrentDepths = state.concurrentDepths
192 if concurrentDepths.length > 0 &&
193 concurrentDepths[concurrentDepths.length - 1] == frameDepth {
194 concurrentDepths.pop()
195 return LayoutMode.Separator
196 }
197 if startsRecordField(tokens, index + 1) {
198 LayoutMode.Continuation
199 } else {
200 LayoutMode.Separator
201 }
204record LayoutFrame {
205 mode: LayoutMode,
206 opener: LayoutOpener,
207 hasItem: bool,
210function topFrame(frames: Array<LayoutFrame>) -> LayoutFrame {
211 frames[frames.length - 1]
214record LayoutState {
215 output: Array<PreviewToken>,
216 diagnostics: Array<PreviewDiagnostic>,
217 frames: Array<LayoutFrame>,
218 pending: Option<PreviewToken>,
219 previous: Option<string>,
220 pattern: Option<LayoutPattern>,
221 concurrentDepths: Array<int>,
222 moduleItem: bool,
225function markFrameItem(state: LayoutState) -> () {
226 let mut frames = state.frames
227 let frame = topFrame(frames)
228 frames.pop()
229 frames.push(LayoutFrame { ...frame, hasItem: true })
232function clearConcurrentAtDepth(state: LayoutState, depth: int) -> () {
233 let mut depths = state.concurrentDepths
234 while depths.length > 0 && depths[depths.length - 1] == depth {
235 depths.pop()
236 }
237 ()
240function resetStatementContext(state: LayoutState, frameDepth: int) -> LayoutState {
241 clearConcurrentAtDepth(state, frameDepth)
242 LayoutState { ...state, moduleItem: false, pattern: None }
245function observeNewline(
246 state: LayoutState,
247 current: PreviewToken,
248 frameDepth: int,
249) -> LayoutState {
250 let pipeContinues = previousIs(state.previous, "pipe") &&
251 patternContinuesAt(state.pattern, frameDepth)
252 let frame = topFrame(state.frames)
253 if frame.mode == LayoutMode.Separator && frame.hasItem &&
254 lastTokenKind(state.output) != "separator" && !previousIsTrailing(state.previous) &&
255 !pipeContinues {
256 match state.pending {
257 case Some(_) => ()
258 case None => return LayoutState {
259 ...state,
260 pending: Some(current),
261 }
262 }
263 }
264 state
267function consumeSemicolon(
268 state: LayoutState,
269 current: PreviewToken,
270 frameDepth: int,
271) -> LayoutState {
272 if topFrame(state.frames).mode == LayoutMode.Continuation {
273 let mut diagnostics = state.diagnostics
274 diagnostics.push(diagnostic(
275 "TPZ1001",
276 "`;` does not separate items inside a delimiter list; use `,`",
277 current.lo,
278 current.hi,
279 ))
280 LayoutState { ...state, previous: Some("semicolon") }
281 } else {
282 let mut output = state.output
283 output.push(token("separator", current.lo, current.hi, false))
284 let next = resetStatementContext(
285 LayoutState { ...state, pending: None },
286 frameDepth,
287 )
288 LayoutState { ...next, previous: Some("semicolon") }
289 }
292function flushPendingSeparator(
293 state: LayoutState,
294 current: PreviewToken,
295 frameDepth: int,
296) -> LayoutState {
297 let pending = match state.pending {
298 case Some(value) => value
299 case None => return state
300 }
301 let currentPipeContinues = current.kind == "pipe" &&
302 patternContinuesAt(state.pattern, frameDepth)
303 let mut next = state
304 if !isLeading(current.kind) && !currentPipeContinues &&
305 !isCloser(current.kind) && current.kind != "eof" {
306 let mut output = state.output
307 output.push(token(
308 "separator",
309 pending.lo,
310 pending.hi,
311 true,
312 ))
313 next = resetStatementContext(state, frameDepth)
314 }
315 LayoutState { ...next, pending: None }
318function recognizeModuleItem(
319 state: LayoutState,
320 tokens: Array<PreviewToken>,
321 bytes: Bytes,
322 index: int,
323 current: PreviewToken,
324 frameDepth: int,
325) -> LayoutState {
326 if frameDepth != 1 || current.kind != "identifier" ||
327 (state.output.length > 0 && lastTokenKind(state.output) != "separator") ||
328 text(bytes, current.lo, current.hi) != "import" {
329 return state
330 }
331 if index + 1 < tokens.length && tokens[index + 1].kind == "identifier" {
332 LayoutState { ...state, moduleItem: true }
333 } else {
334 state
335 }
338function closePatternIfTerminated(
339 state: LayoutState,
340 current: PreviewToken,
341 frameDepth: int,
342) -> LayoutState {
343 let pattern = match state.pattern {
344 case Some(value) => value
345 case None => return state
346 }
347 if pattern.depth != frameDepth { return state }
348 let terminates = match pattern.kind {
349 case Binding => current.kind == "eq" || current.kind == "colon"
350 case Case =>
351 current.kind == "fat-arrow" || current.kind == "keyword/if"
352 case For => current.kind == "keyword/in"
353 }
354 if terminates {
355 LayoutState { ...state, pattern: None }
356 } else {
357 state
358 }
361function openerKind(kind: string) -> LayoutOpener {
362 if kind == "left-paren" || kind == "right-paren" {
363 LayoutOpener.Paren
364 } else if kind == "left-bracket" || kind == "right-bracket" {
365 LayoutOpener.Bracket
366 } else if kind == "left-brace" || kind == "right-brace" {
367 LayoutOpener.Brace
368 } else {
369 LayoutOpener.Interpolation
370 }
373function openContinuationFrame(state: LayoutState, current: PreviewToken) -> () {
374 markFrameItem(state)
375 let mut frames = state.frames
376 frames.push(LayoutFrame {
377 mode: LayoutMode.Continuation,
378 opener: openerKind(current.kind),
379 hasItem: false,
380 })
381 ()
384function openBraceFrame(
385 state: LayoutState,
386 tokens: Array<PreviewToken>,
387 index: int,
388 frameDepth: int,
389) -> () {
390 markFrameItem(state)
391 let mode = classifyBrace(
392 state,
393 tokens,
394 index,
395 frameDepth,
396 )
397 let mut frames = state.frames
398 frames.push(LayoutFrame { mode: mode, opener: LayoutOpener.Brace, hasItem: false })
399 ()
402function closeFrame(state: LayoutState, current: PreviewToken) -> LayoutState {
403 let mut next = state
404 if topFrame(state.frames).opener == openerKind(current.kind) {
405 let mut frames = state.frames
406 let mut concurrentDepths = state.concurrentDepths
407 frames.pop()
408 let newDepth = frames.length
409 let mut trimming = true
410 while concurrentDepths.length > 0 && trimming {
411 if concurrentDepths[concurrentDepths.length - 1] > newDepth {
412 concurrentDepths.pop()
413 } else {
414 trimming = false
415 }
416 }
417 match state.pattern {
418 case Some(pattern) => {
419 if pattern.depth > newDepth {
420 next = LayoutState { ...state, pattern: None }
421 }
422 }
423 case None => ()
424 }
425 }
426 markFrameItem(next)
427 next
430function beginPattern(
431 state: LayoutState,
432 kind: LayoutPatternKind,
433 frameDepth: int,
434) -> LayoutState {
435 markFrameItem(state)
436 LayoutState {
437 ...state,
438 pattern: Some(LayoutPattern { depth: frameDepth, kind: kind }),
439 }
442function observeTokenStructure(
443 state: LayoutState,
444 tokens: Array<PreviewToken>,
445 index: int,
446 current: PreviewToken,
447 frameDepth: int,
448) -> LayoutState {
449 let keywordField = keywordHasFieldRole(state, tokens, index, current)
450 if current.kind == "left-paren" || current.kind == "left-bracket" ||
451 current.kind == "interpolation-start" {
452 openContinuationFrame(state, current)
453 state
454 } else if current.kind == "left-brace" {
455 openBraceFrame(state, tokens, index, frameDepth)
456 state
457 } else if isCloser(current.kind) {
458 closeFrame(state, current)
459 } else if current.kind == "keyword/concurrent" && !keywordField {
460 let mut concurrentDepths = state.concurrentDepths
461 concurrentDepths.push(frameDepth)
462 markFrameItem(state)
463 state
464 } else if (current.kind == "keyword/let" || current.kind == "keyword/const") &&
465 !keywordField {
466 beginPattern(state, LayoutPatternKind.Binding, frameDepth)
467 } else if current.kind == "keyword/case" && !keywordField {
468 beginPattern(state, LayoutPatternKind.Case, frameDepth)
469 } else if current.kind == "keyword/for" && !keywordField {
470 beginPattern(state, LayoutPatternKind.For, frameDepth)
471 } else {
472 markFrameItem(state)
473 state
474 }
477export function normalize(tokens: Array<PreviewToken>, source: string) -> LayoutResult {
478 let bytes = Bytes.encodeUtf8(source)
479 let mut state = LayoutState {
480 output: [],
481 diagnostics: [],
482 frames: [LayoutFrame {
483 mode: LayoutMode.Separator,
484 opener: LayoutOpener.Program,
485 hasItem: false,
486 }],
487 pending: None,
488 previous: None,
489 pattern: None,
490 concurrentDepths: [],
491 moduleItem: false,
492 }
493 let mut index = 0
494 while index < tokens.length {
495 let current = tokens[index]
496 let frameDepth = state.frames.length
497 if current.kind == "newline" {
498 state = observeNewline(state, current, frameDepth)
499 index += 1
500 continue
501 }
502 if current.kind == "semicolon" {
503 state = consumeSemicolon(state, current, frameDepth)
504 index += 1
505 continue
506 }
507 state = flushPendingSeparator(state, current, frameDepth)
508 state = recognizeModuleItem(state, tokens, bytes, index, current, frameDepth)
509 state = closePatternIfTerminated(state, current, frameDepth)
510 state = observeTokenStructure(state, tokens, index, current, frameDepth)
511 let mut output = state.output
512 output.push(copyToken(current))
513 state = LayoutState { ...state, previous: Some(current.kind) }
514 index += 1
515 }
516 LayoutResult { tokens: state.output, diagnostics: state.diagnostics }

SHA256 196383e57db0ad8b51c67f216f5cec15e260c705229e154b75867af83bfd5580