Compare commits
10 Commits
efe0efbee7
...
4deb7bc556
Author | SHA1 | Date |
---|---|---|
Danila Fedorin | 4deb7bc556 | |
Danila Fedorin | 33c3f87564 | |
Danila Fedorin | 250dbb4ee8 | |
Danila Fedorin | acc769e799 | |
Danila Fedorin | 7cc5d05e9c | |
Danila Fedorin | e123f24af0 | |
Danila Fedorin | a24fbad249 | |
Danila Fedorin | 985be53367 | |
Danila Fedorin | 9f7b59c65d | |
Danila Fedorin | 2f1cb79013 |
2
elm.json
2
elm.json
|
@ -9,10 +9,10 @@
|
|||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/parser": "1.1.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3"
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Main</title>
|
||||
<script type="module" src="https://unpkg.com/@navsnpm/katex-expression/dist/katex-expression/katex-expression.esm.js"></script>
|
||||
<script nomodule="" src="https://unpkg.com/@navsnpm/katex-expression/dist/katex-expression/katex-expression.js"></script>
|
||||
<style>body { padding: 0; margin: 0; }</style>
|
||||
<style>
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 10em;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rule-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rule-list katex-expression {
|
||||
margin-left: .5em;
|
||||
margin-right: .5em;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.elm-root {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
border: 1px solid #c3c3c3;
|
||||
border-radius: 0.5em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.elm-root h2 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="elm"></div>
|
||||
<script src="index.js"></script>
|
||||
<script>
|
||||
var app = Elm.Main.init({
|
||||
node: document.getElementById('elm')
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
module Bergamot.Latex exposing (..)
|
||||
|
||||
import Bergamot.Syntax exposing (..)
|
||||
import Bergamot.Rules exposing (..)
|
||||
|
||||
termToLatex : (a -> String) -> Term a -> String
|
||||
termToLatex f t =
|
||||
case t of
|
||||
Call "type" [t1, t2] -> termToLatex f t1 ++ " : " ++ termToLatex f t2
|
||||
Call "intlit" [t1] -> termToLatex f t1
|
||||
Call "strlit" [t1] -> termToLatex f t1
|
||||
Call "tpair" [t1, t2] -> termToLatex f t1 ++ "\\times" ++ termToLatex f t2
|
||||
Call "plus" [t1, t2] -> termToLatex f t1 ++ " + " ++ termToLatex f t2
|
||||
Call "pair" [t1, t2] -> "(" ++ termToLatex f t1 ++ ", " ++ termToLatex f t2 ++ ")"
|
||||
Call s [] -> "\\text{" ++ s ++ "}"
|
||||
Call s ts -> "\\text{" ++ s ++ "}(" ++ String.join "," (List.map (termToLatex f) ts) ++ ")"
|
||||
Var x -> f x
|
||||
IntLit i -> String.fromInt i
|
||||
StringLit s -> "\"" ++ s ++ "\""
|
||||
|
||||
metavariableToLatex : Metavariable -> String
|
||||
metavariableToLatex (MkMetavariable s) =
|
||||
let
|
||||
noQuestion = String.dropLeft 1 s
|
||||
in
|
||||
if String.startsWith "tau" noQuestion then "\\" ++ noQuestion else noQuestion
|
||||
|
||||
ruleToLatex : Rule -> String
|
||||
ruleToLatex r = "\\cfrac{" ++ String.join "\\quad " (List.map (termToLatex metavariableToLatex) r.premises) ++ "}{" ++ termToLatex metavariableToLatex r.conclusion ++ "}\\ \\texttt{" ++ r.name ++ "}"
|
||||
|
||||
unificationVarToLatex : UnificationVar -> String
|
||||
unificationVarToLatex (MkUnificationVar s) = s
|
||||
|
||||
proofTreeToLatex : ProofTree -> String
|
||||
proofTreeToLatex (MkProofTree node) = "\\cfrac{" ++ String.join "\\quad \\quad " (List.map proofTreeToLatex node.premises) ++ "}{" ++ termToLatex unificationVarToLatex node.conclusion ++ "}\\ \\texttt{" ++ node.name ++ "}"
|
|
@ -1,6 +1,6 @@
|
|||
module Bergamot.Parser exposing (..)
|
||||
|
||||
import Bergamot.Syntax exposing (Term(..), Metavariable)
|
||||
import Bergamot.Syntax exposing (Term(..), Metavariable(..))
|
||||
import Bergamot.Rules exposing (Rule, RuleEnv)
|
||||
|
||||
import Parser exposing (Parser, Trailing(..), (|.), (|=))
|
||||
|
@ -16,12 +16,14 @@ name = Parser.variable
|
|||
, reserved = Set.empty
|
||||
}
|
||||
|
||||
variable : Parser String
|
||||
variable = Parser.variable
|
||||
{ start = \c -> c == '?'
|
||||
, inner = \c -> Char.isAlphaNum c || c == '_'
|
||||
, reserved = Set.empty
|
||||
}
|
||||
variable : Parser Metavariable
|
||||
variable =
|
||||
Parser.succeed MkMetavariable
|
||||
|= Parser.variable
|
||||
{ start = \c -> c == '?'
|
||||
, inner = \c -> Char.isAlphaNum c || c == '_'
|
||||
, reserved = Set.empty
|
||||
}
|
||||
|
||||
term : Parser (Term Metavariable)
|
||||
term = Parser.lazy (\() -> Parser.oneOf
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Bergamot.Rules exposing (..)
|
||||
|
||||
import Bergamot.Syntax exposing (Term, Metavariable, UnificationVar, unify, emptyUnificationState, instantiate, instantiateList, emptyInstantiationState, resetVars, InstantiationState, UnificationState)
|
||||
import Bergamot.Syntax exposing (Term, Metavariable, UnificationVar, unify, emptyUnificationState, instantiate, instantiateList, emptyInstantiationState, resetVars, InstantiationState, UnificationState, reify)
|
||||
import Bergamot.Search as Search exposing (Search)
|
||||
|
||||
import Debug
|
||||
|
@ -33,11 +33,26 @@ andThen f p env ps =
|
|||
p env ps
|
||||
|> Search.andThen (\(a, psp) -> (f a) env psp)
|
||||
|
||||
join : Prover (Prover a) -> Prover a
|
||||
join p = andThen (\x -> x) p
|
||||
|
||||
apply : Prover a -> Prover (a -> b) -> Prover b
|
||||
apply pa pf = pf |> andThen (\f -> map f pa)
|
||||
|
||||
map : (a -> b) -> Prover a -> Prover b
|
||||
map f p env ps =
|
||||
p env ps
|
||||
|> Search.map (Tuple.mapFirst f)
|
||||
|
||||
mapM : (a -> Prover b) -> List a -> Prover (List b)
|
||||
mapM f l =
|
||||
case l of
|
||||
x :: xs ->
|
||||
pure (::)
|
||||
|> apply (f x)
|
||||
|> apply (mapM f xs)
|
||||
[] -> pure []
|
||||
|
||||
interleave : Prover a -> Prover a -> Prover a
|
||||
interleave p1 p2 env ps =
|
||||
Search.interleave (p1 env ps) (p2 env ps)
|
||||
|
@ -51,34 +66,56 @@ fail env ps = Search.fail
|
|||
getEnv : Prover RuleEnv
|
||||
getEnv env ps = Search.pure (env, ps)
|
||||
|
||||
rule : Term UnificationVar -> Rule -> Prover ProofTree
|
||||
rule t r env ps =
|
||||
getUnificationState : Prover UnificationState
|
||||
getUnificationState env ps = Search.pure (ps.unificationState, ps)
|
||||
|
||||
liftInstantiation : (a -> InstantiationState -> (b, InstantiationState)) -> a -> Prover b
|
||||
liftInstantiation f a env ps =
|
||||
let
|
||||
(conc, is) = instantiate r.conclusion ps.instantiationState
|
||||
(prems, isp) = instantiateList r.premises is
|
||||
(b, is) = f a ps.instantiationState
|
||||
in
|
||||
case unify t conc ps.unificationState of
|
||||
Nothing -> Search.fail
|
||||
Just (tp, usp) ->
|
||||
let
|
||||
psp = { ps | instantiationState = resetVars isp
|
||||
, unificationState = usp }
|
||||
in
|
||||
provePremises prems env psp
|
||||
|> Search.map (Tuple.mapFirst (\trees -> MkProofTree { name = r.name, conclusion = tp, premises = trees }))
|
||||
Search.pure (b, { ps | instantiationState = is })
|
||||
|
||||
liftUnification : (a -> a -> UnificationState -> Maybe (a, UnificationState)) -> a -> a -> Prover a
|
||||
liftUnification f a1 a2 env ps =
|
||||
case f a1 a2 ps.unificationState of
|
||||
Just (a, us) -> Search.pure (a, { ps | unificationState = us })
|
||||
Nothing -> Search.fail
|
||||
|
||||
withVars : Prover a -> Prover a
|
||||
withVars p env ps =
|
||||
p env ps
|
||||
|> Search.map (Tuple.mapSecond (\psp -> { psp | instantiationState = resetVars psp.instantiationState }))
|
||||
|
||||
|
||||
reifyProofTree : ProofTree -> Prover ProofTree
|
||||
reifyProofTree (MkProofTree node) =
|
||||
pure (\t trees -> MkProofTree { node | conclusion = t, premises = trees })
|
||||
|> apply (map (reify node.conclusion) getUnificationState)
|
||||
|> apply (mapM reifyProofTree node.premises)
|
||||
|
||||
rule : Term UnificationVar -> Rule -> Prover ProofTree
|
||||
rule t r =
|
||||
withVars (pure Tuple.pair
|
||||
|> apply (liftInstantiation instantiate r.conclusion)
|
||||
|> apply (liftInstantiation instantiateList r.premises))
|
||||
|> andThen (\(conc, prems) ->
|
||||
pure (\tp trees -> MkProofTree { name = r.name, conclusion = tp, premises = trees })
|
||||
|> apply (liftUnification unify t conc)
|
||||
|> apply (provePremises prems))
|
||||
|
||||
provePremises : List (Term UnificationVar) -> Prover (List ProofTree)
|
||||
provePremises l =
|
||||
case l of
|
||||
t :: ts ->
|
||||
prove t
|
||||
|> andThen (\tree -> map (\trees -> tree :: trees) (provePremises ts))
|
||||
[] -> pure []
|
||||
provePremises = mapM proveTerm
|
||||
|
||||
prove : Term UnificationVar -> Prover ProofTree
|
||||
prove t =
|
||||
proveTerm : Term UnificationVar -> Prover ProofTree
|
||||
proveTerm t =
|
||||
getEnv
|
||||
|> andThen (List.foldl (\r -> interleave (rule t r)) fail << .rules)
|
||||
|> andThen (\env -> List.foldl (\r -> interleave (rule t r)) fail env.rules)
|
||||
|
||||
prove : Term Metavariable -> Prover ProofTree
|
||||
prove mt =
|
||||
liftInstantiation instantiate mt
|
||||
|> andThen proveTerm
|
||||
|
||||
single : RuleEnv -> Prover a -> Maybe a
|
||||
single env p =
|
||||
|
|
|
@ -12,11 +12,11 @@ map f s () =
|
|||
Empty -> Empty
|
||||
Found a sp -> Found (f a) (map f sp)
|
||||
|
||||
apply : Search (a -> b) -> Search a -> Search b
|
||||
apply sf sa () =
|
||||
apply : Search a -> Search (a -> b) -> Search b
|
||||
apply sa sf () =
|
||||
case sf () of
|
||||
Empty -> Empty
|
||||
Found f sfp -> interleave (map f sa) (apply sfp sa) ()
|
||||
Found f sfp -> interleave (map f sa) (apply sa sfp) ()
|
||||
|
||||
andThen : (a -> Search b) -> Search a -> Search b
|
||||
andThen f sa () =
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module Bergamot.Syntax exposing
|
||||
( Term(..), Metavariable, UnificationVar
|
||||
( Term(..), Metavariable(..), UnificationVar(..)
|
||||
, instantiate, instantiateList, InstantiationState, emptyInstantiationState, resetVars
|
||||
, unify, unifyList, UnificationState, emptyUnificationState
|
||||
, reify
|
||||
|
@ -12,8 +12,14 @@ import Tuple
|
|||
import Debug
|
||||
|
||||
type alias Name = String
|
||||
type alias Metavariable = String
|
||||
type alias UnificationVar = String
|
||||
type Metavariable = MkMetavariable String
|
||||
type UnificationVar = MkUnificationVar String
|
||||
|
||||
unMetavariable : Metavariable -> String
|
||||
unMetavariable (MkMetavariable s) = s
|
||||
|
||||
unUnificationVar : UnificationVar -> String
|
||||
unUnificationVar (MkUnificationVar s) = s
|
||||
|
||||
type Term a
|
||||
= IntLit Int
|
||||
|
@ -23,7 +29,7 @@ type Term a
|
|||
|
||||
type alias InstantiationState =
|
||||
{ counter : Int
|
||||
, vars : Dict Metavariable UnificationVar
|
||||
, vars : Dict String UnificationVar
|
||||
}
|
||||
|
||||
emptyInstantiationState = { counter = 0, vars = Dict.empty }
|
||||
|
@ -33,12 +39,15 @@ resetVars is = { is | vars = Dict.empty }
|
|||
|
||||
metavariable : Metavariable -> InstantiationState -> (UnificationVar, InstantiationState)
|
||||
metavariable mv is =
|
||||
case Dict.get mv is.vars of
|
||||
case Dict.get (unMetavariable mv) is.vars of
|
||||
Just v -> (v, is)
|
||||
Nothing ->
|
||||
let
|
||||
v = "var" ++ (String.fromInt is.counter)
|
||||
isp = { counter = is.counter + 1, vars = Dict.insert mv v is.vars }
|
||||
v = MkUnificationVar ("var" ++ (String.fromInt is.counter))
|
||||
isp =
|
||||
{ counter = is.counter + 1
|
||||
, vars = Dict.insert (unMetavariable mv) v is.vars
|
||||
}
|
||||
in (v, isp)
|
||||
|
||||
instantiateList : List (Term Metavariable) -> InstantiationState -> (List (Term UnificationVar), InstantiationState)
|
||||
|
@ -58,51 +67,58 @@ instantiate mt is =
|
|||
Var mv -> Tuple.mapFirst Var (metavariable mv is)
|
||||
|
||||
type alias UnificationInfo =
|
||||
{ equivalence : Set UnificationVar
|
||||
{ equivalence : Set String
|
||||
, term : Maybe (Term UnificationVar)
|
||||
}
|
||||
|
||||
type alias UnificationState = Dict UnificationVar UnificationInfo
|
||||
type alias UnificationState = Dict String UnificationInfo
|
||||
|
||||
emptyUnificationState = Dict.empty
|
||||
|
||||
reconcile : Set UnificationVar -> Maybe (Term UnificationVar) -> UnificationState -> UnificationState
|
||||
reconcile : Set String -> Maybe (Term UnificationVar) -> UnificationState -> UnificationState
|
||||
reconcile eq mt us =
|
||||
let newValue = { equivalence = eq, term = mt }
|
||||
in Set.foldl (\v -> Dict.insert v newValue) us eq
|
||||
|
||||
merge : UnificationVar -> UnificationVar -> UnificationState -> Maybe UnificationState
|
||||
merge v1 v2 us =
|
||||
case (Dict.get v1 us, Dict.get v2 us) of
|
||||
(Just ui1, Just ui2) ->
|
||||
let
|
||||
newEq = Set.union ui1.equivalence ui2.equivalence
|
||||
in
|
||||
case (ui1.term, ui2.term) of
|
||||
(Just t1, Just t2) ->
|
||||
unify t1 t2 us
|
||||
|> Maybe.map (\(t, usp) -> reconcile newEq (Just t) usp)
|
||||
(Just t1, Nothing) ->
|
||||
Just (reconcile newEq (Just t1) us)
|
||||
(Nothing, Just t2) ->
|
||||
Just (reconcile newEq (Just t2) us)
|
||||
(Nothing, Nothing) ->
|
||||
Just (reconcile newEq Nothing us)
|
||||
(Just ui1, Nothing) -> Just (reconcile (Set.insert v2 ui1.equivalence) ui1.term us)
|
||||
(Nothing, Just ui2) -> Just (reconcile (Set.insert v1 ui2.equivalence) ui2.term us)
|
||||
(Nothing, Nothing) -> Just (reconcile (Set.fromList [v1,v2]) Nothing us)
|
||||
let
|
||||
v1s = unUnificationVar v1
|
||||
v2s = unUnificationVar v2
|
||||
in
|
||||
case (Dict.get v1s us, Dict.get v2s us) of
|
||||
(Just ui1, Just ui2) ->
|
||||
let
|
||||
newEq = Set.union ui1.equivalence ui2.equivalence
|
||||
in
|
||||
case (ui1.term, ui2.term) of
|
||||
(Just t1, Just t2) ->
|
||||
unify t1 t2 us
|
||||
|> Maybe.map (\(t, usp) -> reconcile newEq (Just t) usp)
|
||||
(Just t1, Nothing) ->
|
||||
Just (reconcile newEq (Just t1) us)
|
||||
(Nothing, Just t2) ->
|
||||
Just (reconcile newEq (Just t2) us)
|
||||
(Nothing, Nothing) ->
|
||||
Just (reconcile newEq Nothing us)
|
||||
(Just ui1, Nothing) -> Just (reconcile (Set.insert v2s ui1.equivalence) ui1.term us)
|
||||
(Nothing, Just ui2) -> Just (reconcile (Set.insert v1s ui2.equivalence) ui2.term us)
|
||||
(Nothing, Nothing) -> Just (reconcile (Set.fromList [v1s,v2s]) Nothing us)
|
||||
|
||||
set : UnificationVar -> Term UnificationVar -> UnificationState -> Maybe (Term UnificationVar, UnificationState)
|
||||
set v t us =
|
||||
case Dict.get v us of
|
||||
Just ui ->
|
||||
case ui.term of
|
||||
Just tp ->
|
||||
unify t tp us
|
||||
|> Maybe.map (\(tpp, usp) -> (tpp, reconcile ui.equivalence (Just tpp) usp))
|
||||
Nothing ->
|
||||
Just (t, reconcile ui.equivalence (Just t) us)
|
||||
Nothing -> Just (t, Dict.insert v { equivalence = Set.singleton v, term = Just t } us)
|
||||
let
|
||||
vs = unUnificationVar v
|
||||
in
|
||||
case Dict.get vs us of
|
||||
Just ui ->
|
||||
case ui.term of
|
||||
Just tp ->
|
||||
unify t tp us
|
||||
|> Maybe.map (\(tpp, usp) -> (tpp, reconcile ui.equivalence (Just tpp) usp))
|
||||
Nothing ->
|
||||
Just (t, reconcile ui.equivalence (Just t) us)
|
||||
Nothing -> Just (t, Dict.insert vs { equivalence = Set.singleton vs, term = Just t } us)
|
||||
|
||||
unifyList : List (Term UnificationVar) -> List (Term UnificationVar) -> UnificationState -> Maybe (List (Term UnificationVar), UnificationState)
|
||||
unifyList l1 l2 us =
|
||||
|
@ -136,7 +152,7 @@ reify t us =
|
|||
StringLit s -> StringLit s
|
||||
Call n ts -> Call n (List.map (\tp -> reify tp us) ts)
|
||||
Var v ->
|
||||
case Dict.get v us of
|
||||
case Dict.get (unUnificationVar v) us of
|
||||
Just ui ->
|
||||
case ui.term of
|
||||
Just tp -> reify tp us
|
||||
|
|
66
src/Main.elm
66
src/Main.elm
|
@ -3,32 +3,70 @@ module Main exposing (main)
|
|||
import Browser
|
||||
import Html exposing (Html)
|
||||
import Html.Events exposing (onInput)
|
||||
import Html.Attributes exposing (type_, class)
|
||||
import Bergamot.Syntax exposing (..)
|
||||
import Bergamot.Search exposing (..)
|
||||
import Bergamot.Rules exposing (..)
|
||||
import Bergamot.Parser exposing (..)
|
||||
import Bergamot.Latex exposing (..)
|
||||
import Json.Encode
|
||||
import Maybe
|
||||
import Tuple
|
||||
import Debug
|
||||
|
||||
type alias Model = { program : String }
|
||||
type alias Model =
|
||||
{ program : String
|
||||
, query : String
|
||||
}
|
||||
type alias Flags = ()
|
||||
type Msg
|
||||
= SetProgram String
|
||||
| SetQuery String
|
||||
|
||||
init : Flags -> (Model, Cmd Msg)
|
||||
init () = ({ program = "" }, Cmd.none)
|
||||
init () = ({ program = "", query = "" }, Cmd.none)
|
||||
|
||||
viewSection : String -> Html Msg -> Html Msg
|
||||
viewSection name content =
|
||||
Html.div [] [ Html.h2 [] [ Html.text name ], content ]
|
||||
|
||||
viewRule : Rule -> Html Msg
|
||||
viewRule = latex << ruleToLatex
|
||||
|
||||
viewRules : String -> Html Msg
|
||||
viewRules progs = viewSection "Rendered Rules" <|
|
||||
Html.div [ class "rule-list" ] <|
|
||||
case run program progs of
|
||||
Just prog -> List.map viewRule prog.rules
|
||||
Nothing -> []
|
||||
|
||||
tryProve : String -> String -> Maybe ProofTree
|
||||
tryProve progs querys =
|
||||
case (run program progs, run term querys) of
|
||||
(Just prog, Just query) -> single prog (prove query |> Bergamot.Rules.andThen reifyProofTree)
|
||||
_ -> Nothing
|
||||
|
||||
viewProofTree : String -> String -> Html Msg
|
||||
viewProofTree progs querys = viewSection "Proof Tree" <|
|
||||
Html.div [ class "proof-tree" ] <|
|
||||
case tryProve progs querys of
|
||||
Just tree -> [ latex (proofTreeToLatex tree) ]
|
||||
Nothing -> []
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view m = Html.div []
|
||||
[ Html.textarea [ onInput SetProgram ] []
|
||||
, Html.p [] [ Html.text (Debug.toString (run program m.program)) ]
|
||||
view m = Html.div [ class "elm-root" ]
|
||||
[ viewSection "Rules" <| Html.textarea [ onInput SetProgram ] []
|
||||
, viewSection "Query" <| Html.input [ type_ "text", onInput SetQuery ] []
|
||||
, viewRules m.program
|
||||
, viewProofTree m.program m.query
|
||||
]
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg m =
|
||||
case msg of
|
||||
SetProgram prog -> ({ m | program = prog }, Cmd.none)
|
||||
SetQuery query -> ({ m | query = query }, Cmd.none)
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ = Sub.none
|
||||
|
@ -40,3 +78,21 @@ main =
|
|||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
||||
|
||||
-- Latex support:
|
||||
-- Based on https://stackoverflow.com/questions/75492820/embedding-mathematical-equations-in-an-elm-spa
|
||||
|
||||
latex : String -> Html Msg
|
||||
latex expr =
|
||||
Html.node "katex-expression"
|
||||
[ Html.Attributes.attribute "expression" expr
|
||||
, Html.Attributes.attribute "katex-options" (Json.Encode.encode 0 options)
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
options : Json.Encode.Value
|
||||
options =
|
||||
Json.Encode.object
|
||||
[ ( "displayMode", Json.Encode.bool True )
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue