Skip to content

HLL (Halachic Logic Language) Reference

DSL-First Architecture: Write .hll files to mistaber/dsl/, never directly to ontology/. After editing, run python -m mistaber.dsl.build to compile. The ontology/ directory is 100% generated output. The predicate registry base.yaml is auto-generated from @declare/@sort/@enum directives — never edit it manually.

Overview

HLL is a domain-specific language for expressing halachic rules in a formal, machine-readable format. It compiles to Answer Set Programming (ASP) for execution by the Clingo solver within the Mistaber reasoning engine.

HLL extends standard ASP with 15 directives for vocabulary registration, world definitions, source provenance, and output control.

Grammar Structure

The HLL grammar is defined in mistaber/dsl/grammar.lark:

program     := (directive | rule | fact | constraint)*
directive   := world_directive | rule_directive | makor_directive | madrega_directive
             | declare_directive | sort_directive | subsort_directive | enum_directive
             | world_def_directive | endorses_directive | interprets_directive
             | interpretation_directive | show_directive | encoding_note_directive
             | constraint_directive
rule        := head ":-" body "."
fact        := head "."
constraint  := ":-" body "."

Directives

Directives are metadata annotations that provide context for rules, register vocabulary, define worlds, and control output.

Rule Metadata Directives

@world(identifier)

Specifies the modal world/context for subsequent rules.

@world(base)              % Default world
@world(rema)              % Rema's opinion
@world(mechaber)          % Mechaber's opinion

Usage: Enables multi-world semantics for representing machloket (disputes). File-level (one per file). When @world and @rule are both set, the emitter auto-generates rule(id)., scope(id, world)., makor(id, ...), madrega(id, level). facts and adds xclingo trace annotations.

Valid Worlds: base, mechaber, rema, gra, sefardi_yo, ashk_mb, ashk_ah

Warning: Multiple @world in one file produces a parser warning; last one wins.

@rule(identifier)

Opens a new block-scoped metadata context. Subsequent @makor and @madrega attach to this block. Multiple @rule blocks per file are fully supported.

@rule(r_basar_bechalav_issur)
@rule(r_gevinas_akum)

Block scoping: Each @rule opens a new block that extends until the next @rule or end of file.

Best Practice: Use descriptive names with r_ prefix, following topic-based naming: r_{topic}_{specific}.

Common prefixes: - r_bb_* — basar bechalav (meat and milk) - r_dag_* — fish-related - r_rema_* — Rema-specific (authority prefix for disputes)

Warnings: Duplicate @rule IDs produce a parser warning. @makor/@madrega without a preceding @rule produce orphan warnings.

@makor([source_list])

MANDATORY for normative rules. Cites halachic sources. Must appear after a @rule directive (attaches to the current block).

@makor([sa("Yoreh Deah 87:1")])
@makor([sa("YD:87:1"), rambam("Maachalot Asurot 9:1")])
@makor([mishnah("Chullin 8:1"), gemara("Chullin 103b")])

Source Types: | Type | Full Name | Example | |------|-----------|---------| | sa | Shulchan Aruch | sa("YD:87:1") | | rema | Rema gloss | rema("yd:87:3") | | rambam | Mishneh Torah | rambam("Maachalot Asurot 9:1") | | tur | Arba'ah Turim | tur("YD:87") | | mishnah | Mishnah | mishnah("Chullin 8:1") | | gemara | Talmud Bavli | gemara("Chullin 103b") | | tosefta | Tosefta | tosefta("Chullin 8:3") | | torah | Chumash | torah("shemot:23:19") | | beit_yosef | Beit Yosef | beit_yosef("yd:87") | | shach | Siftei Kohen | shach("yd:87:5") | | taz | Turei Zahav | taz("yd:87:3") | | yalkut_yosef | Yalkut Yosef | yalkut_yosef("yd:87:3") |

@madrega(level)

Specifies the obligation level of the current @rule block. Must appear after a @rule directive.

@madrega(d_oraita)    % Biblical
@madrega(d_rabanan)   % Rabbinic
@madrega(minhag)      % Custom
@madrega(chumra)      % Stringency

Strength Ordering: d_oraita > d_rabanan > minhag > chumra

Vocabulary Registration Directives

These directives auto-populate base.yaml via the build pipeline. Never edit base.yaml manually.

@sort(name, domain, description)

Declares a sort (type) in the ontology vocabulary.

Syntax:

@sort(name, domain, description)
@sort(name, domain, description, key=value, ...)

Arguments: - name — Sort identifier (lowercase) - domain — Classification domain: physical, normative, classification, temporal, meta - description — Human-readable description (quoted string)

Optional key-value pairs: - encoding_note="..." — Rationale for this sort

Examples:

@sort(food, physical, "Physical food items")
@sort(world, normative, "Possible worlds in modal logic")
@sort(food_category, classification, "Types of food (basar, chalav, etc.)")
@sort(integer, classification, "Numeric values")

Build effect: Registered in base.yaml under sorts section. Generates sort-membership atoms in meta.lp.

@subsort(child, parent)

Declares a subsort (type hierarchy) relationship.

Arguments: - child — The child sort (more specific) - parent — The parent sort (more general)

Examples:

@subsort(beheima, food)
@subsort(of, food)

Build effect: Generates subsort(child, parent). fact in compiled output. Enables type hierarchy reasoning in the engine.

@enum(sort_name, [members])

Declares an enumeration type with fixed members.

Arguments: - sort_name — The sort being enumerated (must be declared with @sort first) - [member1, member2, ...] — List of member constants

Examples:

@enum(food_category, [maakhal, basar, chalav, parve, beheima, chaya, of, dag, mashkeh, tavlin])
@enum(action_type, [achiila, bishul, hanaah, melicha, kavush, netila])
@enum(madrega_type, [d_oraita, d_rabanan, minhag, chumra])
@enum(status_type, [assur, mutar, chiyuv, reshut, mitzvah, sakanah])
@enum(context, [ctx_normal, ctx_hefsed, ctx_shaat_hadchak, ctx_choleh, ctx_pikuach_nefesh])

Build effect: Generates membership facts (food_category(basar)., food_category(chalav)., etc.) in compiled output. Registered in base.yaml under enums section.

@declare(name, [sorts], key=value, ...)

Declares a predicate in the ontology vocabulary. This is the primary way to register predicates — they are auto-registered in base.yaml by the build pipeline.

Arguments: - name — Predicate name (lowercase) - [sort1, sort2, ...] — List of argument sorts (defines arity)

Required key-value pairs: - hebrew="..." — Hebrew name - english="..." — English description

Optional key-value pairs: - cwa=true|false — Closed World Assumption (default: true) - derived=true|false — Whether this predicate is derived from rules (default: false) - mandatory=true|false — Whether instances must be declared (default: false) - note="..." — Implementation note

Examples:

@declare(is_food, [food], hebrew="מאכל", english="Entity is a food item", cwa=true)
@declare(food_cat, [food, food_category], hebrew="סוג_מאכל", english="Food category", cwa=true)
@declare(status, [food, status_type], hebrew="סטטוס", english="Normative status", cwa=false, derived=true)
@declare(accessible, [world, world], hebrew="נגיש", english="Modal accessibility relation", cwa=false)

Build effect: Registered in base.yaml under predicates section with full metadata. Generates predicate declaration atoms in meta.lp.

When to use: Use @declare for every new predicate you introduce. This ensures it appears in the predicate registry and the dashboard ontology view.

World Definition Directives

@world_def(world_name, parent)

Defines a Kripke world and its parent(s). This replaces manual world/1 and accessible/2 facts.

Syntax:

@world_def(world_name, parent)
@world_def(world_name, [parent1, parent2], key=value, ...)

Arguments: - world_name — World identifier - parent — Single parent world, or [list] for multiple inheritance

Optional key-value pairs: - era="..." — Historical era (e.g., "rishonim", "acharonim", "contemporary") - endorsement_mode="active" — World actively endorses inherited positions - encoding_note="..." — Why this world exists

Examples:

% Single parent
@world_def(mechaber, base)
@world_def(rema, base)
@world_def(sefardi_yo, mechaber)

% Multiple parents (diamond inheritance)
@world_def(ashk_ah, [rema, gra])

% With metadata
@world_def(shach, mechaber, era="acharonim")

Build effect: Generates world(name)., accessible(parent, name)., and world metadata atoms. Must appear at the TOP of world-specific .hll files.

@endorses(world, proposition, key=value, ...)

Marks a world's independent affirmation of an inherited position. Use this instead of asserts/2 when a world independently derives the same conclusion as its parent.

Arguments: - world — The world endorsing - proposition — The atom being endorsed (can include variables)

Required key-value: - when="body_condition" — The rule body condition (as a quoted string)

Optional key-value: - encoding_note="..." — Why this is an endorsement rather than inheritance

Examples:

@endorses(gra, issur(achiila, M, d_oraita),
    when="is_beheima_chalav_mixture(M)",
    encoding_note="Biur HaGra YD 87:1 cites Chullin 104b-115a directly")

@endorses(gra, issur(bishul, M, d_oraita),
    when="is_beheima_chalav_mixture(M)",
    encoding_note="Biur HaGra YD 87:1 independent derivation from Torah sources")

Build effect: Generates endorses(world, proposition). fact AND the standard asserts(world, proposition) :- body. rule. The holds_via/3 engine distinguishes endorsed from merely inherited holdings.

When to use vs asserts/2: - @endorses — World independently reaches the same conclusion as parent (e.g., GRA derives beheima d'oraita from Talmud, same as base world) - asserts/2 — World has a unique position not in parent

@interprets(commentator, authority)

Declares that a commentator interprets a particular authority. Must appear at the top of interpretation files, before any @interpretation directives.

Arguments: - commentator — The commentator's identifier (e.g., shach, taz) - authority — The authority being interpreted (e.g., mechaber)

Examples:

@interprets(shach, mechaber)
@interprets(taz, mechaber)

Build effect: Generates interprets(commentator, authority). fact. Also registers the commentator in the engine's interpretation resolution system.

@interpretation(commentator, rule_id, action, parameter)

Encodes a specific commentator's modification to a rule. Supports nested @makor and @encoding_note sub-directives.

Syntax:

@interpretation(commentator, rule_id, action, parameter)
    @makor([source1, source2, ...])
    @encoding_note("description")

Arguments: - commentator — Who is interpreting (e.g., shach, taz) - rule_id — The rule being interpreted (must be a declared @rule) - action — Type of modification: adds_condition, removes_condition, expands_scope, narrows_scope - parameter — The specific modification

Sub-directives (optional, indented): - @makor([...]) — Source citations for this interpretation - @encoding_note("...") — Explanation of the interpretation

Examples:

@interprets(shach, mechaber)

@interpretation(shach, r_bb_dag_sakana, adds_condition, cooked_together)
    @makor([shach("yd:87:5")])
    @encoding_note("Shach clarifies sakana applies specifically when cooked together")

@interprets(taz, mechaber)

@interpretation(taz, r_bb_dag_sakana, removes_condition, sakana)
    @makor([taz("yd:87:3")])
    @encoding_note("Taz argues this is a scribal error - original text said meat, not dairy")

Build effect: Generates adds_condition(commentator, rule_id, parameter). (or removes_condition, expands_scope, narrows_scope) fact, plus makor(interpretation(...), source). facts and encoding note comments.

Output and Documentation Directives

@show(predicate/arity)

Controls ASP output visibility. Use @show instead of #show in HLL files.

Examples:

@show(asserts/2)
@show(holds/2)
@show(override/3)
@show(machloket/4)

Build effect: Generates #show predicate/arity. in compiled .lp output.

Why use @show instead of #show: The build pipeline processes all @show directives to ensure consistent output filtering across all compiled files.

@encoding_note("description")

Attaches a rationale note to the preceding directive or rule. Used for documentation and displayed in the dashboard ontology view.

Usage contexts: - As a standalone directive after any other directive - As a nested sub-directive inside @interpretation - As a key-value pair inside @endorses or @sort (via encoding_note="...")

Examples:

% Standalone
@encoding_note("The three repetitions of lo tevashel teach three prohibitions")

% As kv-pair in @endorses
@endorses(gra, issur(achiila, M, d_oraita),
    when="is_beheima_chalav_mixture(M)",
    encoding_note="Biur HaGra YD 87:1 cites Chullin 104b-115a directly")

Build effect: Generates encoding_note(entity, "text"). atom in meta.lp. Displayed in the dashboard's ontology sidebar panel.

@constraint(name, category, "description")

Declares a named integrity constraint with an identifier, category, and description. The constraint body (:- body.) follows on the next line.

Arguments: - name — Constraint identifier (lowercase) - category — Category: integrity, disjointness, completeness - description — Human-readable description (quoted string)

Examples:

@constraint(no_dual_status, integrity, "Food cannot be both assur and mutar")
:- asserts(W, assur(F)), asserts(W, mutar(F)), world(W), is_food(F).

@constraint(exclusive_food_type, disjointness, "Food cannot be both basar and chalav")
:- food_type(F, basar), food_type(F, chalav).

Build effect: Generates the named constraint with a comment in the compiled output. The constraint body is passed through directly as an ASP integrity constraint.

Facts

Facts are unconditional statements about the world.

% Simple facts
is_food(chicken).
vessel(pot1).
mixture(m1).

% Facts with arguments
food_type(chicken, basar).
food_type(milk, chalav).
contains(m1, chicken).
contains(m1, milk).

% Classification facts
madrega(r_basar_bechalav, d_oraita).

Rules

Rules define logical implications.

% Basic rule
head :- body.

% Rule with multiple conditions (AND)
forbidden(W, achiila, M, ctx_normal) :-
    mixture(M),
    mixture_contains_basar(M),
    mixture_contains_chalav(M).

% Rule with negation
permitted(W, achiila, F, ctx_normal) :-
    food(F),
    food_type(F, parve),
    not has_issur(F).

Negation

Use not prefix for negation-as-failure:

% Permitted if not explicitly forbidden
permitted(W, A, F, C) :-
    food(F),
    not forbidden(W, A, F, C).

% Classification when not classified
classification_unknown(F) :-
    food(F),
    not food_type(F, basar),
    not food_type(F, chalav),
    not food_type(F, parve).

Warning: Negating OWA predicates generates a compiler warning.

Data Types

Identifiers (Constants)

Lowercase identifiers represent constants:

chicken         % Food item
basar           % Category
achiila         % Action type
ctx_normal      % Context
d_oraita        % Madrega level

Variables

Uppercase identifiers represent variables (bind to any value):

Food            % Any food
W               % Any world
M               % Any mixture
Category        % Any category

Convention: M = Mixture, F = Food, W = World, A = Action, L = Level (madrega)

Strings

Quoted text for source references and descriptions:

"Yoreh Deah 87:1"
"Rambam Hilchot Maachalot Asurot"

Numbers

Integer literals:

60              % Bitul ratio
24              % Hours for kavush
45              % Yad soledet temperature

Compound Terms

Nested function-like structures:

sa("YD:87:1")
rambam("Maachalot Asurot 9:1")
ratio(m1, 60)

Surface Syntax (Shortcuts)

HLL provides Hebrew-inspired shortcuts that normalize to canonical predicates.

Food Type Shortcuts

Shortcut Expands To
basar(X) food_type(X, basar)
chalav(X) food_type(X, chalav)
parve(X) food_type(X, parve)
beheima(X) food_type(X, beheima)
chaya(X) food_type(X, chaya)
of(X) food_type(X, of)
dag(X) food_type(X, dag)
mashkeh(X) food_type(X, mashkeh)
tavlin(X) food_type(X, tavlin)

Example:

% Surface syntax
basar(chicken).
chalav(milk).

% Normalizes to
food_type(chicken, basar).
food_type(milk, chalav).

Status Shortcuts

Shortcut Expands To
issur(A, F) forbidden(W, A, F, ctx_normal)
mutar(A, F) permitted(W, A, F, ctx_normal)

Example:

% Surface syntax
issur(achiila, treif_meat).

% Normalizes to (W from @world or variable)
forbidden(base, achiila, treif_meat, ctx_normal).

Comments

Line comments start with %:

% This is a comment
is_food(chicken).  % Inline comment

% Multi-line comments use multiple %
% Line 1
% Line 2

Complete Examples

Example 1: Classic Directives

Using @world/@rule/@makor/@madrega directives (block-scoped — multiple @rule blocks per file supported):

% =============================================================
% Basar B'Chalav (Meat and Milk) - Basic Prohibition
% =============================================================

@world(base)
@rule(r_basar_bechalav_issur)
@makor([sa("Yoreh Deah 87:1"), rambam("Maachalot Asurot 9:1")])
@madrega(d_oraita)

% === Food Declarations ===
is_food(beef).
is_food(chicken).
is_food(milk).
is_food(butter).
is_food(carrot).

% === Food Classifications ===
food_type(beef, beheima).      % Domesticated land animal
food_type(chicken, of).         % Bird
food_type(milk, chalav).        % Dairy
food_type(butter, chalav).      % Dairy
food_type(carrot, parve).       % Neutral

% === Mixture Declaration ===
mixture(beef_milk_stew).
contains(beef_milk_stew, beef).
contains(beef_milk_stew, milk).

% === Prohibition Rule ===
forbidden(W, achiila, M, ctx_normal) :-
    world(W),
    mixture(M),
    mixture_is_basar_bechalav(M).

% === Permission for Parve ===
permitted(W, achiila, F, ctx_normal) :-
    world(W),
    food(F),
    food_type(F, parve),
    not has_issur(F).

Example 2: DSL-First with Vocabulary Registration

Using @sort, @declare, @world_def for a multi-rule world file:

% mistaber/dsl/worlds/mechaber.hll
% Mechaber world — DSL-first architecture

@sort(food, physical, "Physical food items")
@sort(food_category, classification, "Types of food")
@enum(food_category, [basar, chalav, parve, beheima, chaya, of, dag])

@declare(is_food, [food], hebrew="מאכל", english="Entity is a food item", cwa=true)
@declare(food_cat, [food, food_category], hebrew="סוג_מאכל", english="Food category", cwa=true)

@world_def(mechaber, base)

% === Rule metadata (explicit ASP predicates) ===
rule(r_bb_beheima_achiila).
makor(r_bb_beheima_achiila, sa("yd:87:1")).
madrega(r_bb_beheima_achiila, d_oraita).
scope(r_bb_beheima_achiila, mechaber).

asserts(mechaber, issur(achiila, M, d_oraita)) :-
    is_beheima_chalav_mixture(M).

rule(r_bb_dag_sakana).
makor(r_bb_dag_sakana, sa("yd:87:3")).
scope(r_bb_dag_sakana, mechaber).

asserts(mechaber, sakana(M)) :-
    is_dag_chalav_mixture(M).

@show(asserts/2)
@show(holds/2)

Build Pipeline

After writing or editing any .hll file in mistaber/dsl/, run:

python -m mistaber.dsl.build

The build pipeline has 4 phases:

  1. Collect & Parse — Find .hll files by layer order, parse ASTs, extract directives into BuildManifest
  2. Registry MergeYAMLMerger merges @declare/@sort/@enum/@world_def/@interprets/@interpretation into base.yaml
  3. Compile — Run each .hll through normalize → type_check → emit, producing .lp files in ontology/
  4. Generate Meta — Rebuild meta.lp from updated base.yaml

Layer order (each layer may reference sorts/predicates from earlier layers): schemabaseworldsengineinterpretationscorpus

Passthrough files: .lp files in dsl/ (e.g., engine/preferences.lp for asprin directives) are copied verbatim to ontology/. A naming conflict (both foo.hll and foo.lp in the same directory) is an error.

Atomic writes: All outputs are written to disk only after all 4 phases succeed. If any phase fails, no files are modified.

Type System

Normative Predicates

These predicates make halachic determinations:

Predicate Arity Description
forbidden(world, action, food, context) 4 Action on food is prohibited
permitted(world, action, food, context) 4 Action on food is allowed
safek(food) 1 Food has doubtful status

Requirement: All normative predicates require @makor directive.

Classification Predicates

Predicate Arity Description
is_food(food) 1 Entity is a food item
food_type(food, category) 2 Food belongs to category
food_cat(food, category) 2 Alias for food_type
mixture(mixture) 1 Entity is a mixture
contains(mixture, food) 2 Mixture contains food
vessel(vessel) 1 Entity is a vessel

Enumerated Values

Food Categories: maakhal, basar, chalav, parve, beheima, chaya, of, dag, mashkeh, tavlin

Action Types: achiila (eating), bishul (cooking), hanaah (benefit)

Contexts: ctx_normal, ctx_hefsed, ctx_shaat_hadchak, ctx_choleh, ctx_pikuach_nefesh

Madrega Levels: d_oraita, d_rabanan, minhag, chumra

Type Checker

The type checker validates the normalized AST against the predicate registry (mistaber/dsl/vocabulary/base.yaml). Most issues are warnings (not errors) because ASP supports multi-arity predicates and local helpers.

Errors (stop compilation)

Check Description
Missing hebrew/english in @declare Required fields for predicate registration
Undefined sort in @declare signature Sort must exist in registry or local @sort
Arity conflict with existing predicate Same predicate name, different arity already registered
Invalid @sort domain Must be: physical, normative, classification, temporal, meta
Invalid @interpretation action Must be: adds_condition, removes_condition, restricts_scope, expands_scope
Self-referential @world_def parent World cannot be its own parent
Invalid @madrega value Must match registry's madrega_type enum

Warnings (compilation continues)

Check Description
Undeclared predicate Predicate not in registry or local @declare
Arity mismatch Argument count differs from declaration
Invalid enum value Constant not in enumeration
OWA predicate with negation May cause unsafe permissiveness
Normative rule without @makor Source citations expected

Error Messages

Parse Errors

ParseError: Parse error at line 5, column 12: Unexpected token

Common Causes: - Missing period at end of fact/rule - Unbalanced parentheses - Invalid identifier (starting with uppercase when constant expected)

Normalization Errors

NormalizationError: issur() expects exactly 2 arguments, got 1

Common Causes: - Wrong arity for surface syntax predicates - Using undefined shortcuts

Type Check Errors

TypeCheckError: Undeclared predicate: unknwon_pred/2
TypeCheckError: Arity mismatch for food_type: expected 2, got 3
TypeCheckError: Missing @makor directive for normative rule with head: forbidden
TypeCheckError: Undefined sort 'vessel_type' in @declare signature
TypeCheckError: Invalid @sort domain 'spatial' - must be: physical, normative, classification, temporal, meta

Type Check Warnings

TypeCheckWarning: Using negation with OWA predicate 'permitted' may cause unsafe permissiveness

Meaning: Negating an open-world predicate can lead to unintended conclusions.

Best Practices

  1. Always use @makor: Cite sources for all normative rules
  2. Use descriptive rule IDs: r_basar_bechalav_issur not r1
  3. Use @declare for new predicates: Register every new predicate via @declare in .hll
  4. Group related facts: Organize by entity type
  5. Comment complex rules: Explain halachic reasoning
  6. Use surface syntax: basar(X) is clearer than food_type(X, basar)
  7. Test incrementally: Compile and test small changes
  8. Avoid OWA negation: Prefer explicit positive conditions
  9. Run the build: Always run python -m mistaber.dsl.build after editing .hll files