specs §3.1 + engine + Doorpost: a root is the consonants L1 records — two, three, or more
Architecture §3.1 typed a root as exactly three consonants
(`Shoresh {c1,c2,c3}`) and listed data/roots.json and
data/torah_refs.json in the engine's file tree. L1 has 115 roots that
are not three letters (32 of two, 83 of four or more), and the engine
has no data directory: the roots are in L1.
The code had followed the spec's type and paid for it. textToShoresh
padded a two-letter root with He — בן became בנה, כל became כלה — and
returned Nothing for anything longer, so Lexicon.buildLexiconFromVerses
either falsified those roots or dropped them. Shoresh.Roots (tablet 33)
had stepped around the type by carrying roots as Text.
Spec:
- §3.1 data structures: `newtype Shoresh = Shoresh { radicals ::
[Consonant] }` — two or more, never padded or truncated; `mkShoresh`,
`triliteral`.
- relatedRoots / neighbors: same length, differing in exactly one
consonant (for a root of three, the old "sharing 2 consonants").
- File tree: Roots.hs and Server.hs added; data/ removed, with a line
saying where the roots are; cabal.project.freeze listed; aeson's
purpose corrected.
Engine:
- Types: the newtype, mkShoresh :: [Consonant] -> Maybe Shoresh,
triliteral, textToShoresh without padding.
- Gematria: sum over the radicals. Graph: neighbors among roots of the
same length; neighborsByPosition labels the shared positions
("c1c2" for a triliteral, as before).
- Server: consonant names through the type.
- Spec.hs: בן stays two letters (gematria 52, not 57); שמאל is held
whole; the graph compares only roots of equal length. 59 examples.
Doorpost @expected_hash 094b8164… → f653ef05…, in this commit, with the
witnessed file that moved it (architecture).
Read before committing: Haskell 59/0; Elixir 474/0 on seeds 0 7 42;
both images built and run together with the real binary — Shemot 3:14
answers ב-ן (two letters), Bereishit 48:13 answers ש-מ-א-ל (four),
Devarim 6:5 unchanged; local specification_identity verified
f653ef05…; no engine errors or fallbacks.
Not touched: §3.1's dependency list omits bytestring, filepath and
directory, which the cabal file has.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4f1879d7ce
commit
5ff1b73d00
8 files changed
+153
-96
No files matched your search
@@ -62,7 +62,7 @@ runVerification = do
|
|||||||
|
|
||||||
-- 1. Root activation through all 7 binyanim
|
-- 1. Root activation through all 7 binyanim
|
||||||
putStrLn "--- Test 1: Root activation (ב-ר-א through 7 binyanim) ---"
|
putStrLn "--- Test 1: Root activation (ב-ר-א through 7 binyanim) ---"
|
||||||
let bara = mkShoresh Bet Resh Aleph -- ברא = create
|
let bara = triliteral Bet Resh Aleph -- ברא = create
|
||||||
let expanded = expand bara
|
let expanded = expand bara
|
||||||
mapM_ (\(b, p) -> do
|
mapM_ (\(b, p) -> do
|
||||||
TIO.putStr $ " " <> T.pack (show b) <> ": "
|
TIO.putStr $ " " <> T.pack (show b) <> ": "
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ letterValue c = case c of
|
|||||||
Shin -> 300
|
Shin -> 300
|
||||||
Tav -> 400
|
Tav -> 400
|
||||||
|
|
||||||
-- | Gematria of a triliteral root.
|
-- | Gematria of a root — the sum of its consonants, however many.
|
||||||
gematriaRoot :: Shoresh -> Int
|
gematriaRoot :: Shoresh -> Int
|
||||||
gematriaRoot (Shoresh a b d) = letterValue a + letterValue b + letterValue d
|
gematriaRoot = sum . map letterValue . radicals
|
||||||
|
|
||||||
-- | Gematria of a single Hebrew character.
|
-- | Gematria of a single Hebrew character.
|
||||||
-- Returns 0 for non-consonant characters (vowels, cantillation marks).
|
-- Returns 0 for non-consonant characters (vowels, cantillation marks).
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
-- neighbors :: Shoresh -> ShoreshLexicon -> [ShoreshEntry]
|
-- neighbors :: Shoresh -> ShoreshLexicon -> [ShoreshEntry]
|
||||||
-- "Find all roots sharing 2 consonants (semantic neighborhood)"
|
-- "Find all roots sharing 2 consonants (semantic neighborhood)"
|
||||||
--
|
--
|
||||||
-- Two roots are neighbors if they share 2 of 3 consonants
|
-- Two roots are neighbors if they have the same number of consonants
|
||||||
-- in the same positions. This captures the Hebrew linguistic
|
-- and differ in exactly one position — for a root of three, the spec's
|
||||||
-- phenomenon where related roots share consonants.
|
-- "sharing 2 consonants". This captures the Hebrew linguistic phenomenon
|
||||||
|
-- where related roots share consonants. Roots of different lengths are
|
||||||
|
-- not compared.
|
||||||
module Shoresh.Graph
|
module Shoresh.Graph
|
||||||
( neighbors
|
( neighbors
|
||||||
, sharedConsonants
|
, sharedConsonants
|
||||||
@@ -20,30 +22,42 @@ import Shoresh.Lexicon (ShoreshLexicon, allRoots, lookupRoot)
|
|||||||
import Data.Maybe (mapMaybe)
|
import Data.Maybe (mapMaybe)
|
||||||
|
|
||||||
-- | Count shared consonants between two roots (position-sensitive).
|
-- | Count shared consonants between two roots (position-sensitive).
|
||||||
|
-- Roots of different lengths share none: there is no position to align.
|
||||||
sharedConsonants :: Shoresh -> Shoresh -> Int
|
sharedConsonants :: Shoresh -> Shoresh -> Int
|
||||||
sharedConsonants (Shoresh a1 b1 c1') (Shoresh a2 b2 c2') =
|
sharedConsonants x y
|
||||||
(if a1 == a2 then 1 else 0) +
|
| length rx /= length ry = 0
|
||||||
(if b1 == b2 then 1 else 0) +
|
| otherwise = length (filter id (zipWith (==) rx ry))
|
||||||
(if c1' == c2' then 1 else 0)
|
where
|
||||||
|
rx = radicals x
|
||||||
|
ry = radicals y
|
||||||
|
|
||||||
-- | Find all roots sharing exactly 2 consonants in the same positions.
|
-- | Find all roots of the same length differing in exactly one consonant.
|
||||||
neighbors :: Shoresh -> ShoreshLexicon -> [ShoreshEntry]
|
neighbors :: Shoresh -> ShoreshLexicon -> [ShoreshEntry]
|
||||||
neighbors sh lexicon =
|
neighbors sh lexicon =
|
||||||
mapMaybe (\sh' ->
|
mapMaybe (\sh' ->
|
||||||
if sh' /= sh && sharedConsonants sh sh' >= 2
|
if sh' /= sh && isNeighbor sh sh'
|
||||||
then lookupRoot sh' lexicon
|
then lookupRoot sh' lexicon
|
||||||
else Nothing
|
else Nothing
|
||||||
) (allRoots lexicon)
|
) (allRoots lexicon)
|
||||||
|
|
||||||
-- | Find neighbors that share consonants at specific positions.
|
isNeighbor :: Shoresh -> Shoresh -> Bool
|
||||||
-- Returns (position_shared, neighbor_entry) pairs.
|
isNeighbor x y =
|
||||||
|
length (radicals x) == length (radicals y)
|
||||||
|
&& sharedConsonants x y == length (radicals x) - 1
|
||||||
|
|
||||||
|
-- | Find neighbors, labelled by the positions they share.
|
||||||
|
-- Returns (positions_shared, neighbor_entry) pairs — for a root of
|
||||||
|
-- three, "c1c2", "c1c3" or "c2c3".
|
||||||
neighborsByPosition :: Shoresh -> ShoreshLexicon -> [(String, ShoreshEntry)]
|
neighborsByPosition :: Shoresh -> ShoreshLexicon -> [(String, ShoreshEntry)]
|
||||||
neighborsByPosition sh@(Shoresh a b d) lexicon =
|
neighborsByPosition sh lexicon =
|
||||||
concatMap checkNeighbor (allRoots lexicon)
|
concatMap checkNeighbor (allRoots lexicon)
|
||||||
where
|
where
|
||||||
checkNeighbor sh'@(Shoresh a' b' d')
|
checkNeighbor sh'
|
||||||
| sh' == sh = []
|
| sh' /= sh && isNeighbor sh sh' =
|
||||||
| a == a' && b == b' = maybe [] (\e -> [("c1c2", e)]) (lookupRoot sh' lexicon)
|
maybe [] (\e -> [(sharedLabel sh', e)]) (lookupRoot sh' lexicon)
|
||||||
| a == a' && d == d' = maybe [] (\e -> [("c1c3", e)]) (lookupRoot sh' lexicon)
|
|
||||||
| b == b' && d == d' = maybe [] (\e -> [("c2c3", e)]) (lookupRoot sh' lexicon)
|
|
||||||
| otherwise = []
|
| otherwise = []
|
||||||
|
|
||||||
|
sharedLabel sh' =
|
||||||
|
concat [ 'c' : show i
|
||||||
|
| (i, same) <- zip [1 :: Int ..] (zipWith (==) (radicals sh) (radicals sh'))
|
||||||
|
, same ]
|
||||||
@@ -248,10 +248,10 @@ extractScope _ = Nothing
|
|||||||
-- | Render a root heard from L1 in the port's contract shape
|
-- | Render a root heard from L1 in the port's contract shape
|
||||||
-- (Construction spec §3.1), plus where it was heard from.
|
-- (Construction spec §3.1), plus where it was heard from.
|
||||||
--
|
--
|
||||||
-- The shoresh is rendered exactly as L1 records it. A two-letter root
|
-- The shoresh is rendered exactly as L1 records it: a two-letter root
|
||||||
-- (כל, שמ) stays two letters: the old path padded it with He to fit the
|
-- (בן, יד) stays two letters and a longer one stays whole. The old path
|
||||||
-- triliteral type and reported a root — and a gematria — the text does
|
-- padded two letters with He to fit a triliteral type and reported a
|
||||||
-- not contain.
|
-- root — and a gematria — the text does not contain.
|
||||||
--
|
--
|
||||||
-- @binyan@ is the stem the verb actually stands in, read from the
|
-- @binyan@ is the stem the verb actually stands in, read from the
|
||||||
-- verse's morphology. It is null for a noun and for a word that is not
|
-- verse's morphology. It is null for a noun and for a word that is not
|
||||||
@@ -259,8 +259,11 @@ extractScope _ = Nothing
|
|||||||
activateRoot :: ResolvedRoot -> Value
|
activateRoot :: ResolvedRoot -> Value
|
||||||
activateRoot r =
|
activateRoot r =
|
||||||
let sh = rrShoresh r
|
let sh = rrShoresh r
|
||||||
|
-- L1's roots are consonants, so this parse cannot fail; if one
|
||||||
|
-- ever did, the names are simply absent — never invented.
|
||||||
|
names = maybe [] (map consonantName . radicals) (textToShoresh sh)
|
||||||
in object
|
in object
|
||||||
[ "shoresh" .= mapMaybe (fmap consonantName . charToConsonant) (T.unpack sh)
|
[ "shoresh" .= names
|
||||||
, "shoresh_hebrew" .= sh
|
, "shoresh_hebrew" .= sh
|
||||||
, "binyan" .= fmap (T.pack . show) (rrBinyan r)
|
, "binyan" .= fmap (T.pack . show) (rrBinyan r)
|
||||||
, "domain" .= ("unknown" :: Text) -- Phase 3: domain lookup
|
, "domain" .= ("unknown" :: Text) -- Phase 3: domain lookup
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ module Shoresh.Types
|
|||||||
-- * Triliteral Roots
|
-- * Triliteral Roots
|
||||||
, Shoresh(..)
|
, Shoresh(..)
|
||||||
, mkShoresh
|
, mkShoresh
|
||||||
|
, triliteral
|
||||||
, shoreshToText
|
, shoreshToText
|
||||||
, textToShoresh
|
, textToShoresh
|
||||||
-- * Binyanim (Transformation Operators)
|
-- * Binyanim (Transformation Operators)
|
||||||
@@ -96,39 +97,39 @@ consonantName c = case c of
|
|||||||
Shin -> "shin"; Tav -> "tav"
|
Shin -> "shin"; Tav -> "tav"
|
||||||
|
|
||||||
|
|
||||||
-- | A triliteral root — the atomic semantic unit.
|
-- | A root — the atomic semantic unit: the consonants under which the
|
||||||
-- Most Hebrew roots are three consonants. Some are two (biconsonantal).
|
-- lexicon files a word, exactly as L1 records them.
|
||||||
-- The spec says triliteral; L1 shows some bicons (e.g. "שמ").
|
--
|
||||||
-- We model the common case. Bicons get a silent third.
|
-- Most roots are three consonants. Torah also has roots of two (בן, יד)
|
||||||
data Shoresh = Shoresh
|
-- and of four or more (שמאל). A root is never padded and never truncated
|
||||||
{ c1 :: !Consonant
|
-- to make it three: until 2026-10-04 this type held exactly three
|
||||||
, c2 :: !Consonant
|
-- consonants, a two-letter root was given a He the text never wrote
|
||||||
, c3 :: !Consonant
|
-- (כל became כלה), and a longer one could not be held at all.
|
||||||
|
newtype Shoresh = Shoresh
|
||||||
|
{ radicals :: [Consonant] -- ^ two or more, in order
|
||||||
} deriving (Eq, Ord, Show, Generic)
|
} deriving (Eq, Ord, Show, Generic)
|
||||||
|
|
||||||
instance ToJSON Shoresh
|
instance ToJSON Shoresh
|
||||||
instance FromJSON Shoresh
|
instance FromJSON Shoresh
|
||||||
|
|
||||||
-- | Construct a Shoresh from three consonants.
|
-- | Construct a root from its consonants. A root has at least two.
|
||||||
mkShoresh :: Consonant -> Consonant -> Consonant -> Shoresh
|
mkShoresh :: [Consonant] -> Maybe Shoresh
|
||||||
mkShoresh = Shoresh
|
mkShoresh cs
|
||||||
|
| length cs >= 2 = Just (Shoresh cs)
|
||||||
|
| otherwise = Nothing
|
||||||
|
|
||||||
|
-- | A root of three consonants — the common case, and total.
|
||||||
|
triliteral :: Consonant -> Consonant -> Consonant -> Shoresh
|
||||||
|
triliteral a b d = Shoresh [a, b, d]
|
||||||
|
|
||||||
-- | Render a Shoresh as Hebrew text.
|
-- | Render a Shoresh as Hebrew text.
|
||||||
shoreshToText :: Shoresh -> Text
|
shoreshToText :: Shoresh -> Text
|
||||||
shoreshToText (Shoresh a b d) =
|
shoreshToText = T.pack . map consonantToChar . radicals
|
||||||
T.pack [consonantToChar a, consonantToChar b, consonantToChar d]
|
|
||||||
|
|
||||||
-- | Parse a Hebrew text string into a Shoresh.
|
-- | Parse Hebrew consonants into a Shoresh. Every character must be a
|
||||||
-- Accepts 2 or 3 consonant strings.
|
-- consonant and there must be at least two. Nothing is added.
|
||||||
-- For 2-consonant roots, pads with He (ה) as placeholder.
|
|
||||||
textToShoresh :: Text -> Maybe Shoresh
|
textToShoresh :: Text -> Maybe Shoresh
|
||||||
textToShoresh t =
|
textToShoresh t = mapM charToConsonant (T.unpack t) >>= mkShoresh
|
||||||
let chars = T.unpack t
|
|
||||||
consonants = mapM charToConsonant chars
|
|
||||||
in case consonants of
|
|
||||||
Just [a, b, d] -> Just (Shoresh a b d)
|
|
||||||
Just [a, b] -> Just (Shoresh a b He) -- bicons: pad with He
|
|
||||||
_ -> Nothing
|
|
||||||
|
|
||||||
|
|
||||||
-- | The seven binyanim — transformation operators on roots.
|
-- | The seven binyanim — transformation operators on roots.
|
||||||
|
|||||||
+65
-35
@@ -64,32 +64,39 @@ consonantSpec = describe "Consonant" $ do
|
|||||||
shoreshSpec :: Spec
|
shoreshSpec :: Spec
|
||||||
shoreshSpec = describe "Shoresh" $ do
|
shoreshSpec = describe "Shoresh" $ do
|
||||||
it "constructs a triliteral root" $ do
|
it "constructs a triliteral root" $ do
|
||||||
let sh = mkShoresh Bet Resh Aleph
|
let sh = triliteral Bet Resh Aleph
|
||||||
c1 sh `shouldBe` Bet
|
radicals sh `shouldBe` [Bet, Resh, Aleph]
|
||||||
c2 sh `shouldBe` Resh
|
|
||||||
c3 sh `shouldBe` Aleph
|
|
||||||
|
|
||||||
it "renders as Hebrew text" $ do
|
it "renders as Hebrew text" $ do
|
||||||
shoreshToText (mkShoresh Bet Resh Aleph) `shouldBe` "ברא"
|
shoreshToText (triliteral Bet Resh Aleph) `shouldBe` "ברא"
|
||||||
shoreshToText (mkShoresh Qof Dalet Shin) `shouldBe` "קדש"
|
shoreshToText (triliteral Qof Dalet Shin) `shouldBe` "קדש"
|
||||||
|
|
||||||
it "parses from Hebrew text (3 consonants)" $ do
|
it "parses from Hebrew text (3 consonants)" $ do
|
||||||
textToShoresh "ברא" `shouldBe` Just (mkShoresh Bet Resh Aleph)
|
textToShoresh "ברא" `shouldBe` Just (triliteral Bet Resh Aleph)
|
||||||
textToShoresh "קדש" `shouldBe` Just (mkShoresh Qof Dalet Shin)
|
textToShoresh "קדש" `shouldBe` Just (triliteral Qof Dalet Shin)
|
||||||
|
|
||||||
it "parses from Hebrew text (2 consonants — biconsonantal)" $ do
|
it "a two-letter root stays two letters — nothing is added" $ do
|
||||||
-- Build log: some roots in L1 are 2-consonant (e.g. "שמ")
|
-- L1 has roots of two consonants (בן, יד). This type once held
|
||||||
let result = textToShoresh "שמ"
|
-- exactly three and padded these with He: בן became בנה.
|
||||||
result `shouldSatisfy` isJust
|
fmap radicals (textToShoresh "בן") `shouldBe` Just [Bet, Nun]
|
||||||
-- Bicons pad with He
|
fmap shoreshToText (textToShoresh "בן") `shouldBe` Just "בנ"
|
||||||
case result of
|
fmap gematriaRoot (textToShoresh "בן") `shouldBe` Just 52
|
||||||
Just sh -> c3 sh `shouldBe` He
|
|
||||||
Nothing -> expectationFailure "Should have parsed"
|
it "a root of four or more is held whole" $ do
|
||||||
|
-- שמאל — once rejected as "too long".
|
||||||
|
fmap radicals (textToShoresh "שמאל") `shouldBe` Just [Shin, Mem, Aleph, Lamed]
|
||||||
|
fmap gematriaRoot (textToShoresh "שמאל") `shouldBe` Just 371
|
||||||
|
|
||||||
|
it "mkShoresh needs at least two consonants" $ do
|
||||||
|
mkShoresh [] `shouldBe` Nothing
|
||||||
|
mkShoresh [Aleph] `shouldBe` Nothing
|
||||||
|
mkShoresh [Bet, Nun] `shouldSatisfy` isJust
|
||||||
|
|
||||||
it "rejects invalid input" $ do
|
it "rejects invalid input" $ do
|
||||||
textToShoresh "" `shouldBe` Nothing
|
textToShoresh "" `shouldBe` Nothing
|
||||||
textToShoresh "א" `shouldBe` Nothing -- too short
|
textToShoresh "א" `shouldBe` Nothing -- one letter is not a root
|
||||||
textToShoresh "אבגד" `shouldBe` Nothing -- too long
|
textToShoresh "abc" `shouldBe` Nothing
|
||||||
|
textToShoresh "בר1" `shouldBe` Nothing
|
||||||
|
|
||||||
|
|
||||||
binyanSpec :: Spec
|
binyanSpec :: Spec
|
||||||
@@ -98,14 +105,14 @@ binyanSpec = describe "Binyanim" $ do
|
|||||||
length allBinyanim `shouldBe` 7
|
length allBinyanim `shouldBe` 7
|
||||||
|
|
||||||
it "activates a root through all 7 binyanim" $ do
|
it "activates a root through all 7 binyanim" $ do
|
||||||
let sh = mkShoresh Bet Resh Aleph
|
let sh = triliteral Bet Resh Aleph
|
||||||
let expanded = expand sh
|
let expanded = expand sh
|
||||||
Map.size expanded `shouldBe` 7
|
Map.size expanded `shouldBe` 7
|
||||||
-- Each binyan should produce a peulah with the same root
|
-- Each binyan should produce a peulah with the same root
|
||||||
mapM_ (\(b, p) -> root p `shouldBe` sh) (Map.toList expanded)
|
mapM_ (\(b, p) -> root p `shouldBe` sh) (Map.toList expanded)
|
||||||
|
|
||||||
it "activate produces correct grammatical features" $ do
|
it "activate produces correct grammatical features" $ do
|
||||||
let sh = mkShoresh Shin Mem Resh -- שמר = guard
|
let sh = triliteral Shin Mem Resh -- שמר = guard
|
||||||
let p = activate Qal Third Singular Masculine Perfect sh
|
let p = activate Qal Third Singular Masculine Perfect sh
|
||||||
root p `shouldBe` sh
|
root p `shouldBe` sh
|
||||||
binyan p `shouldBe` Qal
|
binyan p `shouldBe` Qal
|
||||||
@@ -115,7 +122,7 @@ binyanSpec = describe "Binyanim" $ do
|
|||||||
aspect p `shouldBe` Perfect
|
aspect p `shouldBe` Perfect
|
||||||
|
|
||||||
it "activateDefault uses 3ms perfect citation form" $ do
|
it "activateDefault uses 3ms perfect citation form" $ do
|
||||||
let sh = mkShoresh Kaf Tav Bet -- כתב = write
|
let sh = triliteral Kaf Tav Bet -- כתב = write
|
||||||
let p = activateDefault Qal sh
|
let p = activateDefault Qal sh
|
||||||
person p `shouldBe` Third
|
person p `shouldBe` Third
|
||||||
number p `shouldBe` Singular
|
number p `shouldBe` Singular
|
||||||
@@ -133,11 +140,11 @@ gematriaSpec = describe "Gematria" $ do
|
|||||||
|
|
||||||
it "computes root gematria correctly" $ do
|
it "computes root gematria correctly" $ do
|
||||||
-- ברא = Bet(2) + Resh(200) + Aleph(1) = 203
|
-- ברא = Bet(2) + Resh(200) + Aleph(1) = 203
|
||||||
gematriaRoot (mkShoresh Bet Resh Aleph) `shouldBe` 203
|
gematriaRoot (triliteral Bet Resh Aleph) `shouldBe` 203
|
||||||
-- קדש = Qof(100) + Dalet(4) + Shin(300) = 404
|
-- קדש = Qof(100) + Dalet(4) + Shin(300) = 404
|
||||||
gematriaRoot (mkShoresh Qof Dalet Shin) `shouldBe` 404
|
gematriaRoot (triliteral Qof Dalet Shin) `shouldBe` 404
|
||||||
-- שלם = Shin(300) + Lamed(30) + Mem(40) = 370
|
-- שלם = Shin(300) + Lamed(30) + Mem(40) = 370
|
||||||
gematriaRoot (mkShoresh Shin Lamed Mem) `shouldBe` 370
|
gematriaRoot (triliteral Shin Lamed Mem) `shouldBe` 370
|
||||||
|
|
||||||
it "computes word gematria correctly" $ do
|
it "computes word gematria correctly" $ do
|
||||||
-- תורה = Tav(400) + Vav(6) + Resh(200) + He(5) = 611
|
-- תורה = Tav(400) + Vav(6) + Resh(200) + He(5) = 611
|
||||||
@@ -151,8 +158,8 @@ gematriaSpec = describe "Gematria" $ do
|
|||||||
gematriaWord "בְּרֵאשִׁ֖ית" `shouldBe` gematriaWord "בראשית"
|
gematriaWord "בְּרֵאשִׁ֖ית" `shouldBe` gematriaWord "בראשית"
|
||||||
|
|
||||||
it "finds gematria neighbors" $ do
|
it "finds gematria neighbors" $ do
|
||||||
let bara = mkShoresh Bet Resh Aleph -- 203
|
let bara = triliteral Bet Resh Aleph -- 203
|
||||||
let other = mkShoresh Resh Gimel Gimel -- 200+3+3 = 206, not 203
|
let other = triliteral Resh Gimel Gimel -- 200+3+3 = 206, not 203
|
||||||
let lex = Map.fromList
|
let lex = Map.fromList
|
||||||
[ (bara, ShoreshEntry bara Creation [] [] Map.empty)
|
[ (bara, ShoreshEntry bara Creation [] [] Map.empty)
|
||||||
, (other, ShoreshEntry other Creation [] [] Map.empty)
|
, (other, ShoreshEntry other Creation [] [] Map.empty)
|
||||||
@@ -240,10 +247,10 @@ ketivQereSpec = describe "KetivQere" $ do
|
|||||||
graphSpec :: Spec
|
graphSpec :: Spec
|
||||||
graphSpec = describe "Graph" $ do
|
graphSpec = describe "Graph" $ do
|
||||||
it "finds roots sharing 2 consonants" $ do
|
it "finds roots sharing 2 consonants" $ do
|
||||||
let bara = mkShoresh Bet Resh Aleph -- ברא
|
let bara = triliteral Bet Resh Aleph -- ברא
|
||||||
let barak = mkShoresh Bet Resh Kaf -- ברך (shares ב,ר)
|
let barak = triliteral Bet Resh Kaf -- ברך (shares ב,ר)
|
||||||
let garaEnt = mkShoresh Gimel Resh Aleph -- גרא (shares ר,א)
|
let garaEnt = triliteral Gimel Resh Aleph -- גרא (shares ר,א)
|
||||||
let unrelated = mkShoresh Shin Lamed Mem -- שלם (shares nothing)
|
let unrelated = triliteral Shin Lamed Mem -- שלם (shares nothing)
|
||||||
let lex = Map.fromList
|
let lex = Map.fromList
|
||||||
[ (bara, ShoreshEntry bara Creation [] [] Map.empty)
|
[ (bara, ShoreshEntry bara Creation [] [] Map.empty)
|
||||||
, (barak, ShoreshEntry barak Blessing [] [] Map.empty)
|
, (barak, ShoreshEntry barak Blessing [] [] Map.empty)
|
||||||
@@ -253,25 +260,48 @@ graphSpec = describe "Graph" $ do
|
|||||||
let ns = neighbors bara lex
|
let ns = neighbors bara lex
|
||||||
length ns `shouldBe` 2 -- barak and gara, not unrelated
|
length ns `shouldBe` 2 -- barak and gara, not unrelated
|
||||||
|
|
||||||
|
it "compares only roots of the same length" $ do
|
||||||
|
let Just ben = textToShoresh "בן"
|
||||||
|
let Just bat = textToShoresh "בת"
|
||||||
|
let bana = triliteral Bet Nun He
|
||||||
|
sharedConsonants ben bat `shouldBe` 1
|
||||||
|
sharedConsonants ben bana `shouldBe` 0
|
||||||
|
let lex = Map.fromList
|
||||||
|
[ (ben, ShoreshEntry ben Creation [] [] Map.empty)
|
||||||
|
, (bat, ShoreshEntry bat Creation [] [] Map.empty)
|
||||||
|
, (bana, ShoreshEntry bana Creation [] [] Map.empty)
|
||||||
|
]
|
||||||
|
map seShoresh (neighbors ben lex) `shouldBe` [bat]
|
||||||
|
map fst (neighborsByPosition ben lex) `shouldBe` ["c1"]
|
||||||
|
|
||||||
|
it "labels a triliteral neighbor by the positions it shares" $ do
|
||||||
|
let bara = triliteral Bet Resh Aleph
|
||||||
|
let barak = triliteral Bet Resh Kaf
|
||||||
|
let lex = Map.fromList
|
||||||
|
[ (bara, ShoreshEntry bara Creation [] [] Map.empty)
|
||||||
|
, (barak, ShoreshEntry barak Blessing [] [] Map.empty)
|
||||||
|
]
|
||||||
|
map fst (neighborsByPosition bara lex) `shouldBe` ["c1c2"]
|
||||||
|
|
||||||
it "counts shared consonants correctly" $ do
|
it "counts shared consonants correctly" $ do
|
||||||
let sh1 = mkShoresh Bet Resh Aleph
|
let sh1 = triliteral Bet Resh Aleph
|
||||||
let sh2 = mkShoresh Bet Resh Kaf
|
let sh2 = triliteral Bet Resh Kaf
|
||||||
sharedConsonants sh1 sh2 `shouldBe` 2
|
sharedConsonants sh1 sh2 `shouldBe` 2
|
||||||
let sh3 = mkShoresh Shin Lamed Mem
|
let sh3 = triliteral Shin Lamed Mem
|
||||||
sharedConsonants sh1 sh3 `shouldBe` 0
|
sharedConsonants sh1 sh3 `shouldBe` 0
|
||||||
|
|
||||||
|
|
||||||
kiTovSpec :: Spec
|
kiTovSpec :: Spec
|
||||||
kiTovSpec = describe "KiTov" $ do
|
kiTovSpec = describe "KiTov" $ do
|
||||||
it "returns Tov when all roots exist in lexicon" $ do
|
it "returns Tov when all roots exist in lexicon" $ do
|
||||||
let sh = mkShoresh Bet Resh Aleph
|
let sh = triliteral Bet Resh Aleph
|
||||||
let entry = ShoreshEntry sh Creation [] [Reference Bereishit 1 1] Map.empty
|
let entry = ShoreshEntry sh Creation [] [Reference Bereishit 1 1] Map.empty
|
||||||
let lex = Map.fromList [(sh, entry)]
|
let lex = Map.fromList [(sh, entry)]
|
||||||
let result = ProcessedResult [] [entry]
|
let result = ProcessedResult [] [entry]
|
||||||
kiTov result lex `shouldBe` Tov
|
kiTov result lex `shouldBe` Tov
|
||||||
|
|
||||||
it "returns LoTov when roots are missing" $ do
|
it "returns LoTov when roots are missing" $ do
|
||||||
let sh = mkShoresh Bet Resh Aleph
|
let sh = triliteral Bet Resh Aleph
|
||||||
let entry = ShoreshEntry sh Creation [] [Reference Bereishit 1 1] Map.empty
|
let entry = ShoreshEntry sh Creation [] [Reference Bereishit 1 1] Map.empty
|
||||||
let emptyLex = Map.empty
|
let emptyLex = Map.empty
|
||||||
let result = ProcessedResult [] [entry]
|
let result = ProcessedResult [] [entry]
|
||||||
|
|||||||
@@ -119,13 +119,17 @@ data Consonant = Aleph | Bet | Gimel | Dalet | He | Vav | Zayin
|
|||||||
| Samekh | Ayin | Pe | Tsade | Qof | Resh | Shin | Tav
|
| Samekh | Ayin | Pe | Tsade | Qof | Resh | Shin | Tav
|
||||||
deriving (Eq, Ord, Show, Enum, Bounded)
|
deriving (Eq, Ord, Show, Enum, Bounded)
|
||||||
|
|
||||||
-- A triliteral root — the atomic semantic unit
|
-- A root — the atomic semantic unit: the consonants under which the
|
||||||
data Shoresh = Shoresh
|
-- lexicon files a word, exactly as L1 records them. Most roots have
|
||||||
{ c1 :: Consonant
|
-- three consonants; Torah also has roots of two (בן, יד) and of four or
|
||||||
, c2 :: Consonant
|
-- more (שמאל). A root is never padded or truncated to make it three.
|
||||||
, c3 :: Consonant
|
newtype Shoresh = Shoresh
|
||||||
|
{ radicals :: [Consonant] -- two or more, in order
|
||||||
} deriving (Eq, Ord, Show)
|
} deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
mkShoresh :: [Consonant] -> Maybe Shoresh -- needs at least two
|
||||||
|
triliteral :: Consonant -> Consonant -> Consonant -> Shoresh -- the common case
|
||||||
|
|
||||||
-- The seven binyanim — transformation operators
|
-- The seven binyanim — transformation operators
|
||||||
data Binyan = Qal -- Simple active
|
data Binyan = Qal -- Simple active
|
||||||
| Nifal -- Passive / reflexive
|
| Nifal -- Passive / reflexive
|
||||||
@@ -157,7 +161,7 @@ data SemanticDomain = Creation | Separation | Speech | Movement
|
|||||||
data ShoreshEntry = ShoreshEntry
|
data ShoreshEntry = ShoreshEntry
|
||||||
{ shoresh :: Shoresh
|
{ shoresh :: Shoresh
|
||||||
, primaryDomain :: SemanticDomain
|
, primaryDomain :: SemanticDomain
|
||||||
, relatedRoots :: [Shoresh] -- roots sharing 2 consonants
|
, relatedRoots :: [Shoresh] -- same length, differing in one consonant
|
||||||
, torahOccurrences :: [Reference] -- where this root appears in Torah
|
, torahOccurrences :: [Reference] -- where this root appears in Torah
|
||||||
, glosses :: Map Binyan Text -- basic meanings per binyan
|
, glosses :: Map Binyan Text -- basic meanings per binyan
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
@@ -176,7 +180,8 @@ data Reference = Reference
|
|||||||
-- Apply a binyan to a root (the fundamental operation)
|
-- Apply a binyan to a root (the fundamental operation)
|
||||||
activate :: Binyan -> Shoresh -> Peulah
|
activate :: Binyan -> Shoresh -> Peulah
|
||||||
|
|
||||||
-- Find all roots sharing 2 consonants (semantic neighborhood)
|
-- Find all roots of the same length differing in exactly one consonant
|
||||||
|
-- (for a root of three: sharing 2 consonants) — the semantic neighborhood
|
||||||
neighbors :: Shoresh -> ShoreshLexicon -> [ShoreshEntry]
|
neighbors :: Shoresh -> ShoreshLexicon -> [ShoreshEntry]
|
||||||
|
|
||||||
-- Trace a root through all 7 binyanim (full semantic expansion)
|
-- Trace a root through all 7 binyanim (full semantic expansion)
|
||||||
@@ -316,6 +321,7 @@ shoresh-engine/
|
|||||||
│ ├── Shoresh/
|
│ ├── Shoresh/
|
||||||
│ │ ├── Types.hs -- All data types above
|
│ │ ├── Types.hs -- All data types above
|
||||||
│ │ ├── Lexicon.hs -- The root lexicon, read from L1 (1,378 roots)
|
│ │ ├── Lexicon.hs -- The root lexicon, read from L1 (1,378 roots)
|
||||||
|
│ │ ├── Roots.hs -- A word's root, heard from L1 (verse, lemma, Torah-wide)
|
||||||
│ │ ├── Binyanim.hs -- Transformation operators
|
│ │ ├── Binyanim.hs -- Transformation operators
|
||||||
│ │ ├── Havdalah.hs -- Separation/distinction engine
|
│ │ ├── Havdalah.hs -- Separation/distinction engine
|
||||||
│ │ ├── Kriah.hs -- Naming/identification
|
│ │ ├── Kriah.hs -- Naming/identification
|
||||||
@@ -324,23 +330,26 @@ shoresh-engine/
|
|||||||
│ │ ├── Teamim.hs -- Te'amim reader (reads raw marks, constructs parse tree)
|
│ │ ├── Teamim.hs -- Te'amim reader (reads raw marks, constructs parse tree)
|
||||||
│ │ ├── Gematria.hs -- Numerical value lookup and equivalences
|
│ │ ├── Gematria.hs -- Numerical value lookup and equivalences
|
||||||
│ │ ├── Smikhut.hs -- Construct-state composition (reads attested compounds)
|
│ │ ├── Smikhut.hs -- Construct-state composition (reads attested compounds)
|
||||||
│ │ └── KetivQere.hs -- Dual reading type support
|
│ │ ├── KetivQere.hs -- Dual reading type support
|
||||||
|
│ │ └── Server.hs -- JSON-line port for the Vav Pipeline (Construction §3.1)
|
||||||
│ ├── Luchot/
|
│ ├── Luchot/
|
||||||
│ │ └── Firmware.hs -- L0 immutable constants (compiled in)
|
│ │ └── Firmware.hs -- L0 immutable constants (compiled in)
|
||||||
│ └── Main.hs
|
│ └── Main.hs
|
||||||
├── data/
|
|
||||||
│ ├── roots.json -- Root lexicon data
|
|
||||||
│ └── torah_refs.json -- Torah reference index
|
|
||||||
├── test/
|
├── test/
|
||||||
│ └── Spec.hs
|
│ └── Spec.hs
|
||||||
|
├── cabal.project.freeze
|
||||||
└── shoresh-engine.cabal
|
└── shoresh-engine.cabal
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The engine carries no data files of its own. The roots and the Torah
|
||||||
|
references are in L1 (`l1/torah/`, found through `L1_MOUNT_POINT`) and
|
||||||
|
are read from there when the engine starts.
|
||||||
|
|
||||||
**Dependencies (Haskell packages):**
|
**Dependencies (Haskell packages):**
|
||||||
- `base`
|
- `base`
|
||||||
- `containers` (Map, Set)
|
- `containers` (Map, Set)
|
||||||
- `text`
|
- `text`
|
||||||
- `aeson` (JSON parsing for lexicon data)
|
- `aeson` (JSON: L1 verses and the port protocol)
|
||||||
- `vector`
|
- `vector`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ defmodule VavPipeline.Doorpost do
|
|||||||
# changes; the update is a deliberate operator commit. A bare
|
# changes; the update is a deliberate operator commit. A bare
|
||||||
# hash-update commit (without a corresponding spec change in the
|
# hash-update commit (without a corresponding spec change in the
|
||||||
# same commit) is itself a drift signal.
|
# same commit) is itself a drift signal.
|
||||||
@expected_hash "094b8164dc1e5b280dc797d0e4c351ed67898a2046133e5f00a1972ffbde5661"
|
@expected_hash "f653ef0520c86bb378727fa877421796e18a424acc3fb396c62082b47c53cd50"
|
||||||
|
|
||||||
@doc "Return the list of files that constitute the category-grade specification."
|
@doc "Return the list of files that constitute the category-grade specification."
|
||||||
@spec files() :: [String.t()]
|
@spec files() :: [String.t()]
|
||||||
|
|||||||
Reference in new issue
Block a user