# SPDX-License-Identifier: LUL-1.0 # Copyright (c) 2026 Markus Maiwald # Stewardship: Self Sovereign Society Foundation # # This file is part of the Nexus SDK. # See legal/LICENSE_UNBOUND.md for license terms. # MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) # NipBox KDL Core (The Semantic Spine) # Defines the typed object system for the Sovereign Shell. import strutils import std/assertions type ValueKind* = enum VString, VInt, VBool, VNull Value* = object case kind*: ValueKind of VString: s*: string of VInt: i*: int of VBool: b*: bool of VNull: discard # A KDL Node: name arg1 arg2 key=val { children } Node* = ref object name*: string args*: seq[Value] props*: seq[tuple[key: string, val: Value]] children*: seq[Node] # --- Constructors --- proc newVal*(s: string): Value = Value(kind: VString, s: s) proc newVal*(i: int): Value = Value(kind: VInt, i: i) proc newVal*(b: bool): Value = Value(kind: VBool, b: b) proc newNull*(): Value = Value(kind: VNull) proc newNode*(name: string): Node = new(result) result.name = name result.args = @[] result.props = @[] result.children = @[] proc addArg*(n: Node, v: Value) = n.args.add(v) proc addProp*(n: Node, key: string, v: Value) = n.props.add((key, v)) proc addChild*(n: Node, child: Node) = n.children.add(child) # --- Serialization (The Renderer) --- proc `$`*(v: Value): string = case v.kind of VString: "\"" & v.s & "\"" # TODO: Escape quotes properly of VInt: $v.i of VBool: $v.b of VNull: "null" proc render*(n: Node, indent: int = 0): string = let prefix = repeat(' ', indent) var line = prefix & n.name # Args for arg in n.args: line.add(" " & $arg) # Props for prop in n.props: line.add(" " & prop.key & "=" & $prop.val) # Children if n.children.len > 0: line.add(" {\n") for child in n.children: line.add(render(child, indent + 2)) line.add(prefix & "}\n") else: line.add("\n") return line # Table View (For Flat Lists) proc renderTable*(nodes: seq[Node]): string = var s = "" for n in nodes: s.add(render(n)) return s # --- Parser --- type Parser = ref object input: string pos: int proc peek(p: Parser): char = if p.pos >= p.input.len: return '\0' return p.input[p.pos] proc next(p: Parser): char = if p.pos >= p.input.len: return '\0' result = p.input[p.pos] p.pos.inc proc skipSpace(p: Parser) = while true: let c = p.peek() if c == ' ' or c == '\t' or c == '\r': discard p.next() else: break proc parseIdentifier(p: Parser): string = # Simple identifier: strictly alphanumeric + _ - for now # TODO: Quoted identifiers if p.peek() == '"': discard p.next() while true: let c = p.next() if c == '\0': break if c == '"': break result.add(c) else: while true: let c = p.peek() if c in {'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', '/'}: result.add(p.next()) else: break proc parseValue(p: Parser): Value = skipSpace(p) let c = p.peek() if c == '"': # String discard p.next() var s = "" while true: let ch = p.next() if ch == '\0': break if ch == '"': break s.add(ch) return newVal(s) elif c in {'0'..'9', '-'}: # Number (Int only for now) var s = "" s.add(p.next()) while p.peek() in {'0'..'9'}: s.add(p.next()) try: return newVal(parseInt(s)) except: return newVal(0) elif c == 't': # true if p.input.substr(p.pos, p.pos+3) == "true": p.pos += 4 return newVal(true) elif c == 'f': # false if p.input.substr(p.pos, p.pos+4) == "false": p.pos += 5 return newVal(false) elif c == 'n': # null if p.input.substr(p.pos, p.pos+3) == "null": p.pos += 4 return newNull() # Fallback: Bare string identifier return newVal(parseIdentifier(p)) proc parseNode(p: Parser): Node = skipSpace(p) let name = parseIdentifier(p) if name.len == 0: return nil var node = newNode(name) while true: skipSpace(p) let c = p.peek() if c == '\n' or c == ';' or c == '}' or c == '\0': break if c == '{': break # Children start # Arg or Prop? # Peek ahead to see if next is identifier=value # Simple heuristic: parse identifier, if next char is '=', it's a prop. let startPos = p.pos let id = parseIdentifier(p) if id.len > 0 and p.peek() == '=': # Property discard p.next() # skip = let val = parseValue(p) node.addProp(id, val) else: # Argument # Backtrack? Or realize we parsed a value? # If `id` was a bare string value, it works. # If `id` was quoted string, `parseIdentifier` handled it. # But `parseValue` handles numbers/bools too. `parseIdentifier` does NOT. # Better approach: # Reset pos p.pos = startPos # Check if identifier followed by = # We need a proper lookahead for keys. # For now, simplistic: let val = parseValue(p) # Check if we accidentally parsed a key? # If val is string, and next char is '=', convert to key? if val.kind == VString and p.peek() == '=': discard p.next() let realVal = parseValue(p) node.addProp(val.s, realVal) else: node.addArg(val) # Children skipSpace(p) if p.peek() == '{': discard p.next() # skip { while true: skipSpace(p) if p.peek() == '}': discard p.next() break skipSpace(p) # Skip newlines while p.peek() == '\n': discard p.next() if p.peek() == '}': discard p.next() break let child = parseNode(p) if child != nil: node.addChild(child) else: # Check if just newline? if p.peek() == '\n': discard p.next() else: break # Error or empty return node proc parseKdl*(input: string): seq[Node] = var p = Parser(input: input, pos: 0) result = @[] while true: skipSpace(p) while p.peek() == '\n' or p.peek() == ';': discard p.next() if p.peek() == '\0': break let node = parseNode(p) if node != nil: result.add(node) else: break