// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// IR (Intermediate Representation) for diago
///
/// The IR is a tree data structure that keeps track of resolved values
/// of diago keys after processing variable substitutions, imports, and globs.
///|
/// A scalar value in the IR.
///
/// Scalars carry a source `range` for error reporting and reference tracking.
pub struct Scalar {
value : ScalarValue
range : @lexer.Range
ast : @ast.Node?
source_path : String?
path : NodePath?
} derive(Eq, Debug)
///|
/// Types of scalar values
pub(all) enum ScalarValue {
Null
Bool(String)
Number(String)
String(@ast.StringValue)
} derive(Eq, Debug)
///|
/// An IR Map containing fields and edges
pub struct Map {
fields : Array[Field]
edges : Array[Edge]
ast : @ast.Node?
import_ast : @ast.Node?
source_path : String?
path : NodePath?
} derive(Eq, Debug)
///|
/// A field in the IR map
pub struct Field {
name : String
name_syntax : @ast.StringValue?
primary : Scalar?
composite : Composite?
references : Array[FieldReference]
import_ast : @ast.Node?
path : NodePath?
} derive(Eq, Debug)
///|
/// Composite values (Array or Map)
pub(all) enum Composite {
Array(IRArray)
Map(Map)
} derive(Eq, Debug)
///|
/// An array of values
pub struct IRArray {
values : Array[Value]
ast : @ast.Node?
source_path : String?
path : NodePath?
} derive(Eq, Debug)
///|
/// Value types in IR
pub(all) enum Value {
Scalar(Scalar)
Array(IRArray)
Map(Map)
} derive(Eq, Debug)
///|
/// An edge in the IR
pub struct Edge {
id : EdgeID
primary : Scalar?
map : Map?
references : Array[EdgeReference]
import_ast : @ast.Node?
path : NodePath?
} derive(Eq, Debug)
///|
/// Unique identifier for an edge
pub struct EdgeID {
src_path : Array[String]
src_path_syntax : Array[@ast.StringValue]
src_arrow : Bool
dst_path : Array[String]
dst_path_syntax : Array[@ast.StringValue]
dst_arrow : Bool
index : Int?
glob : Bool
} derive(Eq, Debug)
///|
/// Stable path to an IR node inside a compiled map.
pub(all) enum NodePathSegment {
FieldStep(String, Bool)
EdgeStep(EdgeID)
PrimaryStep
CompositeStep
ArrayItemStep(Int)
} derive(Eq, Debug)
///|
/// Stable path to an IR node inside a compiled map.
pub struct NodePath {
segments : Array[NodePathSegment]
} derive(Eq, Debug)
///|
/// Reference context for a field or edge reference.
pub struct RefContext {
edge : @ast.Edge?
key : @ast.Key?
scope : @ast.Map?
source_path : String?
} derive(Eq, Debug)
///|
/// Reference to a field from the AST
pub struct FieldReference {
range : @lexer.Range
string : String
syntax : @ast.StringValue?
key_path : @ast.KeyPath?
context : RefContext?
key_path_index : Int
primary : Bool
due_to_glob : Bool
due_to_lazy_glob : Bool
} derive(Eq, Debug)
///|
/// Reference to an edge from the AST
pub struct EdgeReference {
range : @lexer.Range
edge : @ast.Edge?
context : RefContext?
edge_index : Int
primary : Bool
due_to_glob : Bool
due_to_lazy_glob : Bool
} derive(Eq, Debug)
///|
/// Boxed reference for generic traversal.
pub(all) enum Reference {
FieldRef(FieldReference)
EdgeRef(EdgeReference)
} derive(Eq, Debug)
///|
/// Boxed IR node for generic traversal.
pub(all) enum Node {
Scalar(Scalar)
Field(Field)
Edge(Edge)
IRArray(IRArray)
Map(Map)
Composite(Composite)
Value(Value)
} derive(Eq, Debug)
///|
pub fn Node::node_type(self : Node) -> String {
match self {
Scalar(_) => "Scalar"
Field(_) => "Field"
Edge(_) => "Edge"
IRArray(_) => "IRArray"
Map(_) => "Map"
Composite(_) => "Composite"
Value(_) => "Value"
}
}
///|
pub fn Node::children(self : Node) -> Array[Node] {
let out : Array[Node] = []
match self {
Scalar(_) => ()
Field(f) => {
match f.primary {
Some(s) => out.push(Scalar(s))
None => ()
}
match f.composite {
Some(c) => out.push(Composite(c))
None => ()
}
}
Edge(e) => {
match e.primary {
Some(s) => out.push(Scalar(s))
None => ()
}
match e.map {
Some(m) => out.push(Map(m))
None => ()
}
}
IRArray(a) =>
for v in a.values {
out.push(Value(v))
}
Map(m) => {
for f in m.fields {
out.push(Field(f))
}
for e in m.edges {
out.push(Edge(e))
}
}
Composite(c) =>
match c {
Array(a) => out.push(IRArray(a))
Map(m) => out.push(Map(m))
}
Value(v) =>
match v {
Scalar(s) => out.push(Scalar(s))
Array(a) => out.push(IRArray(a))
Map(m) => out.push(Map(m))
}
}
out
}
///|
pub fn Node::ast(self : Node) -> @ast.Node? {
match self {
Scalar(s) => s.ast()
Field(f) => f.ast()
Edge(e) => e.ast()
IRArray(a) => a.ast()
Map(m) => m.ast()
Composite(c) =>
match c {
Array(a) => a.ast()
Map(m) => m.ast()
}
Value(v) =>
match v {
Scalar(s) => s.ast()
Array(a) => a.ast()
Map(m) => m.ast()
}
}
}
///|
pub fn Node::import_ast(self : Node) -> @ast.Node? {
match self {
Scalar(_) => None
Field(f) => f.import_ast()
Edge(e) => e.import_ast()
IRArray(_) => None
Map(m) => m.import_ast()
Composite(c) =>
match c {
Array(_) => None
Map(m) => m.import_ast()
}
Value(v) =>
match v {
Scalar(_) => None
Array(_) => None
Map(m) => m.import_ast()
}
}
}
///|
pub fn Node::last_ref(self : Node) -> Reference? {
match self {
Field(f) => f.last_ref()
Edge(e) => e.last_ref()
_ => None
}
}
///|
pub fn Node::last_primary_ref(self : Node) -> Reference? {
match self {
Field(f) => f.last_primary_ref()
Edge(e) => e.last_primary_ref()
_ => None
}
}
///|
pub fn Node::last_primary_key(self : Node) -> @ast.Key? {
match self {
Field(f) => f.last_primary_key()
Edge(e) => e.last_primary_key()
_ => None
}
}
///|
pub fn NodePath::root() -> NodePath {
{ segments: [] }
}
///|
pub fn NodePath::append(self : NodePath, segment : NodePathSegment) -> NodePath {
let segments = self.segments.copy()
segments.push(segment)
{ segments, }
}
///|
pub fn NodePath::parent(self : NodePath) -> NodePath? {
if self.segments.is_empty() {
return None
}
let segments = self.segments.copy()
let _ = segments.remove(segments.length() - 1)
Some({ segments, })
}
///|
pub fn Node::path(self : Node) -> NodePath? {
match self {
Scalar(s) => s.path()
Field(f) => f.path()
Edge(e) => e.path()
IRArray(a) => a.path()
Map(m) => m.path()
Composite(c) =>
match c {
Array(a) => a.path()
Map(m) => m.path()
}
Value(v) =>
match v {
Scalar(s) => s.path()
Array(a) => a.path()
Map(m) => m.path()
}
}
}
///|
pub fn Node::parent(self : Node, root : Map) -> Node? {
match self.path() {
Some(path) =>
match path.parent() {
Some(parent) => root.get_node(parent)
None => None
}
None => None
}
}
///|
pub fn Node::copy(self : Node) -> Node {
match self {
Scalar(s) => Scalar(s.copy())
Field(f) => Field(f.copy())
Edge(e) => Edge(e.copy())
IRArray(a) => IRArray(a.copy())
Map(m) => Map(m.copy())
Composite(c) => Composite(copy_composite_detached(c))
Value(v) => Value(copy_value_detached(v))
}
}
// ============================================================================
// Constructors
// ============================================================================
///|
fn default_range() -> @lexer.Range {
let p = @lexer.Position::zero()
@lexer.Range::new(p, p)
}
///|
fn synthetic_unquoted_string(value : String) -> @ast.StringValue {
let r = default_range()
Unquoted(@ast.UnquotedString::new(r, [Text(value)], []))
}
///|
fn is_unquoted_string_value(value : @ast.StringValue) -> Bool {
match value {
Unquoted(_) => true
_ => false
}
}
///|
fn copy_string_value_array(
values : Array[@ast.StringValue],
) -> Array[@ast.StringValue] {
let out : Array[@ast.StringValue] = []
for value in values {
out.push(value)
}
out
}
///|
fn default_edge_path_syntax(path : Array[String]) -> Array[@ast.StringValue] {
let out : Array[@ast.StringValue] = []
for segment in path {
out.push(synthetic_unquoted_string(segment))
}
out
}
///|
fn field_name_matches(
field : Field,
name : String,
syntax : @ast.StringValue?,
) -> Bool {
if field.name.to_lower() != name.to_lower() {
return false
}
if is_reserved_keyword(name) {
let want_unquoted = match syntax {
Some(segment) => is_unquoted_string_value(segment)
None => true
}
return field.name_is_unquoted() == want_unquoted
}
true
}
///|
pub fn Scalar::at(range : @lexer.Range, value : ScalarValue) -> Scalar {
{ value, range, ast: None, source_path: None, path: None }
}
///|
pub fn Scalar::from_ast(
ast : @ast.Node,
value : ScalarValue,
source_path? : String? = None,
) -> Scalar {
{ value, range: ast.range(), ast: Some(ast), source_path, path: None }
}
///|
pub fn Scalar::new(value : ScalarValue) -> Scalar {
Scalar::at(default_range(), value)
}
///|
pub fn Scalar::null() -> Scalar {
Scalar::at(default_range(), Null)
}
///|
pub fn Scalar::bool(b : Bool) -> Scalar {
let s = if b { "true" } else { "false" }
Scalar::at(default_range(), Bool(s))
}
///|
pub fn Scalar::number(n : String) -> Scalar {
Scalar::at(default_range(), Number(n))
}
///|
pub fn Scalar::string(s : String) -> Scalar {
let r = default_range()
Scalar::at(r, String(Unquoted(@ast.UnquotedString::new(r, [Text(s)], []))))
}
///|
pub fn Map::new() -> Map {
{
fields: [],
edges: [],
ast: None,
import_ast: None,
source_path: None,
path: None,
}
}
///|
pub fn Map::from_ast(ast : @ast.Map, source_path? : String? = None) -> Map {
{
fields: [],
edges: [],
ast: Some(Map(ast)),
import_ast: None,
source_path,
path: None,
}
}
///|
pub fn Field::new(
name : String,
primary : Scalar?,
composite : Composite?,
references : Array[FieldReference],
name_syntax? : @ast.StringValue? = None,
) -> Field {
{
name,
name_syntax,
primary,
composite,
references,
import_ast: None,
path: None,
}
}
///|
pub fn Field::simple(
name : String,
name_syntax? : @ast.StringValue? = None,
) -> Field {
{
name,
name_syntax,
primary: None,
composite: None,
references: [],
import_ast: None,
path: None,
}
}
///|
pub fn IRArray::new(values : Array[Value]) -> IRArray {
{ values, ast: None, source_path: None, path: None }
}
///|
pub fn IRArray::from_ast(
ast : @ast.ArrayValue,
values : Array[Value],
source_path? : String? = None,
) -> IRArray {
{ values, ast: Some(ArrayValue(ast)), source_path, path: None }
}
///|
pub fn Edge::new(
id : EdgeID,
primary : Scalar?,
map : Map?,
references : Array[EdgeReference],
) -> Edge {
{ id, primary, map, references, import_ast: None, path: None }
}
///|
pub fn EdgeID::new(
src_path : Array[String],
dst_path : Array[String],
src_arrow : Bool,
dst_arrow : Bool,
src_path_syntax? : Array[@ast.StringValue]? = None,
dst_path_syntax? : Array[@ast.StringValue]? = None,
) -> EdgeID {
{
src_path,
src_path_syntax: match src_path_syntax {
Some(path) => copy_string_value_array(path)
None => default_edge_path_syntax(src_path)
},
src_arrow,
dst_path,
dst_path_syntax: match dst_path_syntax {
Some(path) => copy_string_value_array(path)
None => default_edge_path_syntax(dst_path)
},
dst_arrow,
index: None,
glob: false,
}
}
///|
pub fn EdgeID::with_index(
src_path : Array[String],
dst_path : Array[String],
src_arrow : Bool,
dst_arrow : Bool,
index : Int,
src_path_syntax? : Array[@ast.StringValue]? = None,
dst_path_syntax? : Array[@ast.StringValue]? = None,
) -> EdgeID {
{
src_path,
src_path_syntax: match src_path_syntax {
Some(path) => copy_string_value_array(path)
None => default_edge_path_syntax(src_path)
},
src_arrow,
dst_path,
dst_path_syntax: match dst_path_syntax {
Some(path) => copy_string_value_array(path)
None => default_edge_path_syntax(dst_path)
},
dst_arrow,
index: Some(index),
glob: false,
}
}
///|
pub fn EdgeID::with_glob(
src_path : Array[String],
dst_path : Array[String],
src_arrow : Bool,
dst_arrow : Bool,
src_path_syntax? : Array[@ast.StringValue]? = None,
dst_path_syntax? : Array[@ast.StringValue]? = None,
) -> EdgeID {
{
src_path,
src_path_syntax: match src_path_syntax {
Some(path) => copy_string_value_array(path)
None => default_edge_path_syntax(src_path)
},
src_arrow,
dst_path,
dst_path_syntax: match dst_path_syntax {
Some(path) => copy_string_value_array(path)
None => default_edge_path_syntax(dst_path)
},
dst_arrow,
index: None,
glob: true,
}
}
///|
pub fn FieldReference::new(
range : @lexer.Range,
string : String,
key_path_index : Int,
primary : Bool,
) -> FieldReference {
{
range,
string,
syntax: None,
key_path: None,
context: None,
key_path_index,
primary,
due_to_glob: false,
due_to_lazy_glob: false,
}
}
///|
pub fn RefContext::new(
edge : @ast.Edge?,
key : @ast.Key?,
scope : @ast.Map?,
source_path? : String? = None,
) -> RefContext {
{ edge, key, scope, source_path }
}
///|
pub fn FieldReference::from_ast(
syntax : @ast.StringValue,
key_path : @ast.KeyPath,
key_path_index : Int,
primary : Bool,
context : RefContext,
due_to_glob? : Bool = false,
due_to_lazy_glob? : Bool = false,
) -> FieldReference {
{
range: syntax.range(),
string: syntax.content(),
syntax: Some(syntax),
key_path: Some(key_path),
context: Some(context),
key_path_index,
primary,
due_to_glob,
due_to_lazy_glob,
}
}
///|
pub fn EdgeReference::new(
range : @lexer.Range,
edge_index : Int,
primary : Bool,
) -> EdgeReference {
{
range,
edge: None,
context: None,
edge_index,
primary,
due_to_glob: false,
due_to_lazy_glob: false,
}
}
///|
pub fn EdgeReference::from_ast(
edge : @ast.Edge,
edge_index : Int,
primary : Bool,
context : RefContext,
due_to_glob? : Bool = false,
due_to_lazy_glob? : Bool = false,
) -> EdgeReference {
{
range: edge.range,
edge: Some(edge),
context: Some(context),
edge_index,
primary,
due_to_glob,
due_to_lazy_glob,
}
}
// ============================================================================
// Map operations
// ============================================================================
///|
/// Get a field by name (case-insensitive)
pub fn Map::get_field(self : Map, name : String) -> Field? {
self.get_field_by_name(name, None)
}
///|
pub fn Map::get_field_by_syntax(self : Map, name : @ast.StringValue) -> Field? {
self.get_field_by_name(name.content(), Some(name))
}
///|
fn Map::get_field_by_name(
self : Map,
name : String,
syntax : @ast.StringValue?,
) -> Field? {
for f in self.fields {
if field_name_matches(f, name, syntax) {
return Some(f)
}
}
None
}
///|
/// Check if map has a field with given name
pub fn Map::has_field(self : Map, name : String) -> Bool {
self.get_field(name) is Some(_)
}
///|
/// Add a field to the map
pub fn Map::add_field(self : Map, field : Field) -> Unit {
self.fields.push(field)
}
///|
fn Map::set_field(self : Map, field : Field) -> Unit {
for i, f in self.fields {
if field_name_matches(f, field.name, field.name_syntax()) {
self.fields[i] = field
return
}
}
self.fields.push(field)
}
///|
/// Add an edge to the map
pub fn Map::add_edge(self : Map, edge : Edge) -> Unit {
self.edges.push(edge)
}
///|
fn Map::set_edge(self : Map, edge : Edge) -> Unit {
for i, e in self.edges {
if e.id == edge.id {
self.edges[i] = edge
return
}
}
self.edges.push(edge)
}
///|
/// Get or create a field by name
pub fn Map::ensure_field(self : Map, name : String) -> Field {
match self.get_field(name) {
Some(f) => f
None => {
let f = Field::simple(name)
self.fields.push(f)
f
}
}
}
///|
pub fn Map::ensure_field_by_syntax(
self : Map,
name : @ast.StringValue,
) -> Field {
match self.get_field_by_syntax(name) {
Some(f) => f
None => {
let f = Field::simple(name.content(), name_syntax=Some(name))
self.fields.push(f)
f
}
}
}
///|
/// Delete a field by name, returning it if found
pub fn Map::delete_field(self : Map, name : String) -> Field? {
for i, f in self.fields {
if field_name_matches(f, name, None) {
// Remove and return
let removed = self.fields.remove(i)
return Some(removed)
}
}
None
}
///|
/// Find edges matching an EdgeID
pub fn Map::find_edges(self : Map, id : EdgeID) -> Array[Edge] {
let result : Array[Edge] = []
for e in self.edges {
if e.id.matches(id) {
result.push(e)
}
}
result
}
///|
pub fn Map::get_node(self : Map, path : NodePath) -> Node? {
get_node_from_map(self, path.segments, 0)
}
///|
pub fn Map::reindex_paths(self : Map) -> Map {
reindex_map(self, NodePath::root())
}
///|
fn get_node_from_map(
m : Map,
segments : Array[NodePathSegment],
index : Int,
) -> Node? {
if index == segments.length() {
return Some(Map(m))
}
match segments[index] {
FieldStep(name, unquoted) => {
let step_syntax = if unquoted {
synthetic_unquoted_string(name)
} else {
DoubleQuoted(
@ast.DoubleQuotedString::new(default_range(), [Text(name)]),
)
}
match m.get_field_by_name(name, Some(step_syntax)) {
Some(field) => get_node_from_field(field, segments, index + 1)
None => None
}
}
EdgeStep(id) =>
match get_edge_by_id(m, id) {
Some(edge) => get_node_from_edge(edge, segments, index + 1)
None => None
}
_ => None
}
}
///|
fn get_node_from_field(
field : Field,
segments : Array[NodePathSegment],
index : Int,
) -> Node? {
if index == segments.length() {
return Some(Field(field))
}
match segments[index] {
PrimaryStep =>
match field.primary {
Some(scalar) =>
if index + 1 == segments.length() {
Some(Scalar(scalar))
} else {
None
}
None => None
}
CompositeStep =>
match field.composite {
Some(composite) =>
get_node_from_composite(composite, segments, index + 1)
None => None
}
_ => None
}
}
///|
fn get_node_from_edge(
edge : Edge,
segments : Array[NodePathSegment],
index : Int,
) -> Node? {
if index == segments.length() {
return Some(Edge(edge))
}
match segments[index] {
PrimaryStep =>
match edge.primary {
Some(scalar) =>
if index + 1 == segments.length() {
Some(Scalar(scalar))
} else {
None
}
None => None
}
CompositeStep =>
match edge.map {
Some(map) => get_node_from_map(map, segments, index + 1)
None => None
}
_ => None
}
}
///|
fn get_node_from_composite(
composite : Composite,
segments : Array[NodePathSegment],
index : Int,
) -> Node? {
match composite {
Array(array) => get_node_from_array(array, segments, index)
Map(map) => get_node_from_map(map, segments, index)
}
}
///|
fn get_node_from_array(
array : IRArray,
segments : Array[NodePathSegment],
index : Int,
) -> Node? {
if index == segments.length() {
return Some(IRArray(array))
}
match segments[index] {
ArrayItemStep(item_index) =>
if item_index < 0 || item_index >= array.values.length() {
None
} else {
get_node_from_value(array.values[item_index], segments, index + 1)
}
_ => None
}
}
///|
fn get_node_from_value(
value : Value,
segments : Array[NodePathSegment],
index : Int,
) -> Node? {
match value {
Scalar(scalar) =>
if index == segments.length() {
Some(Scalar(scalar))
} else {
None
}
Map(map) => get_node_from_map(map, segments, index)
Array(array) => get_node_from_array(array, segments, index)
}
}
///|
fn get_edge_by_id(m : Map, id : EdgeID) -> Edge? {
for edge in m.edges {
if edge.id == id {
return Some(edge)
}
}
None
}
///|
fn reindex_map(m : Map, path : NodePath) -> Map {
let fields : Array[Field] = []
for field in m.fields {
let field_path = path.append(
FieldStep(field.name, field.name_is_unquoted()),
)
fields.push(reindex_field(field, field_path))
}
let edges : Array[Edge] = []
for edge in m.edges {
let edge_path = path.append(EdgeStep(edge.id.copy()))
edges.push(reindex_edge(edge, edge_path))
}
{
fields,
edges,
ast: m.ast,
import_ast: m.import_ast,
source_path: m.source_path,
path: Some(path),
}
}
///|
fn reindex_field(field : Field, path : NodePath) -> Field {
let primary = match field.primary {
Some(scalar) => Some(reindex_scalar(scalar, path.append(PrimaryStep)))
None => None
}
let composite = match field.composite {
Some(node) => Some(reindex_composite(node, path.append(CompositeStep)))
None => None
}
{
name: field.name,
name_syntax: field.name_syntax(),
primary,
composite,
references: field.references.copy(),
import_ast: field.import_ast,
path: Some(path),
}
}
///|
fn reindex_edge(edge : Edge, path : NodePath) -> Edge {
let primary = match edge.primary {
Some(scalar) => Some(reindex_scalar(scalar, path.append(PrimaryStep)))
None => None
}
let map = match edge.map {
Some(child) => Some(reindex_map(child, path.append(CompositeStep)))
None => None
}
{
id: edge.id.copy(),
primary,
map,
references: edge.references.copy(),
import_ast: edge.import_ast,
path: Some(path),
}
}
///|
fn reindex_scalar(scalar : Scalar, path : NodePath) -> Scalar {
{
value: scalar.value,
range: scalar.range,
ast: scalar.ast,
source_path: scalar.source_path,
path: Some(path),
}
}
///|
fn reindex_composite(composite : Composite, path : NodePath) -> Composite {
match composite {
Array(array) => Array(reindex_array(array, path))
Map(map) => Map(reindex_map(map, path))
}
}
///|
fn reindex_value(value : Value, path : NodePath) -> Value {
match value {
Scalar(scalar) => Scalar(reindex_scalar(scalar, path))
Map(map) => Map(reindex_map(map, path))
Array(array) => Array(reindex_array(array, path))
}
}
///|
fn reindex_array(array : IRArray, path : NodePath) -> IRArray {
let values : Array[Value] = []
for i, value in array.values {
values.push(reindex_value(value, path.append(ArrayItemStep(i))))
}
{ values, ast: array.ast, source_path: array.source_path, path: Some(path) }
}
///|
fn copy_scalar_detached(scalar : Scalar) -> Scalar {
{
value: scalar.value,
range: scalar.range,
ast: scalar.ast,
source_path: scalar.source_path,
path: None,
}
}
///|
fn copy_composite_detached(composite : Composite) -> Composite {
match composite {
Array(array) => Array(copy_array_detached(array))
Map(map) => Map(copy_map_detached(map))
}
}
///|
fn copy_value_detached(value : Value) -> Value {
match value {
Scalar(scalar) => Scalar(copy_scalar_detached(scalar))
Map(map) => Map(copy_map_detached(map))
Array(array) => Array(copy_array_detached(array))
}
}
///|
fn copy_array_detached(array : IRArray) -> IRArray {
let values : Array[Value] = []
for value in array.values {
values.push(copy_value_detached(value))
}
{ values, ast: array.ast, source_path: array.source_path, path: None }
}
///|
fn copy_field_detached(field : Field) -> Field {
let primary = match field.primary {
Some(scalar) => Some(copy_scalar_detached(scalar))
None => None
}
let composite = match field.composite {
Some(node) => Some(copy_composite_detached(node))
None => None
}
{
name: field.name,
name_syntax: field.name_syntax(),
primary,
composite,
references: field.references.copy(),
import_ast: field.import_ast,
path: None,
}
}
///|
fn copy_edge_detached(edge : Edge) -> Edge {
let primary = match edge.primary {
Some(scalar) => Some(copy_scalar_detached(scalar))
None => None
}
let map = match edge.map {
Some(child) => Some(copy_map_detached(child))
None => None
}
{
id: edge.id.copy(),
primary,
map,
references: edge.references.copy(),
import_ast: edge.import_ast,
path: None,
}
}
///|
fn copy_map_detached(m : Map) -> Map {
let fields : Array[Field] = []
for field in m.fields {
fields.push(copy_field_detached(field))
}
let edges : Array[Edge] = []
for edge in m.edges {
edges.push(copy_edge_detached(edge))
}
{
fields,
edges,
ast: m.ast,
import_ast: m.import_ast,
source_path: m.source_path,
path: None,
}
}
///|
fn with_import_ast_map(m : Map, import_ast : @ast.Node) -> Map {
let fields : Array[Field] = []
for field in m.fields {
fields.push(with_import_ast_field(field, import_ast))
}
let edges : Array[Edge] = []
for edge in m.edges {
edges.push(with_import_ast_edge(edge, import_ast))
}
{
fields,
edges,
ast: m.ast,
import_ast: Some(import_ast),
source_path: m.source_path,
path: m.path,
}
}
///|
fn with_import_ast_field(field : Field, import_ast : @ast.Node) -> Field {
let composite = match field.composite {
Some(Map(map)) => Some(Composite::Map(with_import_ast_map(map, import_ast)))
Some(other) => Some(other)
None => None
}
{
name: field.name,
name_syntax: field.name_syntax(),
primary: field.primary,
composite,
references: field.references.copy(),
import_ast: Some(import_ast),
path: field.path,
}
}
///|
fn with_import_ast_edge(edge : Edge, import_ast : @ast.Node) -> Edge {
let map = match edge.map {
Some(child) => Some(with_import_ast_map(child, import_ast))
None => None
}
{
id: edge.id.copy(),
primary: edge.primary,
map,
references: edge.references.copy(),
import_ast: Some(import_ast),
path: edge.path,
}
}
// ============================================================================
// EdgeID operations
// ============================================================================
///|
/// Check if this EdgeID matches another (for queries)
pub fn EdgeID::matches(self : EdgeID, other : EdgeID) -> Bool {
// Check index if both have it
match (self.index, other.index) {
(Some(i1), Some(i2)) => if i1 != i2 { return false }
_ => ()
}
// Check arrows
if self.src_arrow != other.src_arrow {
return false
}
if self.dst_arrow != other.dst_arrow {
return false
}
// Check paths (case-insensitive)
if self.src_path.length() != other.src_path.length() {
return false
}
for i, s in self.src_path {
if s.to_lower() != other.src_path[i].to_lower() {
return false
}
}
if self.dst_path.length() != other.dst_path.length() {
return false
}
for i, s in self.dst_path {
if s.to_lower() != other.dst_path[i].to_lower() {
return false
}
}
true
}
///|
/// Create a copy of this EdgeID
pub fn EdgeID::copy(self : EdgeID) -> EdgeID {
{
src_path: self.src_path.copy(),
src_path_syntax: copy_string_value_array(self.src_path_syntax),
src_arrow: self.src_arrow,
dst_path: self.dst_path.copy(),
dst_path_syntax: copy_string_value_array(self.dst_path_syntax),
dst_arrow: self.dst_arrow,
index: self.index,
glob: self.glob,
}
}
// ============================================================================
// Value operations
// ============================================================================
///|
/// Get the scalar value if this is a scalar
pub fn Value::as_scalar(self : Value) -> Scalar? {
match self {
Scalar(s) => Some(s)
_ => None
}
}
///|
/// Get the map if this is a map
pub fn Value::as_map(self : Value) -> Map? {
match self {
Map(m) => Some(m)
_ => None
}
}
///|
/// Get the array if this is an array
pub fn Value::as_array(self : Value) -> IRArray? {
match self {
Array(a) => Some(a)
_ => None
}
}
// ============================================================================
// Reference operations
// ============================================================================
///|
pub fn FieldReference::ast(self : FieldReference) -> @ast.Node? {
match self.syntax {
Some(syntax) => Some(StringValue(syntax))
None =>
match self.key_path {
Some(key_path) => Some(KeyPath(key_path))
None =>
match self.context {
Some(context) =>
match context.key {
Some(key) => Some(Key(key))
None => None
}
None => None
}
}
}
}
///|
pub fn FieldReference::context(self : FieldReference) -> RefContext? {
self.context
}
///|
pub fn EdgeReference::ast(self : EdgeReference) -> @ast.Node? {
match self.edge {
Some(edge) => Some(Edge(edge))
None =>
match self.context {
Some(context) =>
match context.key {
Some(key) => Some(Key(key))
None => None
}
None => None
}
}
}
///|
pub fn EdgeReference::context(self : EdgeReference) -> RefContext? {
self.context
}
///|
pub fn Reference::range(self : Reference) -> @lexer.Range {
match self {
FieldRef(reference) => reference.range
EdgeRef(reference) => reference.range
}
}
///|
pub fn Reference::ast(self : Reference) -> @ast.Node? {
match self {
FieldRef(reference) => reference.ast()
EdgeRef(reference) => reference.ast()
}
}
///|
pub fn Reference::primary(self : Reference) -> Bool {
match self {
FieldRef(reference) => reference.primary
EdgeRef(reference) => reference.primary
}
}
///|
pub fn Reference::context(self : Reference) -> RefContext? {
match self {
FieldRef(reference) => reference.context()
EdgeRef(reference) => reference.context()
}
}
///|
pub fn Reference::due_to_glob(self : Reference) -> Bool {
match self {
FieldRef(reference) => reference.due_to_glob
EdgeRef(reference) => reference.due_to_glob
}
}
///|
pub fn Reference::due_to_lazy_glob(self : Reference) -> Bool {
match self {
FieldRef(reference) => reference.due_to_lazy_glob
EdgeRef(reference) => reference.due_to_lazy_glob
}
}
// ============================================================================
// Scalar operations
// ============================================================================
///|
/// Get the string representation of a scalar value
pub fn Scalar::to_string(self : Scalar) -> String {
match self.value {
Null => "null"
Bool(s) => s
Number(n) => n
String(s) => s.content()
}
}
///|
/// Get underlying string value (including block-string metadata) if present
pub fn Scalar::as_string_value(self : Scalar) -> @ast.StringValue? {
match self.value {
String(s) => Some(s)
_ => None
}
}
///|
/// Get block-string language tag if this scalar is a block string
pub fn Scalar::block_string_tag(self : Scalar) -> String? {
match self.value {
String(Block(bs)) => Some(bs.tag)
_ => None
}
}
///|
/// Check if scalar is null
pub fn Scalar::is_null(self : Scalar) -> Bool {
self.value is Null
}
///|
/// Check if scalar is a boolean
pub fn Scalar::is_bool(self : Scalar) -> Bool {
self.value is Bool(_)
}
///|
/// Get boolean value if this is a boolean
pub fn Scalar::as_bool(self : Scalar) -> Bool? {
match self.value {
Bool(s) =>
match s.to_lower() {
"true" => Some(true)
"false" => Some(false)
_ => None
}
_ => None
}
}
///|
pub fn Scalar::as_int(self : Scalar) -> Int? {
match self.value {
Number(n) => Some(@string.parse_int(n)) catch { _ => None }
String(s) => Some(@string.parse_int(s.content())) catch { _ => None }
_ => None
}
}
///|
pub fn Scalar::as_double(self : Scalar) -> Double? {
match self.value {
Number(n) => Some(@string.parse_double(n)) catch { _ => None }
String(s) => Some(@string.parse_double(s.content())) catch { _ => None }
_ => None
}
}
///|
pub fn Scalar::get_range(self : Scalar) -> @lexer.Range {
self.range
}
///|
pub fn Scalar::ast(self : Scalar) -> @ast.Node? {
self.ast
}
///|
pub fn Scalar::source_path(self : Scalar) -> String? {
self.source_path
}
///|
pub fn Scalar::path(self : Scalar) -> NodePath? {
self.path
}
///|
pub fn Scalar::parent(self : Scalar, root : Map) -> Node? {
Node::Scalar(self).parent(root)
}
///|
pub fn Scalar::copy(self : Scalar) -> Scalar {
copy_scalar_detached(self)
}
///|
/// Check if scalar is a number
pub fn Scalar::is_number(self : Scalar) -> Bool {
self.value is Number(_)
}
///|
/// Check if scalar is a string
pub fn Scalar::is_string(self : Scalar) -> Bool {
self.value is String(_)
}
// ============================================================================
// Field operations
// ============================================================================
///|
/// Get the map composite if this field has one
pub fn Field::map(self : Field) -> Map? {
match self.composite {
Some(Map(m)) => Some(m)
_ => None
}
}
///|
/// Get the array composite if this field has one
pub fn Field::array(self : Field) -> IRArray? {
match self.composite {
Some(Array(a)) => Some(a)
_ => None
}
}
///|
/// Check if this field has a primary value
pub fn Field::has_primary(self : Field) -> Bool {
self.primary is Some(_)
}
///|
/// Check if this field has a composite value
pub fn Field::has_composite(self : Field) -> Bool {
self.composite is Some(_)
}
///|
pub fn Field::last_ref(self : Field) -> Reference? {
if self.references.is_empty() {
None
} else {
Some(FieldRef(self.references[self.references.length() - 1]))
}
}
///|
pub fn Field::last_primary_ref(self : Field) -> Reference? {
for i = self.references.length() - 1; i >= 0; i = i - 1 {
if self.references[i].primary {
return Some(FieldRef(self.references[i]))
}
}
None
}
///|
pub fn Field::last_primary_key(self : Field) -> @ast.Key? {
match self.last_primary_ref() {
Some(reference) =>
match reference.context() {
Some(context) => context.key
None => None
}
None => None
}
}
///|
pub fn Field::ast(self : Field) -> @ast.Node? {
match self.last_ref() {
Some(reference) => reference.ast()
None => None
}
}
///|
pub fn Field::name_syntax(self : Field) -> @ast.StringValue? {
match self.name_syntax {
Some(syntax) => Some(syntax)
None =>
match self.last_ref() {
Some(FieldRef(reference)) => reference.syntax
_ => None
}
}
}
///|
pub fn Field::name_is_unquoted(self : Field) -> Bool {
match self.name_syntax() {
Some(syntax) => is_unquoted_string_value(syntax)
None => true
}
}
///|
pub fn Field::is_reserved_keyword_name(self : Field) -> Bool {
is_reserved_keyword(self.name) && self.name_is_unquoted()
}
///|
pub fn Field::is_board_keyword_name(self : Field) -> Bool {
is_board_keyword(self.name) && self.name_is_unquoted()
}
///|
pub fn Field::import_ast(self : Field) -> @ast.Node? {
self.import_ast
}
///|
pub fn Field::path(self : Field) -> NodePath? {
self.path
}
///|
pub fn Field::parent(self : Field, root : Map) -> Node? {
Node::Field(self).parent(root)
}
///|
pub fn Field::copy(self : Field) -> Field {
copy_field_detached(self)
}
///|
pub fn Edge::last_ref(self : Edge) -> Reference? {
if self.references.is_empty() {
None
} else {
Some(EdgeRef(self.references[self.references.length() - 1]))
}
}
///|
pub fn Edge::last_primary_ref(self : Edge) -> Reference? {
for i = self.references.length() - 1; i >= 0; i = i - 1 {
if self.references[i].primary && !self.references[i].due_to_lazy_glob {
return Some(EdgeRef(self.references[i]))
}
}
None
}
///|
pub fn Edge::last_primary_key(self : Edge) -> @ast.Key? {
match self.last_primary_ref() {
Some(reference) =>
match reference.context() {
Some(context) => context.key
None => None
}
None => None
}
}
///|
pub fn Edge::ast(self : Edge) -> @ast.Node? {
match self.last_ref() {
Some(reference) => reference.ast()
None => None
}
}
///|
pub fn Edge::import_ast(self : Edge) -> @ast.Node? {
self.import_ast
}
///|
pub fn Edge::path(self : Edge) -> NodePath? {
self.path
}
///|
pub fn Edge::parent(self : Edge, root : Map) -> Node? {
Node::Edge(self).parent(root)
}
///|
pub fn Edge::copy(self : Edge) -> Edge {
copy_edge_detached(self)
}
///|
pub fn EdgeID::src_path_syntax(self : EdgeID) -> Array[@ast.StringValue] {
copy_string_value_array(self.src_path_syntax)
}
///|
pub fn EdgeID::dst_path_syntax(self : EdgeID) -> Array[@ast.StringValue] {
copy_string_value_array(self.dst_path_syntax)
}
///|
pub fn Map::ast(self : Map) -> @ast.Node? {
self.ast
}
///|
pub fn Map::import_ast(self : Map) -> @ast.Node? {
self.import_ast
}
///|
pub fn Map::source_path(self : Map) -> String? {
self.source_path
}
///|
pub fn Map::path(self : Map) -> NodePath? {
self.path
}
///|
pub fn Map::parent(self : Map, root : Map) -> Node? {
Node::Map(self).parent(root)
}
///|
pub fn Map::copy(self : Map) -> Map {
reindex_map(self, NodePath::root())
}
///|
pub fn IRArray::ast(self : IRArray) -> @ast.Node? {
self.ast
}
///|
pub fn IRArray::source_path(self : IRArray) -> String? {
self.source_path
}
///|
pub fn IRArray::path(self : IRArray) -> NodePath? {
self.path
}
///|
pub fn IRArray::parent(self : IRArray, root : Map) -> Node? {
Node::IRArray(self).parent(root)
}
///|
pub fn IRArray::copy(self : IRArray) -> IRArray {
copy_array_detached(self)
}