Files
luma/Sources/LumaCore/Patterns/PatternPath.swift
Ole André Vadla Ravnås bbf63ec269 core: Expose patterns to LLMs
Let a model list, check and write patterns, decode memory with them,
and place them on insights.
2026-10-01 20:49:11 +02:00

129 lines
4.0 KiB
Swift

import Foundation
public struct PatternPath: Sendable {
public let steps: [Step]
public enum Step: Sendable {
case field(String)
case element(Int)
}
public init(_ text: String) throws {
var steps: [Step] = []
var name = ""
var index: String?
func flushName() {
if !name.isEmpty {
steps.append(.field(name))
name = ""
}
}
for character in text {
switch character {
case ".":
flushName()
case "[":
flushName()
index = ""
case "]":
guard let digits = index, let element = Int(digits) else { throw PatternPathError.malformed(text) }
steps.append(.element(element))
index = nil
default:
if index != nil {
index!.append(character)
} else {
name.append(character)
}
}
}
guard index == nil else { throw PatternPathError.malformed(text) }
flushName()
self.steps = steps
}
}
public enum PatternPathError: LocalizedError {
case malformed(String)
case noField(String, available: [String])
case noElement(Int, count: Int)
public var errorDescription: String? {
switch self {
case .malformed(let path):
return "Malformed path \"\(path)\"; use names and indices, like commands[3].segname."
case .noField(let name, let available):
return "No field \"\(name)\"; available: \(available.joined(separator: ", "))."
case .noElement(let index, let count):
return "No element [\(index)]; the array has \(count) decoded elements."
}
}
}
extension DecodedPattern {
public func descendant(at path: PatternPath) throws -> DecodedPattern {
var node = self
for step in path.steps {
switch step {
case .field(let name):
guard let field = node.fields.first(where: { $0.name == name }) else {
throw PatternPathError.noField(name, available: node.fields.map(\.name))
}
node = field
case .element(let index):
guard node.elements.indices.contains(index) else {
throw PatternPathError.noElement(index, count: node.elements.count)
}
node = node.elements[index]
}
}
return node
}
public func projection(depth: Int, elementLimit: Int) -> [String: Any] {
var object: [String: Any] = [
"type": typeName,
"address": String(format: "0x%llx", address),
]
if !name.isEmpty {
object["name"] = name
}
if let displayName {
object["display_name"] = displayName
}
if let size {
object["size"] = size
}
if let bitOffset {
object["bit_offset"] = bitOffset
object["bits"] = bits
}
if let shown = formatted ?? label ?? value?.description {
object["value"] = shown
}
if let comment {
object["comment"] = comment
}
if truncated {
object["truncated"] = true
}
if let visualizer {
object["visualizer"] = visualizer.name
}
let visibleFields = fields.filter { !$0.hidden }
if !visibleFields.isEmpty {
object["field_count"] = visibleFields.count
if depth > 0 {
object["fields"] = visibleFields.map { $0.projection(depth: depth - 1, elementLimit: elementLimit) }
}
}
if !elements.isEmpty {
object["element_count"] = elements.count
if depth > 0 {
object["elements"] = elements.prefix(elementLimit).map { $0.projection(depth: depth - 1, elementLimit: elementLimit) }
}
}
return object
}
}