Phase 1-2 build: Steps 5-6, 8-13
Shoresh Engine (Haskell, Step 5) - 12 modules: Types, Lexicon, Binyanim, Teamim, Gematria, Graph, KetivQere, Smikhut, Havdalah, Kriah, KiTov, Firmware - 33 EUnit tests, 0 failures - Firmware module verifies L1 SHA-256 hash at startup Talmudic Engine (SWI-Prolog, Step 6) - 15 modules: halt, reasoning, specification, interface - 57 PlUnit tests, 0 failures - Three cardinal halt conditions: avodah_zarah, shfichut_damim, gilui_arayot Vav Pipeline (Elixir, Steps 8-11) - 14 modules: BereishitCycle, Shema, Cloud, Manna, L1Reader, Specification, HaltConditions, ShabbatTimer, CycleLog, Chatzotzrot, ShoreshEngineClient, TalmudicEngineClient, Application - 46 ExUnit tests, 0 failures - Nishma-first architecture: hears before it speaks Sanhedrin Framework (Erlang/OTP, Step 12) - 8 modules: sanhedrin_app, sanhedrin_sup, escalation, panel_sup, panel, judge, clerk, halt_monitor - 46 EUnit tests, 0 failures (includes fix: rejected outcome now reachable) - Panel sizes: 1/3/23/71 — unanimous capital guilty → acquit (Sanhedrin 17a) - Minority opinions append-only, never deleted Korban Processor (Python, Step 13) - 6 modules: korban, shechitah, semichah, zerikah, haktarah, router Builder tooling: AGENTS.md, opencode.json, memory/, src/tools/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
622e814c9e
commit
80ec364da6
86 files changed
+9646
No files matched your search
@@ -0,0 +1,291 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
-- | Te'amim — the cantillation mark reader.
|
||||
--
|
||||
-- Architecture §3.1:
|
||||
-- "The marks are RECEIVED from L1 — they are on the text as transmitted.
|
||||
-- The parse tree is CONSTRUCTED by the reader in the act of reading.
|
||||
-- This is not pre-computation. This is literacy."
|
||||
--
|
||||
-- The te'amim system is hierarchical:
|
||||
-- - Melakhim (kings/disjunctors) SEPARATE
|
||||
-- - Meshartim (servants/conjunctors) CONNECT
|
||||
--
|
||||
-- The strongest disjunctor (sof-pasuk) ends the verse.
|
||||
-- The next strongest (etnachta) creates the primary division.
|
||||
-- Within each half, subordinate disjunctors create further divisions.
|
||||
-- The result is a binary tree — the verse's own syntactic structure.
|
||||
--
|
||||
-- L1 stores the mark names as strings in each word's "teamim" array.
|
||||
-- This module reads those strings and builds the tree.
|
||||
module Shoresh.Teamim
|
||||
( -- * Types
|
||||
Taam(..)
|
||||
, TaamRole(..)
|
||||
, AnnotatedWord(..)
|
||||
, ParsedVerse(..)
|
||||
-- * Classification
|
||||
, taamRole
|
||||
, taamRank
|
||||
, taamFromName
|
||||
-- * Parsing
|
||||
, readTeamim
|
||||
, primaryDivision
|
||||
, boundGroups
|
||||
) where
|
||||
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.List (maximumBy)
|
||||
import Data.Ord (comparing)
|
||||
|
||||
-- | Cantillation marks.
|
||||
-- The full system has ~30 marks. We enumerate the ones that appear
|
||||
-- in L1 data (parsed from WLC Unicode codepoints).
|
||||
data Taam
|
||||
= SofPasuk -- ^ Verse end (terminal) — strongest disjunctor
|
||||
| Etnachta -- ^ Primary division — second strongest
|
||||
| ZakefGadol -- ^ Secondary division (strong)
|
||||
| ZakefKatan -- ^ Secondary division
|
||||
| Segol -- ^ Tertiary
|
||||
| Shalshelet -- ^ Rare, strong disjunctor
|
||||
| Tipcha -- ^ Tertiary division (near-subordinate to etnachta/zakef)
|
||||
| Revia -- ^ Mid-level disjunctor
|
||||
| Zarka -- ^ Pre-segol disjunctor
|
||||
| Pashta -- ^ Subordinate disjunctor
|
||||
| Yetiv -- ^ Subordinate disjunctor
|
||||
| Tevir -- ^ Subordinate disjunctor
|
||||
| Geresh -- ^ Subordinate disjunctor
|
||||
| GershayimMark -- ^ Double geresh — subordinate disjunctor
|
||||
| Pazer -- ^ Subordinate disjunctor
|
||||
| PazerGadol -- ^ Rare, subordinate disjunctor
|
||||
| Telisha_Gedola -- ^ Subordinate disjunctor
|
||||
| Telisha_Qetana -- ^ Conjunctor
|
||||
| Munach -- ^ Servant — connects to following disjunctor
|
||||
| Mercha -- ^ Servant — connects to following disjunctor
|
||||
| MerchaKefula -- ^ Double mercha — servant
|
||||
| Darga -- ^ Servant
|
||||
| Kadma -- ^ Servant (azla)
|
||||
| Mahapakh -- ^ Servant
|
||||
| Yerach_Ben_Yomo -- ^ Rare servant
|
||||
| MetegSiluq -- ^ Dual identity: siluq (disjunctor on final word) / meteg
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
-- | Role classification: kings separate, servants connect.
|
||||
data TaamRole
|
||||
= Melekh -- ^ Disjunctor (separator)
|
||||
| Mesharet -- ^ Conjunctor (connector)
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | Classify a taam as melekh (disjunctor) or mesharet (conjunctor).
|
||||
taamRole :: Taam -> TaamRole
|
||||
taamRole t = case t of
|
||||
SofPasuk -> Melekh
|
||||
Etnachta -> Melekh
|
||||
ZakefGadol -> Melekh
|
||||
ZakefKatan -> Melekh
|
||||
Segol -> Melekh
|
||||
Shalshelet -> Melekh
|
||||
Tipcha -> Melekh
|
||||
Revia -> Melekh
|
||||
Zarka -> Melekh
|
||||
Pashta -> Melekh
|
||||
Yetiv -> Melekh
|
||||
Tevir -> Melekh
|
||||
Geresh -> Melekh
|
||||
GershayimMark -> Melekh
|
||||
Pazer -> Melekh
|
||||
PazerGadol -> Melekh
|
||||
Telisha_Gedola -> Melekh
|
||||
MetegSiluq -> Melekh -- siluq on final word is a disjunctor
|
||||
-- Servants
|
||||
Telisha_Qetana -> Mesharet
|
||||
Munach -> Mesharet
|
||||
Mercha -> Mesharet
|
||||
MerchaKefula -> Mesharet
|
||||
Darga -> Mesharet
|
||||
Kadma -> Mesharet
|
||||
Mahapakh -> Mesharet
|
||||
Yerach_Ben_Yomo -> Mesharet
|
||||
|
||||
-- | Disjunctive rank — higher means stronger separator.
|
||||
-- This determines where the tree splits.
|
||||
-- SofPasuk is strongest (it ends the verse).
|
||||
-- Etnachta is next (primary division).
|
||||
-- Then the hierarchy descends.
|
||||
taamRank :: Taam -> Int
|
||||
taamRank t = case t of
|
||||
SofPasuk -> 100
|
||||
Etnachta -> 90
|
||||
ZakefGadol -> 70
|
||||
ZakefKatan -> 65
|
||||
Segol -> 60
|
||||
Shalshelet -> 58
|
||||
Revia -> 50
|
||||
Tipcha -> 45
|
||||
Zarka -> 40
|
||||
Pashta -> 35
|
||||
Yetiv -> 33
|
||||
Tevir -> 30
|
||||
Geresh -> 25
|
||||
GershayimMark -> 25
|
||||
Pazer -> 20
|
||||
PazerGadol -> 20
|
||||
Telisha_Gedola -> 15
|
||||
MetegSiluq -> 80 -- siluq ranks just below etnachta
|
||||
-- Servants get rank 0 — they do not create divisions
|
||||
_ -> 0
|
||||
|
||||
-- | Parse a taam name from L1 JSON data.
|
||||
-- L1 stores these as strings like "tipcha", "etnachta", "munach".
|
||||
-- Build log correction #1: "meteg_siluq" is in TEAMIM_MAP due to
|
||||
-- U+05BD dual identity.
|
||||
taamFromName :: Text -> Maybe Taam
|
||||
taamFromName name = case T.toLower name of
|
||||
"sof_pasuk" -> Just SofPasuk
|
||||
"sof-pasuk" -> Just SofPasuk
|
||||
"sofpasuk" -> Just SofPasuk
|
||||
"etnachta" -> Just Etnachta
|
||||
"etnahta" -> Just Etnachta
|
||||
"zakef_gadol" -> Just ZakefGadol
|
||||
"zakef-gadol" -> Just ZakefGadol
|
||||
"zaqef_gadol" -> Just ZakefGadol -- L1 spelling
|
||||
"zakef_katan" -> Just ZakefKatan
|
||||
"zakef-katan" -> Just ZakefKatan
|
||||
"zaqef_qatan" -> Just ZakefKatan -- L1 spelling
|
||||
"segol" -> Just Segol
|
||||
"segol_accent" -> Just Segol -- L1 spelling
|
||||
"shalshelet" -> Just Shalshelet
|
||||
"tipcha" -> Just Tipcha
|
||||
"tipeha" -> Just Tipcha
|
||||
"revia" -> Just Revia
|
||||
"revi'i" -> Just Revia
|
||||
"zarka" -> Just Zarka
|
||||
"pashta" -> Just Pashta
|
||||
"yetiv" -> Just Yetiv
|
||||
"tevir" -> Just Tevir
|
||||
"geresh" -> Just Geresh
|
||||
"gershayim" -> Just GershayimMark
|
||||
"pazer" -> Just Pazer
|
||||
"pazer_gadol" -> Just PazerGadol
|
||||
"telisha_gedola" -> Just Telisha_Gedola
|
||||
"telisha_qetana" -> Just Telisha_Qetana
|
||||
"munach" -> Just Munach
|
||||
"mercha" -> Just Mercha
|
||||
"mercha_kefula" -> Just MerchaKefula
|
||||
"darga" -> Just Darga
|
||||
"kadma" -> Just Kadma
|
||||
"qadma" -> Just Kadma -- L1 spelling
|
||||
"azla" -> Just Kadma
|
||||
"mahapakh" -> Just Mahapakh
|
||||
"yerach_ben_yomo" -> Just Yerach_Ben_Yomo
|
||||
"meteg_siluq" -> Just MetegSiluq
|
||||
"siluq" -> Just MetegSiluq
|
||||
"meteg" -> Just MetegSiluq
|
||||
"zinor" -> Just Zarka -- L1: zinor is variant name for zarka
|
||||
"geresh_muqdam" -> Just Geresh -- L1: pre-positioned geresh variant
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
-- | A word annotated with its cantillation mark and role.
|
||||
-- This is what we receive from L1 after parsing the raw mark names.
|
||||
data AnnotatedWord = AnnotatedWord
|
||||
{ awWord :: !Text -- ^ The Hebrew word text
|
||||
, awTaam :: !Taam -- ^ The primary cantillation mark
|
||||
, awRole :: !TaamRole -- ^ King or servant
|
||||
} deriving (Show)
|
||||
|
||||
-- | A parsed verse: binary tree where branches are te'amim divisions.
|
||||
-- Leaf: a single annotated word.
|
||||
-- Branch: a disjunctor splitting the verse into two halves.
|
||||
--
|
||||
-- This tree is CONSTRUCTED by readTeamim from the raw marks.
|
||||
-- It is not stored in L1. This is the act of reading.
|
||||
data ParsedVerse
|
||||
= Leaf !AnnotatedWord
|
||||
| Branch !Taam !ParsedVerse !ParsedVerse
|
||||
deriving (Show)
|
||||
|
||||
|
||||
-- | Read the te'amim — construct the parse tree from annotated words.
|
||||
--
|
||||
-- Algorithm: find the strongest disjunctor, split there,
|
||||
-- recurse on each half. This mirrors how a human reader
|
||||
-- parses the verse structure from the cantillation marks.
|
||||
--
|
||||
-- The marks are received. The tree is built in the act of reading.
|
||||
readTeamim :: [AnnotatedWord] -> ParsedVerse
|
||||
readTeamim [] = error "readTeamim: empty word list — a verse cannot be empty"
|
||||
readTeamim [w] = Leaf w
|
||||
readTeamim ws =
|
||||
-- Find the word with the strongest disjunctive mark
|
||||
let indexed = zip [0..] ws
|
||||
disjunctors = [ (i, w) | (i, w) <- indexed
|
||||
, awRole w == Melekh
|
||||
, i < length ws - 1 -- don't split after the last word
|
||||
]
|
||||
in case disjunctors of
|
||||
[] ->
|
||||
-- No disjunctors found (all servants) — treat as a chain
|
||||
-- This shouldn't happen in well-formed verses but we handle it
|
||||
buildChain ws
|
||||
_ ->
|
||||
-- Split at the strongest disjunctor
|
||||
let (splitIdx, splitWord) = maximumBy (comparing (taamRank . awTaam . snd)) disjunctors
|
||||
(left, right) = splitAt (splitIdx + 1) ws
|
||||
splitTaam = awTaam splitWord
|
||||
in Branch splitTaam (readTeamim left) (readTeamim right)
|
||||
|
||||
-- | Build a linear chain when no disjunctors are present.
|
||||
buildChain :: [AnnotatedWord] -> ParsedVerse
|
||||
buildChain [] = error "buildChain: empty"
|
||||
buildChain [w] = Leaf w
|
||||
buildChain (w:ws) = Branch (awTaam w) (Leaf w) (buildChain ws)
|
||||
|
||||
|
||||
-- | Find the primary division (etnachta) of a parsed verse.
|
||||
-- Returns the two halves of the verse.
|
||||
-- The etnachta divides the verse into its two main parts.
|
||||
primaryDivision :: ParsedVerse -> (ParsedVerse, ParsedVerse)
|
||||
primaryDivision (Leaf w) = (Leaf w, Leaf w)
|
||||
primaryDivision (Branch t left right)
|
||||
| t == Etnachta = (left, right)
|
||||
| otherwise =
|
||||
-- The etnachta might be deeper in the tree.
|
||||
-- In a well-formed parse, it should be at the top level.
|
||||
-- If not, search left then right.
|
||||
let (ll, lr) = primaryDivision left
|
||||
(rl, rr) = primaryDivision right
|
||||
in if hasEtnachta left
|
||||
then (ll, Branch t lr right)
|
||||
else if hasEtnachta right
|
||||
then (Branch t left rl, rr)
|
||||
else (left, right) -- no etnachta found — use top-level split
|
||||
|
||||
-- | Check if a parsed tree contains an etnachta division.
|
||||
hasEtnachta :: ParsedVerse -> Bool
|
||||
hasEtnachta (Leaf _) = False
|
||||
hasEtnachta (Branch t l r) = t == Etnachta || hasEtnachta l || hasEtnachta r
|
||||
|
||||
|
||||
-- | Identify which words are bound together by meshartim (servant chains).
|
||||
-- Words connected by servants form semantic units.
|
||||
-- Returns groups of words that are bound together.
|
||||
boundGroups :: ParsedVerse -> [[Text]]
|
||||
boundGroups = go []
|
||||
where
|
||||
go acc (Leaf w) = reverse (addToCurrentGroup (awWord w) acc)
|
||||
go acc (Branch t left right)
|
||||
| taamRole t == Mesharet =
|
||||
-- Servants connect — merge groups
|
||||
let leftGroups = go acc left
|
||||
in go leftGroups right
|
||||
| otherwise =
|
||||
-- Kings separate — start new group for right
|
||||
let leftGroups = go acc left
|
||||
-- Close current group, start fresh for right
|
||||
rightGroups = go ([] : leftGroups) right
|
||||
in rightGroups
|
||||
|
||||
addToCurrentGroup w [] = [[w]]
|
||||
addToCurrentGroup w (g:gs) = (w:g) : gs
|
||||
Reference in new issue
Block a user