05-Path-System.md 10 KB

Path System

This document describes how paths are parsed, resolved, and managed in Implexus.

Path Class

The Path class represents a path to an entity in the database hierarchy.

namespace Implexus.Core {

public class Path : Object, Invercargill.Element, Invercargill.Hashable, Invercargill.Equatable {
    
    private Invercargill.DataStructures.Vector<string> _segments;
    
    // Constructors
    public Path(string path_string) {
        _segments = new Invercargill.DataStructures.Vector<string>();
        parse(path_string);
    }
    
    public Path.root() {
        _segments = new Invercargill.DataStructures.Vector<string>();
    }
    
    public Path.from_segments(Invercargill.ReadOnlyCollection<string> segments) {
        _segments = new Invercargill.DataStructures.Vector<string>();
        foreach (var seg in segments) {
            _segments.add(seg);
        }
    }
    
    public Path.child(Path parent, string name) {
        _segments = new Invercargill.DataStructures.Vector<string>();
        foreach (var seg in parent._segments) {
            _segments.add(seg);
        }
        _segments.add(name);
    }
    
    // Properties
    public bool is_root { get { return _segments.count == 0; } }
    
    public string name {
        owned get {
            if (is_root) return "";
            return _segments.last();
        }
    }
    
    public Path parent {
        owned get {
            if (is_root) return this;
            var parent_segments = _segments.take(_segments.count - 1);
            return new Path.from_segments(parent_segments);
        }
    }
    
    public int depth { get { return _segments.count; } }
    
    public Invercargill.ReadOnlyCollection<string> segments {
        owned get { return _segments.as_read_only(); }
    }
    
    // Path Operations
    public Path child(string name) {
        return new Path.child(this, validate_name(name));
    }
    
    public Path sibling(string name) {
        if (is_root) {
            throw new EngineError.INVALID_PATH("Root has no siblings");
        }
        return parent.child(name);
    }
    
    public Path ancestor(int levels) {
        if (levels < 0 || levels > depth) {
            throw new EngineError.INVALID_PATH("Invalid ancestor level: %d", levels);
        }
        var ancestor_segments = _segments.take(depth - levels);
        return new Path.from_segments(ancestor_segments);
    }
    
    public bool is_ancestor_of(Path other) {
        if (depth >= other.depth) return false;
        for (int i = 0; i < depth; i++) {
            if (_segments.get(i) != other._segments.get(i)) return false;
        }
        return true;
    }
    
    public bool is_descendant_of(Path other) {
        return other.is_ancestor_of(this);
    }
    
    public Path relative_to(Path ancestor) {
        if (!ancestor.is_ancestor_of(this)) {
            throw new EngineError.INVALID_PATH("%s is not an ancestor of %s", 
                ancestor.to_string(), this.to_string());
        }
        var relative_segments = _segments.skip(ancestor.depth);
        return new Path.from_segments(relative_segments);
    }
    
    public Path resolve(Path relative_path) {
        var result_segments = new Invercargill.DataStructures.Vector<string>();
        foreach (var seg in _segments) {
            result_segments.add(seg);
        }
        foreach (var seg in relative_path._segments) {
            if (seg == "..") {
                if (result_segments.count > 0) {
                    result_segments.remove_at(result_segments.count - 1);
                }
            } else if (seg != ".") {
                result_segments.add(seg);
            }
        }
        return new Path.from_segments(result_segments);
    }
    
    // String Conversion
    public string to_string() {
        if (is_root) return "/";
        var builder = new StringBuilder();
        foreach (var seg in _segments) {
            builder.append("/");
            builder.append(escape_segment(seg));
        }
        return builder.str;
    }
    
    public string to_key() {
        // Compact representation for storage keys
        if (is_root) return "\0";
        return string.joinv("\0", _segments.to_array());
    }
    
    public static Path from_key(string key) {
        if (key == "\0") return new Path.root();
        var segments = key.split("\0");
        return new Path.from_segments(new Invercargill.DataStructures.Vector<string>.from_array(segments));
    }
    
    // Parsing
    private void parse(string path_string) {
        if (path_string == null || path_string == "") {
            return; // Root path
        }
        
        var normalized = path_string;
        if (normalized.has_prefix("/")) {
            normalized = normalized.substring(1);
        }
        if (normalized.has_suffix("/")) {
            normalized = normalized.substring(0, normalized.length - 1);
        }
        
        if (normalized == "") {
            return; // Root path
        }
        
        var parts = normalized.split("/");
        foreach (var part in parts) {
            if (part == "") continue;
            _segments.add(unescape_segment(part));
        }
    }
    
    // Validation
    private string validate_name(string name) {
        if (name == null || name == "") {
            throw new EngineError.INVALID_PATH("Entity name cannot be empty");
        }
        if (name.contains("/")) {
            throw new EngineError.INVALID_PATH("Entity name cannot contain /: %s", name);
        }
        if (name == "." || name == "..") {
            throw new EngineError.INVALID_PATH("Entity name cannot be . or ..");
        }
        return name;
    }
    
    // Escaping for special characters in segment names
    private string escape_segment(string segment) {
        return segment.replace("~", "~7e")
                      .replace("/", "~2f")
                      .replace("\\", "~5c")
                      .replace("\0", "~00");
    }
    
    private string unescape_segment(string segment) {
        return segment.replace("~00", "\0")
                      .replace("~5c", "\\")
                      .replace("~2f", "/")
                      .replace("~7e", "~");
    }
    
    // Hashable
    public uint hash() {
        uint h = 0;
        foreach (var seg in _segments) {
            h ^= str_hash(seg);
        }
        return h;
    }
    
    // Equatable
    public bool equals(Path other) {
        if (depth != other.depth) return false;
        for (int i = 0; i < depth; i++) {
            if (_segments.get(i) != other._segments.get(i)) return false;
        }
        return true;
    }
    
    // Element interface
    public Type type() { return typeof(Path); }
    public string type_name() { return "Path"; }
    public bool is_null() { return false; }
    public bool is_type(Type t) { return t.is_a(typeof(Path)); }
    public bool assignable_to(Type t) { return t.is_a(typeof(Path)); }
    public bool assignable_to_type(Type t) { return is_type(t); }
    public T? as<T>() { return this; }
    public T assert_as<T>() { return (T) this; }
    public T? as_or_default<T>(T default_value) { return this; }
    public bool try_get_as<T>(out T result) { result = this; return true; }
    public T to_value<T>() { return (T) this; }
    
    // Static factory methods
    public static Path parse(string path_string) {
        return new Path(path_string);
    }
    
    public static Path combine(Path base_path, string relative) {
        return base_path.resolve(new Path(relative));
    }
}

} // namespace Implexus.Core

Path Resolution Flow

sequenceDiagram
    participant App as Application
    participant Engine as Engine
    participant Storage as Storage
    participant Cache as Entity Cache
    
    App->>Engine: get_entity with /users/john/profile
    Engine->>Engine: Create Path object
    Engine->>Storage: has_entity with path
    Storage-->>Engine: true
    Engine->>Cache: check cache for path
    Cache-->>Engine: not found
    Engine->>Storage: load_entity with path
    Storage-->>Engine: binary data
    Engine->>Engine: deserialize entity
    Engine->>Cache: cache entity
    Engine-->>App: Entity object

Path Examples

Path String Segments is_root depth
/ [] true 0
/users ["users"] false 1
/users/john ["users", "john"] false 2
/users/john/profile ["users", "john", "profile"] false 3

Path Operations Examples

// Creation
var root = new Path.root();
var users = new Path("/users");
var john = users.child("john");

// Navigation
assert(john.parent.equals(users));
assert(john.name == "john");
assert(john.depth == 2);

// Relationships
assert(users.is_ancestor_of(john));
assert(john.is_descendant_of(users));
assert(!john.is_ancestor_of(users));

// Relative paths
var profile = john.child("profile");
var relative = profile.relative_to(users);
assert(relative.to_string() == "john/profile");

// Resolution
var resolved = users.resolve(new Path("john/profile"));
assert(resolved.equals(profile));

// With .. and .
var complex = john.resolve(new Path("../jane"));
assert(complex.to_string() == "/users/jane");

Path Validation Rules

Valid Names

  • Non-empty strings
  • Cannot contain /
  • Cannot be . or ..
  • Any other characters allowed (including unicode)

Invalid Names

  • Empty string ""
  • Contains slash "a/b"
  • Current directory "."
  • Parent directory ".."

Escaping

Special characters in names are escaped using tilde encoding:

Character Escaped
~ ~7e
/ ~2f
\ ~5c
\0 ~00

This allows names like "a/b" (escaped as "a~2fb") to be stored safely.

Path-Based Entity Resolution

The engine resolves paths to entities through the storage layer:

public Entity? resolve_path(Path path) {
    // 1. Check cache
    if (_cache.has(path)) {
        return _cache.get(path);
    }
    
    // 2. Load from storage
    if (!_storage.has_entity(path)) {
        return null;
    }
    
    // 3. Deserialize
    var data = _storage.load_entity(path);
    var entity = _deserializer.deserialize(data, this, path);
    
    // 4. Cache and return
    _cache.set(path, entity);
    return entity;
}

Child Name Tracking

Storage tracks child names for each path to enable efficient enumeration:

Key: children:/users
Value: ["john", "jane", "admin"]

Key: children:/users/john
Value: ["profile", "settings"]

This allows get_child_names() to return quickly without scanning all entities.