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/resolver/modules_tables.tpz
Topaz · 12470 B · Original file
1
// Import drafts and module binding and export tables.2
// Graph discovery provides parsed roots; scope walking consumes the canonical3
// table catalog produced here.5
import types {6
PreviewResolvedDiagnostic,7
PreviewResolvedExport,8
SyntaxNode,9
}10
import resolver.model_facts {11
Binding,12
ExportBinding,13
ImportDraft,14
ModuleDraft,15
ModuleTable,16
ModuleTableCatalog,17
boolAttribute,18
child,19
children,20
contains,21
diagnostic,22
isNone,23
stringJoin,24
text,25
}27
export function importDrafts(root: SyntaxNode, source: Bytes) -> Array<ImportDraft> {28
let mut output: Array<ImportDraft> = []29
for statement in root.children {30
if statement.kind != "statement/import" {31
continue32
}33
let itemOption = child(statement, "import")34
if isNone(itemOption) {35
continue36
}37
let item = match itemOption {38
case Some(value) => value39
case None => statement40
}41
let pathOption = child(item, "path")42
if isNone(pathOption) {43
continue44
}45
let path = match pathOption {46
case Some(value) => value47
case None => item48
}49
let mut segments: Array<string> = []50
for segment in children(path, "segments") {51
segments.push(text(source, segment))52
}53
let mut selectedNames: Array<string> = []54
let mut selectedLos: Array<int> = []55
let mut selectedHis: Array<int> = []56
for spec in children(item, "specs") {57
let nameNode = child(spec, "name")58
let name = match nameNode {59
case Some(value) => text(source, value)60
case None => ""61
}62
selectedNames.push(name)63
selectedLos.push(match nameNode {64
case Some(value) => value.lo65
case None => spec.lo66
})67
selectedHis.push(match nameNode {68
case Some(value) => value.hi69
case None => spec.hi70
})71
}72
output.push(ImportDraft {73
target: stringJoin(segments, "."),74
segments: segments,75
pathLo: path.lo,76
pathHi: path.hi,77
selectedNames: selectedNames,78
selectedLos: selectedLos,79
selectedHis: selectedHis,80
})81
}82
output83
}85
export function collectPatternBindings(86
pattern: SyntaxNode,87
source: Bytes,88
output: Array<SyntaxNode>,89
) -> () {90
let mut target = output91
if pattern.kind == "pattern/binding" || pattern.kind == "pattern/typed" {92
match child(pattern, "name") {93
case Some(name) => target.push(name)94
case None => ()95
}96
return ()97
}98
if pattern.kind == "record-pattern-field" {99
match child(pattern, "pattern") {100
case Some(value) => collectPatternBindings(value, source, output)101
case None => match child(pattern, "name") {102
case Some(name) => target.push(name)103
case None => ()104
}105
}106
return ()107
}108
let mut alternatives: Array<string> = []109
for found in pattern.children {110
if pattern.kind == "pattern/or" && found.field == "alternatives" {111
let mut local: Array<SyntaxNode> = []112
collectPatternBindings(found, source, local)113
let mut seenHere: Array<string> = []114
for binding in local {115
let name = text(source, binding)116
let repeatedHere = contains(seenHere, name)117
let firstAlternative = !contains(alternatives, name)118
if repeatedHere || firstAlternative {119
target.push(binding)120
}121
if !repeatedHere {122
seenHere.push(name)123
}124
if firstAlternative {125
alternatives.push(name)126
}127
}128
} else if found.kind.startsWith("pattern/") ||129
found.kind == "record-pattern-field" ||130
found.kind == "list-pattern/rest" {131
collectPatternBindings(found, source, output)132
}133
()134
}135
()136
}138
export function unwrapExport(statement: SyntaxNode) -> SyntaxNode {139
if statement.kind == "statement/export" {140
return match child(statement, "item") {141
case Some(value) => value142
case None => statement143
}144
}145
statement146
}148
function importBindings(149
moduleIndexValue: int,150
item: SyntaxNode,151
source: Bytes,152
) -> Array<Binding> {153
let mut output: Array<Binding> = []154
let importItem = match child(item, "import") {155
case Some(value) => value156
case None => return output157
}158
let path = match child(importItem, "path") {159
case Some(value) => value160
case None => return output161
}162
let mut segments: Array<string> = []163
for segment in children(path, "segments") {164
segments.push(text(source, segment))165
}166
let target = stringJoin(segments, ".")167
let specs = children(importItem, "specs")168
if specs.length == 0 {169
let nameNode = match child(importItem, "alias") {170
case Some(value) => value171
case None => match path.children.get(path.children.length - 1) {172
case Some(value) => value173
case None => return output174
}175
}176
output.push(Binding {177
moduleIndex: moduleIndexValue,178
scopeOrdinal: 0,179
name: text(source, nameNode),180
namespace: "module",181
declarationKind: "namespace-import",182
lo: nameNode.lo,183
hi: nameNode.hi,184
exported: false,185
targetModule: target,186
targetName: "",187
mutable: false,188
})189
return output190
}191
for spec in specs {192
let sourceName = match child(spec, "name") {193
case Some(value) => text(source, value)194
case None => ""195
}196
let bound = match child(spec, "alias") {197
case Some(value) => Some(value)198
case None => child(spec, "name")199
}200
match bound {201
case Some(name) => output.push(Binding {202
moduleIndex: moduleIndexValue,203
scopeOrdinal: 0,204
name: text(source, name),205
namespace: "value",206
declarationKind: "selected-import",207
lo: name.lo,208
hi: name.hi,209
exported: false,210
targetModule: target,211
targetName: sourceName,212
mutable: false,213
})214
case None => ()215
}216
}217
output218
}220
function namedStatementBinding(221
moduleIndexValue: int,222
item: SyntaxNode,223
source: Bytes,224
exported: bool,225
) -> Option<Binding> {226
let kind = match item.kind {227
case "statement/function" => "function"228
case "statement/type-alias" => "type-alias"229
case "statement/enum" | "statement/record" | "statement/newtype" =>230
"nominal-type"231
case "statement/const" => "const"232
case _ => return None233
}234
let holder = if item.kind == "statement/function" {235
match child(item, "declaration") {236
case Some(value) => value237
case None => item238
}239
} else {240
item241
}242
match child(holder, "name") {243
case Some(name) => Some(Binding {244
moduleIndex: moduleIndexValue,245
scopeOrdinal: 0,246
name: text(source, name),247
namespace: if kind == "type-alias" || kind == "nominal-type" {248
"type"249
} else {250
"value"251
},252
declarationKind: kind,253
lo: name.lo,254
hi: name.hi,255
exported: exported,256
targetModule: "",257
targetName: "",258
mutable: false,259
})260
case None => None261
}262
}264
function letBindings(265
moduleIndexValue: int,266
item: SyntaxNode,267
source: Bytes,268
exported: bool,269
) -> Array<Binding> {270
let mut output: Array<Binding> = []271
let pattern = match child(item, "pattern") {272
case Some(value) => value273
case None => return output274
}275
let mut names: Array<SyntaxNode> = []276
collectPatternBindings(pattern, source, names)277
let mutable = boolAttribute(item, "mutable")278
for name in names {279
output.push(Binding {280
moduleIndex: moduleIndexValue,281
scopeOrdinal: 0,282
name: text(source, name),283
namespace: "value",284
declarationKind: "let",285
lo: name.lo,286
hi: name.hi,287
exported: exported && !mutable,288
targetModule: "",289
targetName: "",290
mutable: mutable,291
})292
}293
output294
}296
export function bindingForStatement(297
moduleIndexValue: int,298
statement: SyntaxNode,299
source: Bytes,300
exported: bool,301
) -> Array<Binding> {302
let item = unwrapExport(statement)303
if item.kind == "statement/import" {304
return importBindings(moduleIndexValue, item, source)305
}306
match namedStatementBinding(moduleIndexValue, item, source, exported) {307
case Some(binding) => return [binding]308
case None => ()309
}310
if item.kind == "statement/let" {311
return letBindings(moduleIndexValue, item, source, exported)312
}313
[]314
}316
function buildTable(317
moduleIndexValue: int,318
module: ModuleDraft,319
diagnostics: Array<PreviewResolvedDiagnostic>,320
) -> ModuleTable {321
let source = module.source322
let mut bindings: Array<Binding> = []323
let mut bindingIndexes: Map<string, int> = Map.new()324
let mut exportedBindings: Map<string, ExportBinding> = Map.new()325
for statement in module.root.children {326
let exported = statement.kind == "statement/export"327
for binding in bindingForStatement(moduleIndexValue, statement, source, exported) {328
let existingIndex = bindingIndexes.get(binding.name)329
if exported && binding.declarationKind == "let" && binding.mutable {330
diagnostic(331
diagnostics,332
moduleIndexValue,333
"TPZ3011",334
"`export let mut` is a static error: exported bindings are immutable views",335
binding.lo,336
binding.hi,337
)338
}339
if binding.exported {340
let exportIndex = match existingIndex {341
case Some(index) => index342
case None => bindings.length343
}344
exportedBindings.insert(binding.name, ExportBinding {345
bindingIndex: exportIndex,346
namespace: binding.namespace,347
})348
}349
match existingIndex {350
case Some(index) => {351
let firstLo = bindings[index].lo352
diagnostic(353
diagnostics,354
moduleIndexValue,355
"TPZ3008",356
"`{binding.name}` is already bound at this module's top level (first binding at byte {firstLo})",357
binding.lo,358
binding.hi,359
)360
}361
case None => {362
let bindingIndexValue = bindings.length363
bindingIndexes.insert(binding.name, bindingIndexValue)364
bindings.push(binding)365
}366
}367
}368
}369
let mut exportedNames: Array<string> = []370
for binding in bindings {371
if !isNone(exportedBindings.get(binding.name)) {372
exportedNames.push(binding.name)373
}374
}375
exportedNames.sort()376
ModuleTable {377
moduleIndex: moduleIndexValue,378
identity: module.result.identity,379
bindings: bindings,380
bindingIndexes: bindingIndexes,381
exportedBindings: exportedBindings,382
exportedNames: exportedNames,383
}384
}386
export function catalogTable(387
catalog: ModuleTableCatalog,388
identity: string,389
) -> Option<ModuleTable> {390
match catalog.indexes.get(identity) {391
case Some(index) => Some(catalog.tables[index])392
case None => None393
}394
}396
export function tableBinding(table: ModuleTable, name: string) -> Option<Binding> {397
match table.bindingIndexes.get(name) {398
case Some(index) => Some(table.bindings[index])399
case None => None400
}401
}403
export function exportedBinding(table: ModuleTable, name: string) -> Option<ExportBinding> {404
table.exportedBindings.get(name)405
}407
export function exportedNames(408
table: ModuleTable,409
namespace: string,410
) -> Array<string> {411
if namespace == "" {412
return table.exportedNames413
}414
let mut candidates: Array<string> = []415
for name in table.exportedNames {416
let namespaceValue = match exportedBinding(table, name) {417
case Some(binding) => binding.namespace418
case None => ""419
}420
if (namespace == "type" && namespaceValue == "type") ||421
(namespace == "value" && namespaceValue != "type") {422
candidates.push(name)423
}424
}425
candidates426
}428
export function effectiveNamespace(binding: Binding, tables: ModuleTableCatalog) -> string {429
if binding.declarationKind != "selected-import" {430
return binding.namespace431
}432
let targetTable = match catalogTable(tables, binding.targetModule) {433
case Some(found) => found434
case None => return binding.namespace435
}436
match exportedBinding(targetTable, binding.targetName) {437
case Some(found) => found.namespace438
case None => binding.namespace439
}440
}442
export function buildModuleTables(443
modules: Array<ModuleDraft>,444
diagnostics: Array<PreviewResolvedDiagnostic>,445
) -> ModuleTableCatalog {446
let mut tables: Array<ModuleTable> = []447
let mut indexes: Map<string, int> = Map.new()448
let mut index = 0449
for module in modules {450
indexes.insert(module.result.identity, index)451
tables.push(buildTable(index, module, diagnostics))452
index += 1453
}454
ModuleTableCatalog { tables: tables, indexes: indexes }455
}SHA256 14157005ccd44d84bb0a23c21225e5b6b3368c7b723cec195dbd2e9336f3787d