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
1
import types { LayoutResult, PreviewDiagnostic, PreviewToken }3
function 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
}11
}13
function copyToken(value: PreviewToken) -> PreviewToken {14
token(value.kind, value.lo, value.hi, value.synthetic)15
}17
function diagnostic(code: string, message: string, lo: int, hi: int) -> PreviewDiagnostic {18
PreviewDiagnostic { code: code, message: message, lo: lo, hi: hi, notes: [] }19
}21
function lastTokenKind(values: Array<PreviewToken>) -> string {22
values[values.length - 1].kind23
}25
function text(bytes: Bytes, lo: int, hi: int) -> string {26
match bytes.slice(lo, hi).decodeUtf8() {27
case Ok(value) => value28
case Err(_) => ""29
}30
}32
function isCloser(kind: string) -> bool {33
kind == "right-paren" ||34
kind == "right-bracket" ||35
kind == "right-brace" ||36
kind == "interpolation-end"37
}39
function isKeyword(kind: string) -> bool {40
kind.startsWith("keyword/")41
}43
enum LineContinuation {44
Leading,45
Trailing,46
Both,47
}49
function 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
None68
}69
}71
function isTrailing(kind: string) -> bool {72
match lineContinuation(kind) {73
case Some(Trailing) => true74
case Some(Both) => true75
case _ => false76
}77
}79
function previousIs(previous: Option<string>, kind: string) -> bool {80
match previous {81
case Some(value) => value == kind82
case None => false83
}84
}86
function previousIsTrailing(previous: Option<string>) -> bool {87
match previous {88
case Some(kind) => isTrailing(kind)89
case None => false90
}91
}93
function isLeading(kind: string) -> bool {94
match lineContinuation(kind) {95
case Some(Leading) => true96
case Some(Both) => true97
case _ => false98
}99
}101
function startsRecordField(tokens: Array<PreviewToken>, start: int) -> bool {102
let mut index = start103
let mut first: Option<string> = None104
while index < tokens.length {105
let kind = tokens[index].kind106
index += 1107
if kind == "newline" {108
continue109
}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
false119
}121
function keywordHasFieldRole(122
state: LayoutState,123
tokens: Array<PreviewToken>,124
index: int,125
current: PreviewToken,126
) -> bool {127
if !isKeyword(current.kind) {128
return false129
}130
if previousIs(state.previous, "dot") || previousIs(state.previous, "question-dot") {131
return true132
}133
let mut next = index + 1134
while next < tokens.length {135
let kind = tokens[next].kind136
next += 1137
if kind != "newline" {138
return kind == "colon"139
}140
}141
false142
}144
enum LayoutPatternKind {145
Binding,146
Case,147
For,148
}150
record LayoutPattern {151
depth: int,152
kind: LayoutPatternKind,153
}155
function patternIsActive(pattern: Option<LayoutPattern>) -> bool {156
match pattern {157
case Some(_) => true158
case None => false159
}160
}162
function patternContinuesAt(pattern: Option<LayoutPattern>, depth: int) -> bool {163
match pattern {164
case Some(value) => value.depth == depth165
case None => false166
}167
}169
enum LayoutMode {170
Separator,171
Continuation,172
}174
enum LayoutOpener {175
Program,176
Paren,177
Bracket,178
Brace,179
Interpolation,180
}182
function 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.Continuation190
}191
let mut concurrentDepths = state.concurrentDepths192
if concurrentDepths.length > 0 &&193
concurrentDepths[concurrentDepths.length - 1] == frameDepth {194
concurrentDepths.pop()195
return LayoutMode.Separator196
}197
if startsRecordField(tokens, index + 1) {198
LayoutMode.Continuation199
} else {200
LayoutMode.Separator201
}202
}204
record LayoutFrame {205
mode: LayoutMode,206
opener: LayoutOpener,207
hasItem: bool,208
}210
function topFrame(frames: Array<LayoutFrame>) -> LayoutFrame {211
frames[frames.length - 1]212
}214
record 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,223
}225
function markFrameItem(state: LayoutState) -> () {226
let mut frames = state.frames227
let frame = topFrame(frames)228
frames.pop()229
frames.push(LayoutFrame { ...frame, hasItem: true })230
}232
function clearConcurrentAtDepth(state: LayoutState, depth: int) -> () {233
let mut depths = state.concurrentDepths234
while depths.length > 0 && depths[depths.length - 1] == depth {235
depths.pop()236
}237
()238
}240
function resetStatementContext(state: LayoutState, frameDepth: int) -> LayoutState {241
clearConcurrentAtDepth(state, frameDepth)242
LayoutState { ...state, moduleItem: false, pattern: None }243
}245
function 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
state265
}267
function 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.diagnostics274
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.output283
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
}290
}292
function flushPendingSeparator(293
state: LayoutState,294
current: PreviewToken,295
frameDepth: int,296
) -> LayoutState {297
let pending = match state.pending {298
case Some(value) => value299
case None => return state300
}301
let currentPipeContinues = current.kind == "pipe" &&302
patternContinuesAt(state.pattern, frameDepth)303
let mut next = state304
if !isLeading(current.kind) && !currentPipeContinues &&305
!isCloser(current.kind) && current.kind != "eof" {306
let mut output = state.output307
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 }316
}318
function 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 state330
}331
if index + 1 < tokens.length && tokens[index + 1].kind == "identifier" {332
LayoutState { ...state, moduleItem: true }333
} else {334
state335
}336
}338
function closePatternIfTerminated(339
state: LayoutState,340
current: PreviewToken,341
frameDepth: int,342
) -> LayoutState {343
let pattern = match state.pattern {344
case Some(value) => value345
case None => return state346
}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
state358
}359
}361
function openerKind(kind: string) -> LayoutOpener {362
if kind == "left-paren" || kind == "right-paren" {363
LayoutOpener.Paren364
} else if kind == "left-bracket" || kind == "right-bracket" {365
LayoutOpener.Bracket366
} else if kind == "left-brace" || kind == "right-brace" {367
LayoutOpener.Brace368
} else {369
LayoutOpener.Interpolation370
}371
}373
function openContinuationFrame(state: LayoutState, current: PreviewToken) -> () {374
markFrameItem(state)375
let mut frames = state.frames376
frames.push(LayoutFrame {377
mode: LayoutMode.Continuation,378
opener: openerKind(current.kind),379
hasItem: false,380
})381
()382
}384
function 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.frames398
frames.push(LayoutFrame { mode: mode, opener: LayoutOpener.Brace, hasItem: false })399
()400
}402
function closeFrame(state: LayoutState, current: PreviewToken) -> LayoutState {403
let mut next = state404
if topFrame(state.frames).opener == openerKind(current.kind) {405
let mut frames = state.frames406
let mut concurrentDepths = state.concurrentDepths407
frames.pop()408
let newDepth = frames.length409
let mut trimming = true410
while concurrentDepths.length > 0 && trimming {411
if concurrentDepths[concurrentDepths.length - 1] > newDepth {412
concurrentDepths.pop()413
} else {414
trimming = false415
}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
next428
}430
function 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
}440
}442
function 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
state454
} else if current.kind == "left-brace" {455
openBraceFrame(state, tokens, index, frameDepth)456
state457
} else if isCloser(current.kind) {458
closeFrame(state, current)459
} else if current.kind == "keyword/concurrent" && !keywordField {460
let mut concurrentDepths = state.concurrentDepths461
concurrentDepths.push(frameDepth)462
markFrameItem(state)463
state464
} 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
state474
}475
}477
export 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 = 0494
while index < tokens.length {495
let current = tokens[index]496
let frameDepth = state.frames.length497
if current.kind == "newline" {498
state = observeNewline(state, current, frameDepth)499
index += 1500
continue501
}502
if current.kind == "semicolon" {503
state = consumeSemicolon(state, current, frameDepth)504
index += 1505
continue506
}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.output512
output.push(copyToken(current))513
state = LayoutState { ...state, previous: Some(current.kind) }514
index += 1515
}516
LayoutResult { tokens: state.output, diagnostics: state.diagnostics }517
}SHA256 196383e57db0ad8b51c67f216f5cec15e260c705229e154b75867af83bfd5580