packages feed

phino 0.0.86 → 0.0.87

raw patch · 20 files changed

+345/−91 lines, 20 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Yaml: OpLambda :: Expression -> Operation
+ CLI.Parsers: optContextualize :: Parser Bool
+ CLI.Types: [_contextualize] :: OptsExplain -> Bool
+ LaTeX: explainContextualizeRules :: [ContextualizeRule] -> String
+ Yaml: ContextualizeRule :: String -> Maybe String -> Expression -> Expression -> Expression -> [Premise] -> ContextualizeRule
+ Yaml: OpEvaluate :: Expression -> Operation
+ Yaml: [cmatch] :: ContextualizeRule -> Expression
+ Yaml: [cresult] :: ContextualizeRule -> Expression
+ Yaml: contextualizationRules :: [ContextualizeRule]
+ Yaml: data ContextualizeRule
+ Yaml: instance Data.Aeson.Types.FromJSON.FromJSON Yaml.ContextualizeRule
+ Yaml: instance GHC.Generics.Generic Yaml.ContextualizeRule
+ Yaml: instance GHC.Show.Show Yaml.ContextualizeRule
+ Yaml: parseLabel :: String -> Object -> Parser (Maybe String)
- CLI.Types: OptsExplain :: LogLevel -> Int -> [FilePath] -> Bool -> Bool -> Bool -> Bool -> Maybe FilePath -> OptsExplain
+ CLI.Types: OptsExplain :: LogLevel -> Int -> [FilePath] -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe FilePath -> OptsExplain
- Yaml: [label] :: DataizeRule -> Maybe String
+ Yaml: [label] :: ContextualizeRule -> Maybe String
- Yaml: [match] :: DataizeRule -> Expression
+ Yaml: [match] :: ContextualizeRule -> Expression
- Yaml: [name] :: DataizeRule -> String
+ Yaml: [name] :: ContextualizeRule -> String
- Yaml: [premises] :: DataizeRule -> [Premise]
+ Yaml: [premises] :: ContextualizeRule -> [Premise]

Files

README.md view
@@ -206,9 +206,9 @@ ## Explain  You can _explain_ the built-in rules by printing them in [LaTeX][latex]-format. Pass exactly one of `--normalize`, `--morph` or `--dataize` for-the rewriting, morphing (𝕄) or dataization (𝔻) rules (or `--rule` for a-custom rule file):+format. Pass exactly one of `--normalize`, `--morph`, `--dataize` or+`--contextualize` for the rewriting, morphing (𝕄), dataization (𝔻) or+contextualization (𝒞) rules (or `--rule` for a custom rule file):  ```bash $ phino explain --normalize@@ -268,6 +268,22 @@ \end{tabular} ``` +```bash+$ phino explain --contextualize+\begin{phinoInference}+  \phinoName{cxi}+  \phinoLabel{xi}+  \phinoConclusion{ \phinoContextualize{ \phiTerminal{\xi} }{ k }{ k } }+\end{phinoInference}+...+\begin{phinoInference}+  \phinoName{cdispatch}+  \phinoLabel{disp}+  \phinoPremise{ \phinoContextualize{ n }{ k }{ n_1 } }+  \phinoConclusion{ \phinoContextualize{ n . \tau }{ k }{ n_1 . \tau } }+\end{phinoInference}+```+ For more details, use `phino [COMMAND] --help` option.  ## Rule structure@@ -393,7 +409,7 @@                 or `nf:` guard is needed * `!B` || `𝐵` - list of bindings * `!d` || `δ` - bytes in meta delta binding-* `!F` || `𝐹` - function name in meta lambda binding+* `!F` || `𝑓` - function name in meta lambda binding  Every meta variable may also be used with an integer index, like `!B1` or `𝜏0`. 
phino.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: phino-version: 0.0.86+version: 0.0.87 license: MIT synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
+ resources/contextualization.yaml view
@@ -0,0 +1,82 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# Contextualization 𝒞 — applied top-to-bottom, first matching clause wins. It is+# binary, 𝒞(n, c): n is the term being contextualized and c is the context (the+# expression that every free ξ inside n stands for). 𝒞 walks the term+# structurally, replacing each ξ with the context c, descending through+# dispatches and applications and stopping at formations — which introduce their+# own scope and are returned untouched — and at the global Φ and termination ⊥.+#+# Each rule is an inference rule: when 'match' matches the term and 'c-match'+# matches the context (binding the meta c), the rule yields the conclusion+# 'c-result' (a premise meta or a literal), provided the ordered 'premises'+# reduce as stated. A premise binds its 'n-result' to one 𝒞 ('contextualize')+# judgment. 'c-match' is the context-argument matcher of 𝒞(n, c); it is always+# the c meta. Derived terms are named 𝑛, 𝑛1, … in premise order; the bare 𝑛 is+# skipped only when 'match' already binds it.++- name: cglobe+  label: globe+  match: Φ+  c-match: 𝑘+  c-result: Φ++- name: cxi+  label: xi+  match: ξ+  c-match: 𝑘+  c-result: 𝑘++- name: cdead+  label: dead+  match: ⊥+  c-match: 𝑘+  c-result: ⊥++- name: cprim+  label: prim+  match: ⟦𝐵⟧+  c-match: 𝑘+  c-result: ⟦𝐵⟧++- name: cdispatch+  label: disp+  match: '𝑛.𝜏'+  c-match: 𝑘+  c-result: '𝑛1.𝜏'+  premises:+    - n-result: 𝑛1+      contextualize:+        - 𝑛+        - 𝑘++- name: capplication+  label: app+  match: '𝑛(𝜏 ↦ 𝑒1)'+  c-match: 𝑘+  c-result: '𝑛1(𝜏 ↦ 𝑛2)'+  premises:+    - n-result: 𝑛1+      contextualize:+        - 𝑛+        - 𝑘+    - n-result: 𝑛2+      contextualize:+        - 𝑒1+        - 𝑘++- name: capplicationa+  label: appa+  match: '𝑛(α𝑖 ↦ 𝑒1)'+  c-match: 𝑘+  c-result: '𝑛1(α𝑖 ↦ 𝑛2)'+  premises:+    - n-result: 𝑛1+      contextualize:+        - 𝑛+        - 𝑘+    - n-result: 𝑛2+      contextualize:+        - 𝑒1+        - 𝑘
resources/dataization.yaml view
@@ -10,11 +10,19 @@ # matches the universe (binding the meta e), the rule yields the conclusion # 'd-result' (a premise bytes meta or a literal), provided 'when' holds and the # ordered 'premises' reduce as stated. A premise binds its 'n-result'/'d-result'-# to one judgment — 𝔻 ('dataize'), 𝕄 ('morph'), 𝒩 ('normalize'), 𝔼 ('lambda')+# to one judgment — 𝔻 ('dataize'), 𝕄 ('morph'), 𝒩 ('normalize'), 𝔼 ('evaluate') # or 𝒞 ('contextualize'). 'e-match' is the universe-argument matcher of # 𝔻(n, e); it is always the e meta. The single bytes result is named δ; derived # terms are 𝑛, 𝑛1, … in premise order, skipping the bare 𝑛 only when 'match' # already binds it.+#+# The clauses are disjoint, so their relative order does not change behavior.+# 'norm' matches the bare meta 𝑛, which unifies with any expression, so it is+# guarded to fire only when 𝑛 is neither a formation ('not (formation 𝑛)',+# carving out 'delta', 'box', 'fire' and 'none') nor the termination ⊥+# ('not (𝑛 = ⊥)', carving out 'bott'). Without the guard 'norm' would behave+# correctly only by being declared last; with it the clauses no longer rely on+# their order.  - name: delta   label: \Delta@@ -23,7 +31,6 @@   d-result: δ  - name: box-  label: box   match: ⟦𝐵1, φ ↦ 𝑒1, 𝐵2⟧   e-match: 𝑒   d-result: δ@@ -42,35 +49,43 @@       dataize: 𝑛1  - name: fire-  label: fire-  match: ⟦𝐵1, λ ⤍ 𝐹, 𝐵2⟧+  match: ⟦𝐵1, λ ⤍ 𝑓, 𝐵2⟧   e-match: 𝑒   d-result: δ   premises:     - n-result: 𝑛-      lambda: ⟦𝐵1, λ ⤍ 𝐹, 𝐵2⟧+      evaluate: ⟦𝐵1, λ ⤍ 𝑓, 𝐵2⟧     - n-result: 𝑛1       normalize: 𝑛     - d-result: δ       dataize: 𝑛1  - name: none-  label: none   match: ⟦𝐵⟧   e-match: 𝑒   d-result: '--'+  when:+    disjoint:+      - [Δ, λ, φ]+      - [𝐵]  - name: bott-  label: bott   match: ⊥   e-match: 𝑒   d-result: '--'  - name: norm-  label: norm   match: 𝑛   e-match: 𝑒   d-result: δ+  when:+    and:+      - not:+          formation: 𝑛+      - not:+          eq:+            - 𝑛+            - ⊥   premises:     - n-result: 𝑛1       morph: 𝑛
resources/morphing.yaml view
@@ -15,10 +15,11 @@ # matches the universe (binding the meta e), the rule yields the conclusion # 'n-result' (a premise meta or a literal), provided 'when' holds and the # ordered 'premises' reduce as stated. A premise binds 'n-result' to the result-# of one judgment — 𝕄 ('morph'), 𝒩 ('normalize') or 𝔼 ('lambda'). 'e-match' is-# the universe-argument matcher of 𝕄(n, e); it is always the e meta. Derived-# terms are named 𝑛, 𝑛1, … in premise order; the bare 𝑛 is skipped only when-# 'match' already binds it.+# of one judgment — 𝕄 ('morph'), 𝒩 ('normalize') or 𝔼 ('evaluate'). 'e-match' is+# the universe-argument matcher of 𝕄(n, e); usually the e meta, but a rule may+# pin it to a literal (e.g. 'globe' fires only on 𝕄(Φ, Φ)). Derived terms are+# named 𝑛, 𝑛1, … in premise order; the bare 𝑛 is skipped only when 'match'+# already binds it. # # 'lambda' and 'dispatch' are kept mutually exclusive: 'dispatch' fires only # when its head 𝑛 is not a formation ('not (formation 𝑛)'), so every formation@@ -26,19 +27,18 @@ # are disjoint and their relative order does not change behavior.  - name: prim-  label: prim   match: ⟦𝐵⟧   e-match: 𝑒   n-result: ⟦𝐵⟧  - name: lambda   label: \lambda-  match: '⟦𝐵1, λ ⤍ 𝐹, 𝐵2⟧.𝜏'+  match: '⟦𝐵1, λ ⤍ 𝑓, 𝐵2⟧.𝜏'   e-match: 𝑒   n-result: 𝑛2   premises:     - n-result: 𝑛-      lambda: '⟦𝐵1, λ ⤍ 𝐹, 𝐵2⟧'+      evaluate: '⟦𝐵1, λ ⤍ 𝑓, 𝐵2⟧'     - n-result: 𝑛1       normalize: '𝑛.𝜏'     - n-result: 𝑛2@@ -87,7 +87,6 @@       morph: 𝑛2  - name: root-  label: root   match: Φ   e-match: 𝑒   n-result: 𝑛1@@ -103,19 +102,16 @@       morph: 𝑛  - name: dead-  label: dead   match: ⊥   e-match: 𝑒   n-result: ⊥  - name: xi-  label: xi   match: ξ   e-match: 𝑒   n-result: ⊥  - name: globe-  label: globe   match: Φ-  e-match: 𝑒+  e-match: Φ   n-result: ⊥
src/CLI/Parsers.hs view
@@ -180,6 +180,9 @@ optDataize :: Parser Bool optDataize = switch (long "dataize" <> help "Explain built-in dataization rules") +optContextualize :: Parser Bool+optContextualize = switch (long "contextualize" <> help "Explain built-in contextualization rules")+ optTarget :: Parser (Maybe FilePath) optTarget = optional (strOption (long "target" <> short 't' <> metavar "FILE" <> help "File to save output to")) @@ -234,6 +237,7 @@             <*> optNormalize             <*> optMorph             <*> optDataize+            <*> optContextualize             <*> optShuffle             <*> optTarget         )
src/CLI/Runners.hs view
@@ -22,7 +22,7 @@ import Encoding import qualified Filter as F import Functions (buildTerm)-import LaTeX (explainDataizeRules, explainMorphRules, explainRules)+import LaTeX (explainContextualizeRules, explainDataizeRules, explainMorphRules, explainRules) import Logger import Margin (defaultMargin) import Merge (merge)@@ -189,12 +189,13 @@     explained       | _morph = pure (explainMorphRules Y.morphingRules)       | _dataize = pure (explainDataizeRules Y.dataizationRules)+      | _contextualize = pure (explainContextualizeRules Y.contextualizationRules)       | otherwise = explainRules <$> getRules _normalize _shuffle _rules     validateOpts :: IO ()     validateOpts = do-      let selected = length (filter id [not (null _rules), _normalize, _morph, _dataize])-      when (selected == 0) (invalidCLIArguments "Either --rule, --normalize, --morph or --dataize must be specified")-      when (selected > 1) (invalidCLIArguments "Only one of --rule, --normalize, --morph or --dataize can be specified")+      let selected = length (filter id [not (null _rules), _normalize, _morph, _dataize, _contextualize])+      when (selected == 0) (invalidCLIArguments "Either --rule, --normalize, --morph, --dataize or --contextualize must be specified")+      when (selected > 1) (invalidCLIArguments "Only one of --rule, --normalize, --morph, --dataize or --contextualize can be specified")  runMerge :: OptsMerge -> IO () runMerge OptsMerge{..} = do
src/CLI/Types.hs view
@@ -108,6 +108,7 @@   , _normalize :: Bool   , _morph :: Bool   , _dataize :: Bool+  , _contextualize :: Bool   , _shuffle :: Bool   , _targetFile :: Maybe FilePath   }
src/CST.hs view
@@ -92,7 +92,7 @@   | B' -- B   | D -- δ   | D' -- \delta-  | F -- 𝐹+  | F -- 𝑓   | F' -- F   deriving (Eq, Show) 
src/Dataize.hs view
@@ -72,8 +72,9 @@  -- The Morphing function 𝕄 maps normal forms to formations. It is binary, -- 𝕄(n, e): besides the term 'n' it takes the universe 'e' ('univ') — a plain--- expression — and matches it against the rule's 'e-match' pattern (always the--- '𝑒' meta), which binds 'e' so the 'root' rule substitutes it. It is driven by+-- expression — and matches it against the rule's 'e-match' pattern (usually the+-- '𝑒' meta, which binds 'e' so the 'root' rule substitutes it, but a rule may+-- pin it to a literal such as 'globe' matching Φ). It is driven by -- the ordered rules from 'morphing.yaml': the first matching rule's premises are -- evaluated and its conclusion 'nresult' is built, always forwarding the same -- universe. The 'morph' premise that produces the conclusion is the spine: when@@ -197,7 +198,7 @@     sides :: [Y.Premise] -> Subst -> IO Subst     sides premises subst = foldM (sidePremise univ ctx) subst premises     -- A spliced dataization step is labelled by its first side-computation —-    -- 'box' by its 'contextualize', 'fire' by its 'lambda'; with none it is blank.+    -- 'box' by its 'contextualize', 'fire' by its 'evaluate'; with none it is blank.     labelOf :: [Y.Premise] -> String     labelOf (premise : _) = verb premise.operation     labelOf [] = ""@@ -220,9 +221,9 @@ excluding :: [Y.Premise] -> [Y.Premise] -> [Y.Premise] excluding premises removed = filter (\premise -> premise.result `notElem` map (.result) removed) premises --- Evaluate one side-computation premise — a 'morph', 'lambda' or 'contextualize'+-- Evaluate one side-computation premise — a 'morph', 'evaluate' or 'contextualize' -- of an earlier term — in isolation, binding its result meta. These never splice--- steps into the trace: 'morph' and 'lambda' reduce on a fresh chain and discard+-- steps into the trace: 'morph' and 'evaluate' reduce on a fresh chain and discard -- it, 'contextualize' is pure. sidePremise :: Expression -> DataizeContext -> Subst -> Y.Premise -> IO Subst sidePremise univ ctx subst premise = do@@ -241,7 +242,7 @@ verb :: Y.Operation -> String verb (Y.OpMorph _) = "morph" verb (Y.OpNormalize _) = "normalize"-verb (Y.OpLambda _) = "lambda"+verb (Y.OpEvaluate _) = "evaluate" verb (Y.OpContextualize _ _) = "contextualize" verb (Y.OpDataize _) = "dataize" @@ -249,7 +250,7 @@ verbArgs :: Y.Operation -> [ExtraArgument] verbArgs (Y.OpMorph expr) = [ArgExpression expr] verbArgs (Y.OpNormalize expr) = [ArgExpression expr]-verbArgs (Y.OpLambda expr) = [ArgExpression expr]+verbArgs (Y.OpEvaluate expr) = [ArgExpression expr] verbArgs (Y.OpContextualize expr context) = [ArgExpression expr, ArgExpression context] verbArgs (Y.OpDataize expr) = [ArgExpression expr] @@ -327,25 +328,25 @@ atom func _ _ _ = throwIO (userError (printf "Atom '%s' does not exist" (T.unpack func)))  -- Augment the injected, context-free term builder with the dataization and--- morphing operations that need the universe: 'lambda' applies an atom and+-- morphing operations that need the universe: 'evaluate' applies an atom and -- 'morph' morphs a sub-expression. Both receive the universe 'univ'. Every other -- function is delegated unchanged. execBuildTerm :: Expression -> DataizeContext -> BuildTermFunc-execBuildTerm univ ctx "lambda" = _lambda univ ctx+execBuildTerm univ ctx "evaluate" = _evaluate univ ctx execBuildTerm univ ctx "morph" = _morph univ ctx execBuildTerm _ ctx func = _buildTerm ctx func -_lambda :: Expression -> DataizeContext -> BuildTermMethod-_lambda univ ctx [ArgExpression expr] subst = do+_evaluate :: Expression -> DataizeContext -> BuildTermMethod+_evaluate univ ctx [ArgExpression expr] subst = do   form <- buildExpressionThrows expr subst   case form of     ExFormation bds -> do       resolved <- formation bds univ ctx       case resolved of         Just obj -> pure (TeExpression obj)-        Nothing -> throwIO (userError "Function lambda() expects a formation with a λ binding")-    _ -> throwIO (userError "Function lambda() expects a formation")-_lambda _ _ _ _ = throwIO (userError "Function lambda() requires exactly 1 expression argument")+        Nothing -> throwIO (userError "Function evaluate() expects a formation with a λ binding")+    _ -> throwIO (userError "Function evaluate() expects a formation")+_evaluate _ _ _ _ = throwIO (userError "Function evaluate() requires exactly 1 expression argument")  -- The Morphing function 𝕄 exposed as a build-term function so a rule can morph -- a sub-expression in its 'where' (the 'dispatch' and 'application' rules morph
src/Functions.hs view
@@ -29,7 +29,7 @@ -- 'Dataize.execBuildTerm', not by 'buildTerm'. They are available while -- executing dataization and morphing rules, but not rewriting rules. execFunctions :: [String]-execFunctions = ["lambda", "morph"]+execFunctions = ["evaluate", "morph"]  buildTerm :: BuildTermFunc buildTerm func args subst = do
src/LaTeX.hs view
@@ -12,6 +12,7 @@   ( explainRules   , explainMorphRules   , explainDataizeRules+  , explainContextualizeRules   , rewrittensToLatex   , programToLaTeX   , expressionToLaTeX@@ -392,6 +393,17 @@     (map premiseToLatex rule.premises)     (phinoDataize (renderExpr rule.match) (renderExpr rule.ematch) (renderBytes rule.dresult)) +-- Render a contextualization rule as a LaTeX inference rule, with 𝒞(match, c) ⟿+-- c-result as the conclusion below the line.+explainContextualizeRule :: Y.ContextualizeRule -> String+explainContextualizeRule rule =+  inference+    rule.name+    rule.label+    Nothing+    (map premiseToLatex rule.premises)+    (phinoContextualize (renderExpr rule.match) (renderExpr rule.cmatch) (renderExpr rule.cresult))+ -- One premise judgment, rendered per its operation. 𝕄 ('morph') and 𝔻 -- ('dataize') are binary and carry the universe 'e' they were given; the rest -- are unary.@@ -400,7 +412,7 @@   Y.OpMorph arg -> phinoMorph (renderExpr arg) "e" (renderExpr (ExMeta premise.result))   Y.OpDataize arg -> phinoDataize (renderExpr arg) "e" (renderBytes (BtMeta premise.result))   Y.OpNormalize arg -> phinoNormalize (renderExpr arg) (renderExpr (ExMeta premise.result))-  Y.OpLambda arg -> phinoEvaluate (renderExpr arg) (renderExpr (ExMeta premise.result))+  Y.OpEvaluate arg -> phinoEvaluate (renderExpr arg) (renderExpr (ExMeta premise.result))   Y.OpContextualize arg context -> phinoContextualize (renderExpr arg) (renderExpr context) (renderExpr (ExMeta premise.result))  -- Assemble an inference block from a name, optional label, optional side@@ -487,3 +499,6 @@  explainDataizeRules :: [Y.DataizeRule] -> String explainDataizeRules = intercalate "\n" . map explainDataizeRule++explainContextualizeRules :: [Y.ContextualizeRule] -> String+explainContextualizeRules = intercalate "\n" . map explainContextualizeRule
src/Parser.hs view
@@ -299,7 +299,7 @@         BiLambda . Function . T.pack <$> function     , do         _ <- lambda-        BiLambda . FnMeta <$> meta' 'F' "𝐹"+        BiLambda . FnMeta <$> meta' 'F' "𝑓"     ]     <?> "binding" 
src/Render.hs view
@@ -71,7 +71,7 @@  instance Render DELTA where   render DELTA = "Δ"-  render DELTA' = "\\Delta"+  render DELTA' = "D"  instance Render XI where   render XI = "ξ"@@ -128,7 +128,7 @@   render B' = "B"   render D = "δ"   render D' = "\\delta"-  render F = "𝐹"+  render F = "𝑓"   render F' = "F"  instance Render META where@@ -256,26 +256,26 @@     where       renderWrapped :: CONDITION -> Text       renderWrapped CO_LOGIC{conditions = [cond]} = render cond-      renderWrapped cond@CO_LOGIC{} = "( " <> render cond <> " )"+      renderWrapped cond@CO_LOGIC{} = "\\lparen " <> render cond <> " \\rparen"       renderWrapped cond = render cond   render CO_NF{..} = "\\isnormal{ " <> render expr <> " }"   render CO_ABSOLUTE{..} = render expr <> " " <> render belongs <> " \\mathcal{K}"   render CO_NOT{condition = CO_FORMATION{..}} = "\\phinoNotFormation{ " <> render expr <> " }"   render CO_NOT{..} = renderFunc "not" condition   render CO_COMPARE{..} = render left <> " " <> render equal <> " " <> render right-  render CO_MATCHES{..} = "matches( " <> T.pack regex <> ", " <> render expr <> " )"-  render CO_PART_OF{..} = "part-of( " <> render expr <> ", " <> render binding <> " )"+  render CO_MATCHES{..} = "matches\\lparen " <> T.pack regex <> ", " <> render expr <> " \\rparen"+  render CO_PART_OF{..} = "part-of\\lparen " <> render expr <> ", " <> render binding <> " \\rparen"   render CO_FORMATION{..} = "\\phinoIsFormation{ " <> render expr <> " }"   render CO_DISJOINT{..} =     "[ "       <> T.intercalate ", " (map render attrs)-      <> " ] \\cap ( "+      <> " ] \\cap \\lparen "       <> T.intercalate " \\cup " (map render groups)-      <> " ) = \\emptyset"+      <> " \\rparen = \\emptyset"   render CO_EMPTY = ""  renderFunc :: Render a => Text -> a -> Text-renderFunc func renderable = func <> "( " <> render renderable <> " )"+renderFunc func renderable = func <> "\\lparen " <> render renderable <> " \\rparen"  instance Render EXTRA_ARG where   render ARG_ATTR{..} = render attr@@ -292,5 +292,5 @@   render EXTRA{..} = render meta <> " \\coloneqq " <> macro func <> "{ " <> T.intercalate ", " (map render args) <> " }"     where       macro :: String -> Text-      macro "lambda" = "\\phinoEvaluate"+      macro "evaluate" = "\\phinoEvaluate"       macro name = "\\" <> T.pack name
src/Yaml.hs view
@@ -238,7 +238,7 @@ data Operation   = OpMorph Expression   | OpNormalize Expression-  | OpLambda Expression+  | OpEvaluate Expression   | OpContextualize Expression Expression   | OpDataize Expression   deriving (Eq, Generic, Show)@@ -272,6 +272,20 @@   }   deriving (Generic, Show) +-- One contextualization rule in inference-rule form, structured like 'MorphRule'+-- but binary in 𝒞(n, c): the second argument is the context 'c' ('cmatch',+-- always the 'c' meta) rather than the universe 'e', and the conclusion is the+-- contextualized term 'cresult'.+data ContextualizeRule = ContextualizeRule+  { name :: String+  , label :: Maybe String+  , match :: Expression+  , cmatch :: Expression+  , cresult :: Expression+  , premises :: [Premise]+  }+  deriving (Generic, Show)+ instance FromJSON Premise where   parseJSON =     withObject@@ -299,7 +313,7 @@   asum     [ OpMorph <$> o .: "morph"     , OpNormalize <$> o .: "normalize"-    , OpLambda <$> o .: "lambda"+    , OpEvaluate <$> o .: "evaluate"     , do         vals <- o .: "contextualize"         case vals of@@ -308,14 +322,25 @@     , OpDataize <$> o .: "dataize"     ] +-- Parse the optional 'label', rejecting one that merely repeats the rule's+-- 'name'. A label equal to the name typesets the same token across two macros+-- and adds nothing, so it is forbidden: 'label' is meant to carry a symbol that+-- differs from the plain name (for example '\lambda' or 'disp').+parseLabel :: String -> Object -> Parser (Maybe String)+parseLabel ruleName o = do+  label' <- o .:? "label"+  if label' == Just ruleName+    then fail $ "'label' is redundant when it equals 'name' (" ++ ruleName ++ "); drop it"+    else pure label'+ instance FromJSON MorphRule where   parseJSON =     withObject       "MorphRule"-      ( \o ->-          MorphRule-            <$> o .: "name"-            <*> o .:? "label"+      ( \o -> do+          ruleName <- o .: "name"+          MorphRule ruleName+            <$> parseLabel ruleName o             <*> o .: "match"             <*> o .: "e-match"             <*> o .: "n-result"@@ -327,10 +352,10 @@   parseJSON =     withObject       "DataizeRule"-      ( \o ->-          DataizeRule-            <$> o .: "name"-            <*> o .:? "label"+      ( \o -> do+          ruleName <- o .: "name"+          DataizeRule ruleName+            <$> parseLabel ruleName o             <*> o .: "match"             <*> o .: "e-match"             <*> o .: "d-result"@@ -338,6 +363,20 @@             <*> o .:? "premises" .!= []       ) +instance FromJSON ContextualizeRule where+  parseJSON =+    withObject+      "ContextualizeRule"+      ( \o -> do+          ruleName <- o .: "name"+          ContextualizeRule ruleName+            <$> parseLabel ruleName o+            <*> o .: "match"+            <*> o .: "c-match"+            <*> o .: "c-result"+            <*> o .:? "premises" .!= []+      )+ decodeRules :: (FromJSON a) => FilePath -> BS.ByteString -> [a] decodeRules path bs = case Yaml.decodeEither' bs of   Right rs -> rs@@ -350,3 +389,7 @@ dataizationRules :: [DataizeRule] {-# NOINLINE dataizationRules #-} dataizationRules = decodeRules "resources/dataization.yaml" $(embedFile "resources/dataization.yaml")++contextualizationRules :: [ContextualizeRule]+{-# NOINLINE contextualizationRules #-}+contextualizationRules = decodeRules "resources/contextualization.yaml" $(embedFile "resources/contextualization.yaml")
test/CLISpec.hs view
@@ -367,8 +367,8 @@     it "fails when a rewriting rule uses a dataization-only function" $       withStdin "{⟦⟧}" $         testCLIFailed-          ["rewrite", rule "lambda-in-rewrite.yaml"]-          ["Function 'lambda' in rule 'uses-lambda' is available only for dataization and morphing, not for rewriting"]+          ["rewrite", rule "evaluate-in-rewrite.yaml"]+          ["Function 'evaluate' in rule 'uses-evaluate' is available only for dataization and morphing, not for rewriting"]      it "normalizes with --normalize flag" $       testCLISucceeded@@ -1047,7 +1047,6 @@         [ unlines             [ "\\begin{phinoInference}"             , "  \\phinoName{prim}"-            , "  \\phinoLabel{prim}"             , "  \\phinoConclusion{ \\phinoMorph{ [[ B ]] }{ e }{ [[ B ]] } }"             , "\\end{phinoInference}"             , "\\begin{phinoInference}"@@ -1085,7 +1084,6 @@             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{root}"-            , "  \\phinoLabel{root}"             , "  \\phinoCondition{ e \\not= Q }"             , "  \\phinoPremise{ \\phinoNormalize{ e }{ n } }"             , "  \\phinoPremise{ \\phinoMorph{ n }{ e }{ n_1 } }"@@ -1093,18 +1091,15 @@             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{dead}"-            , "  \\phinoLabel{dead}"             , "  \\phinoConclusion{ \\phinoMorph{ T }{ e }{ T } }"             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{xi}"-            , "  \\phinoLabel{xi}"             , "  \\phinoConclusion{ \\phinoMorph{ \\phiTerminal{\\xi} }{ e }{ T } }"             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{globe}"-            , "  \\phinoLabel{globe}"-            , "  \\phinoConclusion{ \\phinoMorph{ Q }{ e }{ T } }"+            , "  \\phinoConclusion{ \\phinoMorph{ Q }{ Q }{ T } }"             , "\\end{phinoInference}"             ]         ]@@ -1120,8 +1115,7 @@             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{box}"-            , "  \\phinoLabel{box}"-            , "  \\phinoCondition{ [ \\Delta, L ] \\cap ( B_1 \\cup B_2 ) = \\emptyset }"+            , "  \\phinoCondition{ [ D, L ] \\cap \\lparen B_1 \\cup B_2 \\rparen = \\emptyset }"             , "  \\phinoPremise{ \\phinoContextualize{ e_1 }{ [[ B_1, @ -> e_1, B_2 ]] }{ n } }"             , "  \\phinoPremise{ \\phinoNormalize{ n }{ n_1 } }"             , "  \\phinoPremise{ \\phinoDataize{ n_1 }{ e }{ \\delta } }"@@ -1129,7 +1123,6 @@             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{fire}"-            , "  \\phinoLabel{fire}"             , "  \\phinoPremise{ \\phinoEvaluate{ [[ B_1, L> F, B_2 ]] }{ n } }"             , "  \\phinoPremise{ \\phinoNormalize{ n }{ n_1 } }"             , "  \\phinoPremise{ \\phinoDataize{ n_1 }{ e }{ \\delta } }"@@ -1137,17 +1130,16 @@             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{none}"-            , "  \\phinoLabel{none}"+            , "  \\phinoCondition{ [ D, L, @ ] \\cap \\lparen B \\rparen = \\emptyset }"             , "  \\phinoConclusion{ \\phinoDataize{ [[ B ]] }{ e }{ -- } }"             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{bott}"-            , "  \\phinoLabel{bott}"             , "  \\phinoConclusion{ \\phinoDataize{ T }{ e }{ -- } }"             , "\\end{phinoInference}"             , "\\begin{phinoInference}"             , "  \\phinoName{norm}"-            , "  \\phinoLabel{norm}"+            , "  \\phinoCondition{ \\phinoNotFormation{ n } \\;\\text{and}\\; n \\not= T }"             , "  \\phinoPremise{ \\phinoMorph{ n }{ e }{ n_1 } }"             , "  \\phinoPremise{ \\phinoDataize{ n_1 }{ e }{ \\delta } }"             , "  \\phinoConclusion{ \\phinoDataize{ n }{ e }{ \\delta } }"@@ -1155,15 +1147,62 @@             ]         ] +    it "explains contextualization rules" $+      testCLISucceeded+        ["explain", "--contextualize"]+        [ unlines+            [ "\\begin{phinoInference}"+            , "  \\phinoName{cglobe}"+            , "  \\phinoLabel{globe}"+            , "  \\phinoConclusion{ \\phinoContextualize{ Q }{ k }{ Q } }"+            , "\\end{phinoInference}"+            , "\\begin{phinoInference}"+            , "  \\phinoName{cxi}"+            , "  \\phinoLabel{xi}"+            , "  \\phinoConclusion{ \\phinoContextualize{ \\phiTerminal{\\xi} }{ k }{ k } }"+            , "\\end{phinoInference}"+            , "\\begin{phinoInference}"+            , "  \\phinoName{cdead}"+            , "  \\phinoLabel{dead}"+            , "  \\phinoConclusion{ \\phinoContextualize{ T }{ k }{ T } }"+            , "\\end{phinoInference}"+            , "\\begin{phinoInference}"+            , "  \\phinoName{cprim}"+            , "  \\phinoLabel{prim}"+            , "  \\phinoConclusion{ \\phinoContextualize{ [[ B ]] }{ k }{ [[ B ]] } }"+            , "\\end{phinoInference}"+            , "\\begin{phinoInference}"+            , "  \\phinoName{cdispatch}"+            , "  \\phinoLabel{disp}"+            , "  \\phinoPremise{ \\phinoContextualize{ n }{ k }{ n_1 } }"+            , "  \\phinoConclusion{ \\phinoContextualize{ n . \\tau }{ k }{ n_1 . \\tau } }"+            , "\\end{phinoInference}"+            , "\\begin{phinoInference}"+            , "  \\phinoName{capplication}"+            , "  \\phinoLabel{app}"+            , "  \\phinoPremise{ \\phinoContextualize{ n }{ k }{ n_1 } }"+            , "  \\phinoPremise{ \\phinoContextualize{ e_1 }{ k }{ n_2 } }"+            , "  \\phinoConclusion{ \\phinoContextualize{ n ( \\tau -> e_1 ) }{ k }{ n_1 ( \\tau -> n_2 ) } }"+            , "\\end{phinoInference}"+            , "\\begin{phinoInference}"+            , "  \\phinoName{capplicationa}"+            , "  \\phinoLabel{appa}"+            , "  \\phinoPremise{ \\phinoContextualize{ n }{ k }{ n_1 } }"+            , "  \\phinoPremise{ \\phinoContextualize{ e_1 }{ k }{ n_2 } }"+            , "  \\phinoConclusion{ \\phinoContextualize{ n ( \\phiTerminal{\\alpha_{i}} -> e_1 ) }{ k }{ n_1 ( \\phiTerminal{\\alpha_{i}} -> n_2 ) } }"+            , "\\end{phinoInference}"+            ]+        ]+     it "fails with no rules specified" $       testCLIFailed         ["explain"]-        ["Either --rule, --normalize, --morph or --dataize must be specified"]+        ["Either --rule, --normalize, --morph, --dataize or --contextualize must be specified"]      it "fails when more than one rule set is specified" $       testCLIFailed         ["explain", "--morph", "--dataize"]-        ["Only one of --rule, --normalize, --morph or --dataize can be specified"]+        ["Only one of --rule, --normalize, --morph, --dataize or --contextualize can be specified"]      it "writes to target file" $       bracket
test/DataizeSpec.hs view
@@ -95,6 +95,27 @@       morph (chain, (Program ExRoot, Nothing) :| []) ExRoot (defaultDataizeContext ExRoot)         `shouldThrow` (\e -> "Atom 'F' does not exist" `isInfixOf` show (e :: SomeException)) +  -- 'norm' matches the bare meta 𝑛, which unifies with any expression, so it is+  -- guarded to fire only when 𝑛 is neither a formation ('not (formation 𝑛)',+  -- left to 'delta'/'box'/'fire'/'none') nor the termination ⊥ ('not (𝑛 = ⊥)',+  -- left to 'bott'). The dataization clauses are therefore disjoint and their+  -- order in 'dataization.yaml' cannot change behavior.+  describe "dataization 'norm' is disjoint from the specific clauses" $ do+    let rctx = RuleContext (execBuildTerm ExRoot (defaultDataizeContext ExRoot))+        dataizeRule :: String -> Yaml.DataizeRule+        dataizeRule nm = fromMaybe (error ("no dataization rule named " ++ nm)) (find (\r -> r.name == nm) Yaml.dataizationRules)+        asRule :: Yaml.DataizeRule -> Yaml.Rule+        asRule r = Yaml.Rule r.name Nothing Nothing r.match ExRoot r.when Nothing Nothing+    it "does not fire on a formation" $ do+      substs <- matchExpressionWithRule' [substEmpty] (ExFormation [BiDelta (BtOne "00")]) (asRule (dataizeRule "norm")) rctx+      substs `shouldBe` []+    it "does not fire on the termination ⊥" $ do+      substs <- matchExpressionWithRule' [substEmpty] ExTermination (asRule (dataizeRule "norm")) rctx+      substs `shouldBe` []+    it "still fires on a non-formation, non-termination normal form" $ do+      substs <- matchExpressionWithRule' [substEmpty] (ExDispatch ExXi (AtLabel "x")) (asRule (dataizeRule "norm")) rctx+      null substs `shouldBe` False+   describe "dataize" $     test       dataize'@@ -139,7 +160,7 @@     let verb op = case op of           Yaml.OpMorph _ -> "morph"           Yaml.OpNormalize _ -> "normalize"-          Yaml.OpLambda _ -> "lambda"+          Yaml.OpEvaluate _ -> "evaluate"           Yaml.OpContextualize _ _ -> "contextualize"           Yaml.OpDataize _ -> "dataize"         allowed =@@ -167,11 +188,12 @@         (expectationFailure ("Dataization emitted step labels with no defining rule or operation: " ++ show orphans))    describe "names every rule uniquely across rule sets" $-    it "shares no rule name between morphing, dataization and normalization" $ do+    it "shares no rule name between morphing, dataization, normalization and contextualization" $ do       let names =             map (.name) Yaml.morphingRules               ++ map (.name) Yaml.dataizationRules               ++ map (.name) Yaml.normalizationRules+              ++ map (.name) Yaml.contextualizationRules           clashes = nub (filter (\n -> length (filter (== n) names) > 1) names)       clashes `shouldBe` [] @@ -192,7 +214,7 @@                    , "alpha"                    , "copy"                    , "prim"-                   , "lambda"+                   , "evaluate"                    , "applicationa"                    , "alpha"                    , "copy"
test/ParserSpec.hs view
@@ -309,8 +309,8 @@       , ("λ ⤍ Test", Just (BiLambda (Function "Test")))       , ("L> !F", Just (BiLambda (FnMeta "F")))       , ("L> !F0", Just (BiLambda (FnMeta "F0")))-      , ("λ ⤍ 𝐹", Just (BiLambda (FnMeta "F")))-      , ("L> 𝐹2", Just (BiLambda (FnMeta "F2")))+      , ("λ ⤍ 𝑓", Just (BiLambda (FnMeta "F")))+      , ("L> 𝑓2", Just (BiLambda (FnMeta "F2")))       , ("!B", Just (BiMeta "B"))       , ("!B0", Just (BiMeta "B0"))       , ("!B_test", Just (BiMeta "B_test"))
test/PrinterSpec.hs view
@@ -63,8 +63,8 @@       )    describe "printExpression with SWEET UNICODE renders the pretty function meta" $-    it "meta lambda becomes 𝐹" $-      printExpression' (ExFormation [BiLambda (FnMeta "F")]) (SWEET, UNICODE, SINGLELINE, defaultMargin) `shouldBe` "⟦ λ ⤍ 𝐹 ⟧"+    it "meta lambda becomes 𝑓" $+      printExpression' (ExFormation [BiLambda (FnMeta "F")]) (SWEET, UNICODE, SINGLELINE, defaultMargin) `shouldBe` "⟦ λ ⤍ 𝑓 ⟧"    describe "printExpression keeps special double values in byte form so they re-parse" $     forM_
test/YamlSpec.hs view
@@ -6,10 +6,13 @@ import Control.Exception (Exception (displayException), SomeException) import Control.Monad import Data.List (isInfixOf)+import Data.Text qualified as T+import Data.Text.Encoding (encodeUtf8)+import Data.Yaml qualified as Yaml import Misc import System.FilePath-import Test.Hspec (Spec, describe, it, runIO, shouldReturn, shouldThrow)-import Yaml (yamlRule)+import Test.Hspec (Spec, describe, it, runIO, shouldReturn, shouldSatisfy, shouldThrow)+import Yaml (ContextualizeRule, DataizeRule, MorphRule, yamlRule)  spec :: Spec spec = do@@ -37,3 +40,19 @@                    in "Unknown" `isInfixOf` msg || "Exactly one" `isInfixOf` msg               )       )++  describe "rejects a label that equals the name" $ do+    let failsAsRedundant decoded = case decoded of+          Left err -> "redundant" `isInfixOf` Yaml.prettyPrintParseException err+          Right _ -> False+        decodeYaml :: (Yaml.FromJSON a) => String -> Either Yaml.ParseException a+        decodeYaml = Yaml.decodeEither' . encodeUtf8 . T.pack+    it "in a morphing rule" $+      (decodeYaml "name: prim\nlabel: prim\nmatch: ⟦𝐵⟧\ne-match: 𝑒\nn-result: ⟦𝐵⟧" :: Either Yaml.ParseException MorphRule)+        `shouldSatisfy` failsAsRedundant+    it "in a dataization rule" $+      (decodeYaml "name: bott\nlabel: bott\nmatch: ⊥\ne-match: 𝑒\nd-result: '--'" :: Either Yaml.ParseException DataizeRule)+        `shouldSatisfy` failsAsRedundant+    it "in a contextualization rule" $+      (decodeYaml "name: cxi\nlabel: cxi\nmatch: ξ\nc-match: 𝑘\nc-result: 𝑘" :: Either Yaml.ParseException ContextualizeRule)+        `shouldSatisfy` failsAsRedundant