Fix quasiquote flattening bug, decouple relations from evaluator
- Fix qq-expand in eval.sx: use concat+list instead of append to prevent
nested lists from being flattened during quasiquote expansion
- Update append primitive to match spec ("if x is list, concatenate")
- Rebuild sx_ref.py with quasiquote fix
- Make relations.py self-contained: parse defrelation AST directly
without depending on the evaluator (25/25 tests pass)
- Replace hand-written JSEmitter with js.sx self-hosting bootstrapper
- Guard server-only tests in test-eval.sx with runtime check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.sx.evaluator import evaluate, EvalError
|
||||
from shared.sx.parser import parse
|
||||
from shared.sx.relations import (
|
||||
RelationError,
|
||||
_RELATION_REGISTRY,
|
||||
clear_registry,
|
||||
evaluate_defrelation,
|
||||
get_relation,
|
||||
load_relation_registry,
|
||||
relations_from,
|
||||
@@ -38,7 +39,7 @@ class TestDefrelation:
|
||||
:nav-icon "fa fa-shopping-bag"
|
||||
:nav-label "markets")
|
||||
''')
|
||||
result = evaluate(tree)
|
||||
result = evaluate_defrelation(tree)
|
||||
assert isinstance(result, RelationDef)
|
||||
assert result.name == "page->market"
|
||||
assert result.from_type == "page"
|
||||
@@ -54,7 +55,7 @@ class TestDefrelation:
|
||||
(defrelation :a->b
|
||||
:from "a" :to "b" :cardinality :one-to-one :nav :hidden)
|
||||
''')
|
||||
evaluate(tree)
|
||||
evaluate_defrelation(tree)
|
||||
assert get_relation("a->b") is not None
|
||||
assert get_relation("a->b").cardinality == "one-to-one"
|
||||
|
||||
@@ -64,7 +65,7 @@ class TestDefrelation:
|
||||
:from "page" :to "menu_node"
|
||||
:cardinality :one-to-one :nav :hidden)
|
||||
''')
|
||||
result = evaluate(tree)
|
||||
result = evaluate_defrelation(tree)
|
||||
assert result.cardinality == "one-to-one"
|
||||
assert result.inverse is None
|
||||
assert result.nav == "hidden"
|
||||
@@ -79,7 +80,7 @@ class TestDefrelation:
|
||||
:nav-icon "fa fa-file-alt"
|
||||
:nav-label "events")
|
||||
''')
|
||||
result = evaluate(tree)
|
||||
result = evaluate_defrelation(tree)
|
||||
assert result.cardinality == "many-to-many"
|
||||
|
||||
def test_default_nav_is_hidden(self):
|
||||
@@ -87,7 +88,7 @@ class TestDefrelation:
|
||||
(defrelation :x->y
|
||||
:from "x" :to "y" :cardinality :one-to-many)
|
||||
''')
|
||||
result = evaluate(tree)
|
||||
result = evaluate_defrelation(tree)
|
||||
assert result.nav == "hidden"
|
||||
|
||||
def test_invalid_cardinality_raises(self):
|
||||
@@ -95,42 +96,42 @@ class TestDefrelation:
|
||||
(defrelation :bad
|
||||
:from "a" :to "b" :cardinality :wrong)
|
||||
''')
|
||||
with pytest.raises(EvalError, match="invalid cardinality"):
|
||||
evaluate(tree)
|
||||
with pytest.raises(RelationError, match="invalid cardinality"):
|
||||
evaluate_defrelation(tree)
|
||||
|
||||
def test_invalid_nav_raises(self):
|
||||
tree = parse('''
|
||||
(defrelation :bad
|
||||
:from "a" :to "b" :cardinality :one-to-one :nav :bogus)
|
||||
''')
|
||||
with pytest.raises(EvalError, match="invalid nav"):
|
||||
evaluate(tree)
|
||||
with pytest.raises(RelationError, match="invalid nav"):
|
||||
evaluate_defrelation(tree)
|
||||
|
||||
def test_missing_from_raises(self):
|
||||
tree = parse('''
|
||||
(defrelation :bad :to "b" :cardinality :one-to-one)
|
||||
''')
|
||||
with pytest.raises(EvalError, match="missing required :from"):
|
||||
evaluate(tree)
|
||||
with pytest.raises(RelationError, match="missing required :from"):
|
||||
evaluate_defrelation(tree)
|
||||
|
||||
def test_missing_to_raises(self):
|
||||
tree = parse('''
|
||||
(defrelation :bad :from "a" :cardinality :one-to-one)
|
||||
''')
|
||||
with pytest.raises(EvalError, match="missing required :to"):
|
||||
evaluate(tree)
|
||||
with pytest.raises(RelationError, match="missing required :to"):
|
||||
evaluate_defrelation(tree)
|
||||
|
||||
def test_missing_cardinality_raises(self):
|
||||
tree = parse('''
|
||||
(defrelation :bad :from "a" :to "b")
|
||||
''')
|
||||
with pytest.raises(EvalError, match="missing required :cardinality"):
|
||||
evaluate(tree)
|
||||
with pytest.raises(RelationError, match="missing required :cardinality"):
|
||||
evaluate_defrelation(tree)
|
||||
|
||||
def test_name_must_be_keyword(self):
|
||||
tree = parse('(defrelation "not-keyword" :from "a" :to "b" :cardinality :one-to-one)')
|
||||
with pytest.raises(EvalError, match="must be a keyword"):
|
||||
evaluate(tree)
|
||||
with pytest.raises(RelationError, match="must be a keyword"):
|
||||
evaluate_defrelation(tree)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -154,7 +155,7 @@ class TestRegistry:
|
||||
:from "page" :to "menu_node" :cardinality :one-to-one
|
||||
:nav :hidden))
|
||||
''')
|
||||
evaluate(tree)
|
||||
evaluate_defrelation(tree)
|
||||
|
||||
def test_get_relation(self):
|
||||
self._load_sample()
|
||||
|
||||
Reference in New Issue
Block a user