diff --git a/src/ast/base_language.nim b/src/ast/base_language.nim index b9e87e1e..db1f55dd 100644 --- a/src/ast/base_language.nim +++ b/src/ast/base_language.nim @@ -1,7 +1,10 @@ +import std/[tables, strformat] import id, ast_ids, util, custom_logger import model, cells import ui/node +logCategory "base-language" + let typeClass* = newNodeClass(IdType, "Type", isAbstract=true) let stringTypeClass* = newNodeClass(IdString, "StringType", alias="string", base=typeClass) let intTypeClass* = newNodeClass(IdInt, "IntType", alias="int", base=typeClass) @@ -431,6 +434,87 @@ builder.addBuilderFor buildExpressionClass.id, idNone(), proc(builder: CellBuild return cell +var typeComputers = initTable[ClassId, proc(ctx: ModelComputationContextBase, node: AstNode): AstNode]() + +let stringTypeInstance* = newAstNode(stringTypeClass) +let intTypeInstance* = newAstNode(intTypeClass) +let voidTypeInstance* = newAstNode(voidTypeClass) + +# todo: those should technically return something like typeTypeInstance which needs a new typeTypeClass +# and the valueComputer should return the type instance +typeComputers[stringTypeClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for string type literal {node}" + return stringTypeInstance +typeComputers[intTypeClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for int type literal {node}" + return intTypeInstance +typeComputers[voidTypeClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for void type literal {node}" + return voidTypeInstance + +# literals +typeComputers[stringLiteralClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for string type literal {node}" + return stringTypeInstance + +typeComputers[numberLiteralClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for int type literal {node}" + return intTypeInstance + +typeComputers[boolLiteralClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for bool type literal {node}" + return intTypeInstance + +# declarations +typeComputers[letDeclClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for let decl {node}" + return voidTypeInstance + +typeComputers[varDeclClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for var decl {node}" + return voidTypeInstance + +typeComputers[constDeclClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for const decl {node}" + return voidTypeInstance + +# control flow +typeComputers[whileClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for while loop {node}" + return voidTypeInstance + +typeComputers[ifClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for if expr {node}" + return voidTypeInstance + +# function definition +typeComputers[functionDefinitionClass.id] = proc(ctx: ModelComputationContextBase, node: AstNode): AstNode = + debugf"compute type for function definition {node}" + var returnType = voidTypeInstance + + if node.firstChild(IdFunctionDefinitionReturnType).getSome(returnTypeNode): + returnType = ctx.computeType(returnTypeNode) + + var functionType = newAstNode(functionTypeClass) + functionType.add(IdFunctionTypeReturnType, returnType) + + for _, c in node.children(IdFunctionDefinitionParameters): + if c.firstChild(IdParameterDeclType).getSome(paramTypeNode): + # todo: This needs computeValue in the future since the type of a type is 'type', and the value is 'int' or 'string' etc. + var parameterType = ctx.computeType(paramTypeNode) + if parameterType.isNil: + # addDiagnostic(paramTypeNode, "Could not compute type for parameter") + continue + functionType.add(IdFunctionTypeParameterTypes, parameterType) + + functionType.model = node.model + functionType.forEach2 n: + n.model = node.model + + echo fmt"computed function type: {`$`(functionType, true)}" + + return functionType + let baseLanguage* = newLanguage(IdBaseLanguage, @[ namedInterface, declarationInterface, @@ -444,6 +528,6 @@ let baseLanguage* = newLanguage(IdBaseLanguage, @[ lessExpressionClass, lessEqualExpressionClass, greaterExpressionClass, greaterEqualExpressionClass, equalExpressionClass, notEqualExpressionClass, andExpressionClass, orExpressionClass, orderExpressionClass, negateExpressionClass, notExpressionClass, appendStringExpressionClass, printExpressionClass, buildExpressionClass, -], builder) +], builder, typeComputers) # print baseLanguage diff --git a/src/ast/base_language_wasm.nim b/src/ast/base_language_wasm.nim index ebdf7ead..2d1892a9 100644 --- a/src/ast/base_language_wasm.nim +++ b/src/ast/base_language_wasm.nim @@ -1,8 +1,7 @@ import std/[options, tables] -import id, model, ast_ids +import id, model, ast_ids, custom_logger, util, model_state import scripting/[wasm_builder] -import custom_logger, util logCategory "base-language-wasm" @@ -67,11 +66,11 @@ proc getOrCreateWasmFunc(self: BaseLanguageWasmCompiler, node: AstNode, exportNa if not self.wasmFuncs.contains(node.id): var inputs, outputs: seq[WasmValueType] - for c in node.children(IdFunctionDefinitionParameters): + for _, c in node.children(IdFunctionDefinitionParameters): let typ = c.children(IdParameterDeclType)[0] inputs.add typ.toWasmValueType - for c in node.children(IdFunctionDefinitionReturnType): + for _, c in node.children(IdFunctionDefinitionReturnType): outputs.add c.toWasmValueType let funcIdx = self.builder.addFunction(inputs, outputs, exportName=exportName) diff --git a/src/ast/model.nim b/src/ast/model.nim index 62b2dfe4..166f2dca 100644 --- a/src/ast/model.nim +++ b/src/ast/model.nim @@ -135,6 +135,8 @@ type NodeValidator* = ref object propertyValidators*: ArrayTable[RoleId, PropertyValidator] + ModelComputationContextBase* = ref object of RootObj + Language* = ref object id {.getter.}: LanguageId version {.getter.}: int @@ -144,6 +146,9 @@ type validators: Table[ClassId, NodeValidator] + # functions for computing the type of a node + typeComputers*: Table[ClassId, proc(ctx: ModelComputationContextBase, node: AstNode): AstNode] + Model* = ref object id {.getter.}: ModelId rootNodes {.getter.}: seq[AstNode] @@ -165,6 +170,10 @@ generateGetters(NodeClass) generateGetters(Model) generateGetters(Language) +proc hash*(node: AstNode): Hash = node.id.hash + +method computeType*(self: ModelComputationContextBase, node: AstNode): AstNode {.base.} = discard + proc notifyNodeDeleted(self: Model, parent: AstNode, child: AstNode, role: RoleId, index: int) = self.onNodeDeleted.invoke (self, parent, child, role, index) @@ -212,9 +221,11 @@ proc verify*(self: Language): bool = log(lvlError, fmt"Class {c.name} is both final and abstract") result = false -proc newLanguage*(id: LanguageId, classes: seq[NodeClass], builder: CellBuilder): Language = +proc newLanguage*(id: LanguageId, classes: seq[NodeClass], builder: CellBuilder, typeComputers: Table[ClassId, proc(ctx: ModelComputationContextBase, node: AstNode): AstNode]): Language = new result result.id = id + result.typeComputers = typeComputers + for c in classes: result.classes[c.id] = c @@ -392,6 +403,21 @@ proc children*(node: AstNode, role: RoleId): seq[AstNode] = result = c.nodes break +iterator children*(node: AstNode, role: RoleId): (int, AstNode) = + for c in node.childLists.mitems: + if c.role == role: + for i, node in c.nodes: + yield (i, node) + break + +proc firstChild*(node: AstNode, role: RoleId): Option[AstNode] = + result = AstNode.none + for c in node.childLists.mitems: + if c.role == role: + if c.nodes.len > 0: + result = c.nodes[0].some + break + proc hasReference*(node: AstNode, role: RoleId): bool = result = false for c in node.references: @@ -766,6 +792,8 @@ method dump*(self: EmptyCell, recurse: bool = false): string = result.add fmt"EmptyCell(node: {self.node.id})" proc `$`*(node: AstNode, recurse: bool = false): string = + if node.isNil: + return "AstNode(nil)" let class = node.nodeClass if class.isNil: diff --git a/src/ast/model_state.nim b/src/ast/model_state.nim new file mode 100644 index 00000000..b62c1983 --- /dev/null +++ b/src/ast/model_state.nim @@ -0,0 +1,197 @@ +import std/[tables, sets, strutils, hashes, options, strformat] +import fusion/matching +import vmath +import lrucache +import id, util, timer, query_system, custom_logger, model + +logCategory "model-state" + +type + Diagnostic* = object + message*: string + + Diagnostics* = object + queries*: Table[Dependency, seq[Diagnostic]] + +func fingerprint*(node: AstNode): Fingerprint + +CreateContext ModelState: + input AstNode + + # var globalScope*: Table[Id, Symbol] = initTable[Id, Symbol]() + var enableQueryLogging*: bool = false + var enableExecutionLogging*: bool = false + var diagnosticsPerNode*: Table[Id, Diagnostics] = initTable[Id, Diagnostics]() + var diagnosticsPerQuery*: Table[Dependency, seq[Id]] = initTable[Dependency, seq[Id]]() + var computationContextOwner: RootRef = nil + + proc recoverType(ctx: ModelState, key: Dependency) {.recover("Type").} + # proc recoverValue(ctx: ModelState, key: Dependency) {.recover("Value").} + + proc computeTypeImpl(ctx: ModelState, node: AstNode): AstNode {.query("Type").} + # proc computeValueImpl(ctx: ModelState, node: AstNode): Value {.query("Value").} + +type ModelComputationContext* = ref object of ModelComputationContextBase + state*: ModelState + +proc newModelComputationContext*(): ModelComputationContext = + result = new(ModelComputationContext) + result.state = newModelState() + result.state.computationContextOwner = result + +method computeType*(self: ModelComputationContext, node: AstNode): AstNode = + return self.state.computeType(node) + +func fingerprint*(node: AstNode): Fingerprint = + if node.isNil: + return @[] + let (a, b, c) = node.id.deconstruct + result = @[a.int64, b.int64, c.int64] + +template logIf(condition: bool, message: string, logResult: bool) = + let logQuery = condition + if logQuery: inc currentIndent, 1 + defer: + if logQuery: dec currentIndent, 1 + if logQuery: echo repeat2("| ", currentIndent - 1), message + defer: + if logQuery and logResult: + echo repeat2("| ", currentIndent) & "-> " & $result + +template enableDiagnostics(key: untyped): untyped = + # Delete old diagnostics + if ctx.diagnosticsPerQuery.contains(key): + for id in ctx.diagnosticsPerQuery[key]: + ctx.diagnosticsPerNode[id].queries.del key + + var diagnostics: seq[Diagnostic] = @[] + var ids: seq[Id] = @[] + defer: + if diagnostics.len > 0: + ctx.diagnosticsPerQuery[key] = ids + + for i in 0..ids.high: + let id = ids[i] + let diag = diagnostics[i] + if not ctx.diagnosticsPerNode.contains(id): + ctx.diagnosticsPerNode[id] = Diagnostics() + if not ctx.diagnosticsPerNode[id].queries.contains(key): + ctx.diagnosticsPerNode[id].queries[key] = @[] + + ctx.diagnosticsPerNode[id].queries[key].add diag + else: + ctx.diagnosticsPerQuery.del key + + template addDiagnostic(id: Id, msg: untyped) {.used.} = + ids.add(id) + diagnostics.add Diagnostic(message: msg) + +# proc notifySymbolChanged*(ctx: ModelState, sym: Symbol) = +# ctx.depGraph.revision += 1 +# ctx.depGraph.changed[(sym.getItem, -1)] = ctx.depGraph.revision +# log(lvlInfo, fmt"Invalidating symbol {sym.name} ({sym.id})") + +proc insertNode*(ctx: ModelState, node: AstNode) = + ctx.depGraph.revision += 1 + + if node.parent != nil: + ctx.depGraph.changed[(node.parent.getItem, -1)] = ctx.depGraph.revision + + proc insertNodeRec(ctx: ModelState, node: AstNode) = + let item = node.getItem + ctx.depGraph.changed[(item, -1)] = ctx.depGraph.revision + + ctx.itemsAstNode[item] = node + + for children in node.childLists: + for child in children.nodes: + ctx.insertNodeRec(child) + + ctx.insertNodeRec(node) + + # var parent = node.parent + # while parent != nil and parent.findWithParentRec(FunctionDefinition).getSome(child): + # let functionDefinition = child.parent + # ctx.depGraph.changed[(functionDefinition.getItem, -1)] = ctx.depGraph.revision + # parent = functionDefinition.parent + +proc updateNode*(ctx: ModelState, node: AstNode) = + ctx.depGraph.revision += 1 + ctx.depGraph.changed[(node.getItem, -1)] = ctx.depGraph.revision + + # var parent = node.parent + # while parent != nil and parent.findWithParentRec(FunctionDefinition).getSome(child): + # let functionDefinition = child.parent + # ctx.depGraph.changed[(functionDefinition.getItem, -1)] = ctx.depGraph.revision + # parent = functionDefinition.parent + + log(lvlInfo, fmt"Invalidating node {node}") + +proc deleteNode*(ctx: ModelState, node: AstNode, recurse: bool) = + ctx.depGraph.revision += 1 + + if node.parent != nil: + ctx.depGraph.changed[(node.parent.getItem, -1)] = ctx.depGraph.revision + + proc deleteNodeRec(ctx: ModelState, node: AstNode, recurse: bool) = + if recurse: + for children in node.childLists: + for child in children.nodes: + ctx.deleteNodeRec(child, recurse) + + let item = node.getItem + ctx.depGraph.changed.del((item, -1)) + + # Remove diagnostics added by the removed node + for i, update in ctx.updateFunctions: + let key = (item, i) + if ctx.diagnosticsPerQuery.contains(key): + for id in ctx.diagnosticsPerQuery[key]: + ctx.diagnosticsPerNode[id].queries.del key + ctx.diagnosticsPerQuery.del(key) + + ctx.deleteNodeRec(node, recurse) + + # var parent = node.parent + # while parent != nil and parent.findWithParentRec(FunctionDefinition).getSome(child): + # let functionDefinition = child.parent + # ctx.depGraph.changed[(functionDefinition.getItem, -1)] = ctx.depGraph.revision + # parent = functionDefinition.parent + +proc deleteAllNodesAndSymbols*(ctx: ModelState) = + ctx.depGraph.revision += 1 + ctx.depGraph.changed.clear + ctx.depGraph.verified.clear + ctx.depGraph.fingerprints.clear + ctx.depGraph.dependencies.clear + ctx.itemsAstNode.clear + # ctx.itemsSymbol.clear + ctx.queryCacheType.clear + # ctx.queryCacheValue.clear + +proc recoverType(ctx: ModelState, key: Dependency) = + log(lvlInfo, fmt"Recovering type for {key}") + if ctx.getAstNode(key.item.id).getSome(node): + # ctx.queryCacheType[node] = errorType() + discard + +proc computeTypeImpl(ctx: ModelState, node: AstNode): AstNode = + logIf(ctx.enableLogging or ctx.enableQueryLogging, "computeTypeImpl " & $node, true) + + let key: Dependency = ctx.getTypeKey(node.getItem) + enableDiagnostics(key) + + let language = node.language + if language.isNil: + addDiagnostic(node.id, fmt"Node has no language: {node}") + return nil + + if not language.typeComputers.contains(node.class): + addDiagnostic(node.id, fmt"Node has no type computer: {node}") + return nil + + let typeComputer = language.typeComputers[node.class] + + let typ = typeComputer(ctx.computationContextOwner.ModelComputationContext, node) + + return typ diff --git a/src/model_document.nim b/src/model_document.nim index e180b3d1..84dfe48f 100644 --- a/src/model_document.nim +++ b/src/model_document.nim @@ -8,7 +8,7 @@ import workspaces/[workspace] import ast/[model, base_language, cells] import ui/node -import ast/base_language_wasm +import ast/[base_language_wasm, model_state] var project = newProject() @@ -114,6 +114,7 @@ type ModelDocument* = ref object of Document model*: Model project*: Project + ctx*: ModelComputationContext currentTransaction: ModelTransaction undoList: seq[ModelTransaction] @@ -123,7 +124,6 @@ type onFinishedUndoTransaction*: Event[(ModelDocument, ModelTransaction)] onFinishedRedoTransaction*: Event[(ModelDocument, ModelTransaction)] - builder*: CellBuilder ModelCompletionKind* {.pure.} = enum @@ -252,6 +252,7 @@ proc newModelDocument*(filename: string = "", app: bool = false, workspaceFolder testModel.addRootNode(c) result.model = testModel + result.ctx = newModelComputationContext() result.builder = newCellBuilder() for language in result.model.languages: @@ -458,6 +459,9 @@ proc loadAsync*(self: ModelDocument): Future[void] {.async.} = self.undoList.setLen 0 self.redoList.setLen 0 + for rootNode in self.model.rootNodes: + self.ctx.state.insertNode(rootNode) + except CatchableError: log lvlError, fmt"Failed to load model source file '{self.filename}': {getCurrentExceptionMsg()}" @@ -2514,6 +2518,12 @@ proc runSelectedFunctionAsync*(self: ModelDocumentEditor): Future[void] {.async. log(lvlError, fmt"Not inside function") return + block: # todo + self.document.ctx.state.updateNode(function.get) + + let typ = self.document.ctx.computeType(function.get) + echo `$`(typ, true) + if function.get.childCount(IdFunctionDefinitionParameters) > 0: log(lvlError, fmt"Can't call function with parameters") return