Compare commits

..

2 Commits

2 changed files with 109 additions and 7 deletions

View File

@ -0,0 +1,99 @@
data ExprType
= IntType
| BoolType
| StringType
repr : ExprType -> Type
repr IntType = Int
repr BoolType = Bool
repr StringType = String
intBoolImpossible : IntType = BoolType -> Void
intBoolImpossible Refl impossible
intStringImpossible : IntType = StringType -> Void
intStringImpossible Refl impossible
boolStringImpossible : BoolType = StringType -> Void
boolStringImpossible Refl impossible
decEq : (a : ExprType) -> (b : ExprType) -> Dec (a = b)
decEq {a = IntType} {b = IntType} = Yes Refl
decEq {a = BoolType} {b = BoolType} = Yes Refl
decEq {a = StringType} {b = StringType} = Yes Refl
decEq {a = IntType} {b = BoolType} = No intBoolImpossible
decEq {a = BoolType} {b = IntType} = No $ intBoolImpossible . sym
decEq {a = IntType} {b = StringType} = No intStringImpossible
decEq {a = StringType} {b = IntType} = No $ intStringImpossible . sym
decEq {a = BoolType} {b = StringType} = No boolStringImpossible
decEq {a = StringType} {b = BoolType} = No $ boolStringImpossible . sym
data Op
= Add
| Subtract
| Multiply
| Divide
data Expr
= IntLit Int
| BoolLit Bool
| StringLit String
| BinOp Op Expr Expr
| IfElse Expr Expr Expr
data SafeExpr : ExprType -> Type where
IntLiteral : Int -> SafeExpr IntType
BoolLiteral : Bool -> SafeExpr BoolType
StringLiteral : String -> SafeExpr StringType
BinOperation : (repr a -> repr b -> repr c) -> SafeExpr a -> SafeExpr b -> SafeExpr c
IfThenElse : { t : ExprType } -> SafeExpr BoolType -> SafeExpr t -> SafeExpr t -> SafeExpr t
typecheckOp : Op -> (a : ExprType) -> (b : ExprType) -> Either String (c : ExprType ** repr a -> repr b -> repr c)
typecheckOp Add IntType IntType = Right (IntType ** (+))
typecheckOp Subtract IntType IntType = Right (IntType ** (-))
typecheckOp Multiply IntType IntType = Right (IntType ** (*))
typecheckOp Divide IntType IntType = Right (IntType ** div)
typecheckOp _ _ _ = Left "Invalid binary operator application"
requireBool : (n : ExprType ** SafeExpr n) -> Either String (SafeExpr BoolType)
requireBool (BoolType ** e) = Right e
requireBool _ = Left "Not a boolean."
typecheck : Expr -> Either String (n : ExprType ** SafeExpr n)
typecheck (IntLit i) = Right (_ ** IntLiteral i)
typecheck (BoolLit b) = Right (_ ** BoolLiteral b)
typecheck (StringLit s) = Right (_ ** StringLiteral s)
typecheck (BinOp o l r) = do
(lt ** le) <- typecheck l
(rt ** re) <- typecheck r
(ot ** f) <- typecheckOp o lt rt
pure (_ ** BinOperation f le re)
typecheck (IfElse c t e) =
do
ce <- typecheck c >>= requireBool
(tt ** te) <- typecheck t
(et ** ee) <- typecheck e
case decEq tt et of
Yes p => pure (_ ** IfThenElse ce (replace p te) ee)
No _ => Left "Incompatible branch types."
eval : SafeExpr t -> repr t
eval (IntLiteral i) = i
eval (BoolLiteral b) = b
eval (StringLiteral s) = s
eval (BinOperation f l r) = f (eval l) (eval r)
eval (IfThenElse c t e) = if (eval c) then (eval t) else (eval e)
resultStr : {t : ExprType} -> repr t -> String
resultStr {t=IntType} i = show i
resultStr {t=BoolType} b = show b
resultStr {t=StringType} s = show s
tryEval : Expr -> String
tryEval ex =
case typecheck ex of
Left err => "Type error: " ++ err
Right (t ** e) => resultStr $ eval {t} e
main : IO ()
main = putStrLn $ tryEval $ BinOp Add (IfElse (BoolLit True) (IntLit 6) (IntLit 7)) (BinOp Multiply (IntLit 160) (IntLit 2))

View File

@ -42,9 +42,9 @@ like mine, to render math on the backend.
I settled on the following architecture: I settled on the following architecture:
* As before I would generate my pages using Hugo. * As before, I would generate my pages using Hugo.
* I would use the KaTeX NPM package to rendering math. * I would use the KaTeX NPM package to render math.
* To build the website no matter what computer I was on, I would use Nix. * To build the website no matter what system I was on, I would use Nix.
It so happens that Nix isn't really required for using my approach in general. It so happens that Nix isn't really required for using my approach in general.
I will give my setup here, but feel free to skip ahead. I will give my setup here, but feel free to skip ahead.
@ -119,9 +119,12 @@ which replaced mathematical expressions in a page with their SVG forms.
This is still the case, in both MathJax and KaTeX. The ability This is still the case, in both MathJax and KaTeX. The ability
to render math in one step is the main selling point of front-end LaTeX renderers: to render math in one step is the main selling point of front-end LaTeX renderers:
all you have to do is drop in a file from a CDN, and voila, you have your all you have to do is drop in a file from a CDN, and voila, you have your
math. There are no such easy answers for back-end rendering. math. There are no such easy answers for back-end rendering. I decided
to write my own Ruby script to get the job done. From this script, I
would call the `katex` command-line program, which would perform
the heavy lifting of rendering the mathematics.
So what _do_ I do? Well, there are two types on my website: inline math and display math. There are two types of math on my website: inline math and display math.
On the command line ([here are the docs](https://katex.org/docs/cli.html)), On the command line ([here are the docs](https://katex.org/docs/cli.html)),
the distinction is made using the `--display-mode` argument. So, the general algorithm the distinction is made using the `--display-mode` argument. So, the general algorithm
is to replace the code inside the `$$...$$` with their display-rendered version, is to replace the code inside the `$$...$$` with their display-rendered version,
@ -167,7 +170,7 @@ end
There's a bit of a trick to the final layer of this script. We want to be There's a bit of a trick to the final layer of this script. We want to be
really careful about where we replace LaTeX, and where we don't. In really careful about where we replace LaTeX, and where we don't. In
particular, we _don't_ want to go into the `code` tags. Otherwise, particular, we _don't_ want to go into the `code` tags. Otherwise,
it wouldn't be able to talk about LaTeX code! Thus, we can't just it wouldn't be possible to talk about LaTeX code! Thus, we can't just
search-and-replace over the entire HTML document; we need to be context search-and-replace over the entire HTML document; we need to be context
aware. This is where `nokigiri` comes in. We parse the HTML, and iterate aware. This is where `nokigiri` comes in. We parse the HTML, and iterate
over all of the 'text' nodes, calling `perform_katex_sub` on all over all of the 'text' nodes, calling `perform_katex_sub` on all
@ -198,7 +201,7 @@ We write:
* `//`, starting to search for nodes everywhere, not just the root of the document. * `//`, starting to search for nodes everywhere, not just the root of the document.
* `*`, to match _any_ node. We want to replace math inside of `div`s, `span`s, `nav`s, * `*`, to match _any_ node. We want to replace math inside of `div`s, `span`s, `nav`s,
all of the `h`s, and so on. all of the `h`s, and so on.
* `[not(self::code)]` cutting out all the `code` tags. * `[not(self::code)]`, cutting out all the `code` tags.
* `/`, now selecting the nodes that are immediate descendants of the nodes we've selected. * `/`, now selecting the nodes that are immediate descendants of the nodes we've selected.
* `text()`, giving us the text contents of all the nodes we've selected. * `text()`, giving us the text contents of all the nodes we've selected.