Change HTML syntax, split into modules
This commit is contained in:
parent
9de870fb20
commit
97f86146a5
149
src/HTML.agda
149
src/HTML.agda
|
@ -1,16 +1,70 @@
|
||||||
open import Agda.Primitive renaming (Set to Type)
|
open import Agda.Primitive renaming (Set to Type)
|
||||||
|
|
||||||
open import Relation.Binary.PropositionalEquality
|
open import Data.Nat
|
||||||
open import Data.List hiding (head; span)
|
|
||||||
open import Data.Nat using (ℕ; _<ᵇ_)
|
|
||||||
open import Data.String hiding (_==_; show; head) renaming (_++_ to _++ˢ_; concat to concatˢ)
|
|
||||||
open import Data.Char hiding (_==_; show)
|
|
||||||
open import Data.Bool
|
open import Data.Bool
|
||||||
|
|
||||||
|
import Data.String as S
|
||||||
|
import Data.Char as C
|
||||||
|
import Data.List as L
|
||||||
|
open S using (String)
|
||||||
|
open C using (Char)
|
||||||
|
open L using (List)
|
||||||
|
|
||||||
open import Base
|
open import Base
|
||||||
|
|
||||||
module HTML where
|
module HTML where
|
||||||
|
|
||||||
|
private
|
||||||
|
_≤ᶜ_ : Char → Char → Bool
|
||||||
|
c₀ ≤ᶜ c₁ = C.toℕ c₀ ≤ᵇ C.toℕ c₁
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
valid-attribute-name : String → Bool
|
||||||
|
valid-attribute-name x = L.all valid-attribute-char (S.toList x)
|
||||||
|
where
|
||||||
|
valid-attribute-char : Char → Bool
|
||||||
|
valid-attribute-char c =
|
||||||
|
('a' ≤ᶜ c) ∧ (c ≤ᶜ 'z') ∨
|
||||||
|
('A' ≤ᶜ c) ∧ (c ≤ᶜ 'Z')
|
||||||
|
|
||||||
|
valid-tag-name : String → Bool
|
||||||
|
valid-tag-name x = L.all valid-tag-char (S.toList x)
|
||||||
|
where
|
||||||
|
valid-tag-char : Char → Bool
|
||||||
|
valid-tag-char c =
|
||||||
|
('a' ≤ᶜ c) ∧ (c ≤ᶜ 'z') ∨
|
||||||
|
('A' ≤ᶜ c) ∧ (c ≤ᶜ 'Z') ∨
|
||||||
|
('0' ≤ᶜ c) ∧ (c ≤ᶜ '9')
|
||||||
|
|
||||||
|
record Attribute : Type where
|
||||||
|
field
|
||||||
|
name : String
|
||||||
|
value : String
|
||||||
|
{name-is-valid}: T (valid-attribute-name name)
|
||||||
|
|
||||||
|
instance
|
||||||
|
open Attribute
|
||||||
|
EqAttribute : Eq Attribute
|
||||||
|
EqAttribute ._==_ x y = x .name == y .name ∧ x .value == y .value
|
||||||
|
|
||||||
|
record HTMLTag : Type where
|
||||||
|
field
|
||||||
|
name : String
|
||||||
|
attributes : List Attribute
|
||||||
|
{name-is-valid}: T (valid-tag-name name)
|
||||||
|
|
||||||
|
instance
|
||||||
|
open HTMLTag
|
||||||
|
|
||||||
|
EqTag : Eq HTMLTag
|
||||||
|
EqTag ._==_ x y = x .name == y .name ∧ x .attributes == y .attributes
|
||||||
|
|
||||||
|
data Element : Type where
|
||||||
|
textnode : String → Element
|
||||||
|
node : HTMLTag → List Element → Element
|
||||||
|
|
||||||
|
{-
|
||||||
data HTMLTag : Type where
|
data HTMLTag : Type where
|
||||||
html head meta title body : HTMLTag
|
html head meta title body : HTMLTag
|
||||||
div p a span : HTMLTag
|
div p a span : HTMLTag
|
||||||
|
@ -42,29 +96,24 @@ instance
|
||||||
ShowHTMLTag .show span = "span"
|
ShowHTMLTag .show span = "span"
|
||||||
ShowHTMLTag .show (h n) = "h" ++ˢ show n
|
ShowHTMLTag .show (h n) = "h" ++ˢ show n
|
||||||
|
|
||||||
data AttributeName : Type where
|
data Attr : Type where
|
||||||
lang src name content href style : AttributeName
|
lang= src= name= content= href= style= : String → Attr
|
||||||
|
|
||||||
instance
|
attr-name : Attr → String
|
||||||
ShowAttributeName : Show AttributeName
|
attr-name (lang= _) = "lang"
|
||||||
ShowAttributeName .show lang = "lang"
|
attr-name (src= _) = "src"
|
||||||
ShowAttributeName .show src = "src"
|
attr-name (name= _) = "name"
|
||||||
ShowAttributeName .show name = "name"
|
attr-name (content= _) = "content"
|
||||||
ShowAttributeName .show content = "content"
|
attr-name (href= _) = "href"
|
||||||
ShowAttributeName .show href = "href"
|
attr-name (style= _) = "style"
|
||||||
ShowAttributeName .show style = "style"
|
|
||||||
|
|
||||||
record Attribute : Type where
|
attr-value : Attr → String
|
||||||
field
|
attr-value (lang= x) = x
|
||||||
attrname : AttributeName
|
attr-value (src= x) = x
|
||||||
value : String -- maybe string ? how to handle syntax?
|
attr-value (name= x) = x
|
||||||
open Attribute
|
attr-value (content= x) = x
|
||||||
|
attr-value (href= x) = x
|
||||||
AttrList : Type
|
attr-value (style= x) = x
|
||||||
AttrList = List Attribute
|
|
||||||
|
|
||||||
_=_ : AttributeName → String → AttrList
|
|
||||||
attrname = value = record { attrname = attrname ; value = value } ∷ []
|
|
||||||
|
|
||||||
mutual
|
mutual
|
||||||
DOM : Type
|
DOM : Type
|
||||||
|
@ -72,7 +121,7 @@ mutual
|
||||||
|
|
||||||
data Element : Type where
|
data Element : Type where
|
||||||
textnode : String → Element
|
textnode : String → Element
|
||||||
element : HTMLTag → AttrList → DOM → Element
|
element : HTMLTag → List Attr → DOM → Element
|
||||||
|
|
||||||
record Listable (T L : Type) : Type where
|
record Listable (T L : Type) : Type where
|
||||||
field
|
field
|
||||||
|
@ -80,30 +129,29 @@ record Listable (T L : Type) : Type where
|
||||||
open Listable ⦃...⦄
|
open Listable ⦃...⦄
|
||||||
|
|
||||||
instance
|
instance
|
||||||
ListListable : {T : Type} → Listable (List T) T
|
|
||||||
ListListable .enlist l = l
|
|
||||||
|
|
||||||
StringDOMAble : Listable String Element
|
StringDOMAble : Listable String Element
|
||||||
StringDOMAble .enlist x = textnode x ∷ []
|
StringDOMAble .enlist x = textnode x ∷ []
|
||||||
|
|
||||||
_,_ : {D₁ D₂ L : Type} → ⦃ Listable D₁ L ⦄ → ⦃ Listable D₂ L ⦄ → D₁ → D₂ → List L
|
ListListable : {T : Type} → Listable (List T) T
|
||||||
x , y = (enlist x) Data.List.++ (enlist y)
|
ListListable .enlist x = x
|
||||||
|
|
||||||
infixr 10 _,_
|
IdListable : {T : Type} → Listable T T
|
||||||
|
IdListable .enlist x = x ∷ []
|
||||||
|
|
||||||
<_&_>_</_> : {D : Type} → ⦃ Listable D Element ⦄ → (x : HTMLTag) → AttrList → D → (y : HTMLTag) → {T (x == y)} → DOM
|
PairListable : {T L : Type} → ⦃ Listable T L ⦄ → Listable (L × T) L
|
||||||
< tag & attrs > inner </ tag′ > = element tag attrs (enlist inner) ∷ []
|
PairListable .enlist (x , y) = x ∷ enlist y
|
||||||
|
|
||||||
<_>_</_> : {D : Type} → ⦃ Listable D Element ⦄ → (x : HTMLTag) → D → (y : HTMLTag) → {T (x == y)} → DOM
|
<_&_>_</_> : {Es As : Type} → ⦃ Listable Es Element ⦄ → ⦃ Listable As Attr ⦄ → (x : HTMLTag) → As → Es → (y : HTMLTag) → {T (x == y)} → Element
|
||||||
< tag > inner </ tag′ > = element tag [] (enlist inner) ∷ []
|
< tag & attrs > inner </ tag′ > = element tag (enlist attrs) (enlist inner)
|
||||||
|
|
||||||
<_&_/> : (x : HTMLTag) → AttrList → DOM
|
<_>_</_> : {Es : Type} → ⦃ Listable Es Element ⦄ → (x : HTMLTag) → Es → (y : HTMLTag) → {T (x == y)} → Element
|
||||||
< tag & attrs /> = element tag attrs [] ∷ []
|
< tag > inner </ tag′ > = element tag [] (enlist inner)
|
||||||
|
|
||||||
<_/> : (x : HTMLTag) → DOM
|
|
||||||
< tag /> = element tag [] [] ∷ []
|
|
||||||
|
|
||||||
|
<_&_/> : {As : Type} → ⦃ Listable As Attr ⦄ → (x : HTMLTag) → As → Element
|
||||||
|
< tag & attrs /> = element tag (enlist attrs) []
|
||||||
|
|
||||||
|
<_/> : (x : HTMLTag) → Element
|
||||||
|
< tag /> = element tag [] []
|
||||||
|
|
||||||
encode : List Char → String → String
|
encode : List Char → String → String
|
||||||
encode escapes text = fromList (concatMap (λ x → toList (escape-char x)) (toList text))
|
encode escapes text = fromList (concatMap (λ x → toList (escape-char x)) (toList text))
|
||||||
|
@ -119,8 +167,8 @@ private
|
||||||
≡ "hi <3 </p>"
|
≡ "hi <3 </p>"
|
||||||
encode-test = refl
|
encode-test = refl
|
||||||
|
|
||||||
render-attr : Attribute → String
|
render-attr : Attr → String
|
||||||
render-attr x = show (x .attrname) ++ˢ "=" ++ˢ encode (toList "\t\n\r \"'=<>`") (x .value)
|
render-attr x = show (attr-name x) ++ˢ "=" ++ˢ encode (toList "\t\n\r \"'=<>`") (attr-value x)
|
||||||
|
|
||||||
{-# TERMINATING #-} -- TODO: we should eliminate the tree as a traversable instead of doing it like this
|
{-# TERMINATING #-} -- TODO: we should eliminate the tree as a traversable instead of doing it like this
|
||||||
render-element : Element → String
|
render-element : Element → String
|
||||||
|
@ -130,16 +178,13 @@ render-element (element tag attrlist inner) =
|
||||||
++ˢ concatˢ (map render-element inner)
|
++ˢ concatˢ (map render-element inner)
|
||||||
++ˢ "</" ++ˢ show tag ++ˢ ">"
|
++ˢ "</" ++ˢ show tag ++ˢ ">"
|
||||||
|
|
||||||
render-dom : DOM → String
|
sample-dom : Element
|
||||||
render-dom x = concatˢ (map render-element x)
|
|
||||||
|
|
||||||
sample-dom : DOM
|
|
||||||
sample-dom =
|
sample-dom =
|
||||||
< html & lang = "en" >
|
< html & lang= "en" >
|
||||||
< head >
|
< head >
|
||||||
< title > "Test page in Agda" </ title > ,
|
< title > "Test page in Agda" </ title > ,
|
||||||
< meta & name = "description" , content = "Simple webpage rendered in Agda DSL" /> ,
|
< meta & name= "description" , content= "Simple webpage rendered in Agda DSL" /> ,
|
||||||
< meta & name = "author" , content = "xenia" />
|
< meta & name= "author" , content= "xenia" />
|
||||||
</ head > ,
|
</ head > ,
|
||||||
< body >
|
< body >
|
||||||
< p > "hello" </ p > ,
|
< p > "hello" </ p > ,
|
||||||
|
@ -149,3 +194,5 @@ sample-dom =
|
||||||
</ div >
|
</ div >
|
||||||
</ body >
|
</ body >
|
||||||
</ html >
|
</ html >
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
42
src/HTML/Render.agda
Normal file
42
src/HTML/Render.agda
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
open import Agda.Primitive renaming (Set to Type)
|
||||||
|
|
||||||
|
open import Data.Bool
|
||||||
|
|
||||||
|
import Data.String as S
|
||||||
|
import Data.List as L
|
||||||
|
import Data.Char as C
|
||||||
|
open S using (String)
|
||||||
|
open L using (List)
|
||||||
|
open C using (Char)
|
||||||
|
|
||||||
|
open import HTML
|
||||||
|
open import Base
|
||||||
|
|
||||||
|
module HTML.Render where
|
||||||
|
|
||||||
|
private
|
||||||
|
-- encodes using HTML decimal escapes (ɭ)
|
||||||
|
encode : List Char → String → String
|
||||||
|
encode escapes text = S.fromList (L.concatMap (λ x → S.toList (escape-char x)) (S.toList text))
|
||||||
|
where
|
||||||
|
escape-char : Char → String
|
||||||
|
escape-char ch =
|
||||||
|
if L.any (_== ch) escapes
|
||||||
|
then "&#" S.++ show (C.toℕ ch) S.++ ";"
|
||||||
|
else S.fromChar ch
|
||||||
|
|
||||||
|
render-attr : Attribute → String
|
||||||
|
render-attr x = x .name S.++ "=" S.++ encode (S.toList "\t\n\r \"'=<>`") (x .value)
|
||||||
|
where
|
||||||
|
open Attribute
|
||||||
|
|
||||||
|
{-# TERMINATING #-} -- TODO: we should eliminate the tree as a traversable instead of doing it like this
|
||||||
|
render-element : Element → String
|
||||||
|
render-element (textnode x) = encode (S.toList "</>") x -- we don't technically need to escape all these characters, but it's easier to do this
|
||||||
|
render-element (node tag inner) =
|
||||||
|
"<" S.++ tag .name S.++ S.concat (L.map (λ attr → " " S.++ render-attr attr) (tag .attributes)) S.++ ">"
|
||||||
|
S.++ S.concat (L.map render-element inner)
|
||||||
|
S.++ "</" S.++ tag .name S.++ ">"
|
||||||
|
where
|
||||||
|
open HTMLTag
|
||||||
|
|
103
src/HTML/Syntax.agda
Normal file
103
src/HTML/Syntax.agda
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
open import Agda.Primitive renaming (Set to Type)
|
||||||
|
|
||||||
|
open import Data.Product
|
||||||
|
open import Data.Bool hiding (_<_)
|
||||||
|
|
||||||
|
import Data.String as S
|
||||||
|
import Data.List as L
|
||||||
|
open S using (String)
|
||||||
|
open L using (List)
|
||||||
|
|
||||||
|
open import Base
|
||||||
|
|
||||||
|
open import HTML
|
||||||
|
|
||||||
|
module HTML.Syntax where
|
||||||
|
|
||||||
|
module Attributes where
|
||||||
|
open HTMLTag
|
||||||
|
mkAttr : (name : String) → {T (valid-attribute-name name)} → HTMLTag → String → HTMLTag
|
||||||
|
mkAttr attr-name {attr-name-is-valid} tag value =
|
||||||
|
record tag
|
||||||
|
{ attributes = record
|
||||||
|
{ name = attr-name
|
||||||
|
; value = value
|
||||||
|
; name-is-valid = attr-name-is-valid
|
||||||
|
}
|
||||||
|
L.∷ tag .attributes
|
||||||
|
; name = tag .name
|
||||||
|
; name-is-valid = tag .name-is-valid
|
||||||
|
}
|
||||||
|
|
||||||
|
_id=_ = mkAttr "id"
|
||||||
|
infixl 20 _id=_
|
||||||
|
_class=_ = mkAttr "class"
|
||||||
|
infixl 20 _class=_
|
||||||
|
_style=_ = mkAttr "style"
|
||||||
|
infixl 20 _style=_
|
||||||
|
_lang=_ = mkAttr "lang"
|
||||||
|
infixl 20 _lang=_
|
||||||
|
_src=_ = mkAttr "src"
|
||||||
|
infixl 20 _src=_
|
||||||
|
_href=_ = mkAttr "href"
|
||||||
|
infixl 20 _href=_
|
||||||
|
_name=_ = mkAttr "name"
|
||||||
|
infixl 20 _name=_
|
||||||
|
_content=_ = mkAttr "content"
|
||||||
|
infixl 20 _content=_
|
||||||
|
|
||||||
|
open Attributes hiding (mkAttr) public
|
||||||
|
|
||||||
|
module Tags where
|
||||||
|
mkTag : (name : String) → {T (valid-tag-name name)} → HTMLTag
|
||||||
|
mkTag name {name-is-valid} = record { name = name ; attributes = L.[]; name-is-valid = name-is-valid }
|
||||||
|
|
||||||
|
html = mkTag "html"
|
||||||
|
body = mkTag "body"
|
||||||
|
head = mkTag "head"
|
||||||
|
meta = mkTag "meta"
|
||||||
|
title = mkTag "title"
|
||||||
|
img = mkTag "img"
|
||||||
|
p = mkTag "p"
|
||||||
|
span = mkTag "span"
|
||||||
|
footer = mkTag "footer"
|
||||||
|
|
||||||
|
open import Data.Nat
|
||||||
|
open import Relation.Binary.PropositionalEquality
|
||||||
|
postulate
|
||||||
|
h-is-valid : (x : ℕ) → T (valid-tag-name ("h" S.++ show x)) -- :3
|
||||||
|
h : ℕ → HTMLTag
|
||||||
|
h x = record
|
||||||
|
{ name = "h" S.++ show x
|
||||||
|
; attributes = L.[]
|
||||||
|
; name-is-valid = h-is-valid x
|
||||||
|
}
|
||||||
|
|
||||||
|
open Tags hiding (mkTag) public
|
||||||
|
|
||||||
|
record DOMAble (T : Type) : Type where
|
||||||
|
field
|
||||||
|
endom : T → List Element
|
||||||
|
open DOMAble ⦃...⦄
|
||||||
|
|
||||||
|
instance
|
||||||
|
TextnodeDOMAble : DOMAble String
|
||||||
|
TextnodeDOMAble .endom x = textnode x L.∷ L.[]
|
||||||
|
|
||||||
|
ElementDOMAble : DOMAble Element
|
||||||
|
ElementDOMAble .endom e = e L.∷ L.[]
|
||||||
|
|
||||||
|
ListDOMAble : {T : Type} → ⦃ DOMAble T ⦄ → DOMAble (List T)
|
||||||
|
ListDOMAble .endom x = L.concatMap endom x
|
||||||
|
|
||||||
|
PairDOMAble : {T₀ T₁ : Type} → ⦃ DOMAble T₀ ⦄ → ⦃ DOMAble T₁ ⦄ → DOMAble (T₀ × T₁)
|
||||||
|
PairDOMAble .endom (x , y) = endom x L.++ endom y
|
||||||
|
|
||||||
|
module Syntax where
|
||||||
|
open HTMLTag
|
||||||
|
<_>_</_> : {D : Type} → ⦃ DOMAble D ⦄ → (t₀ : HTMLTag) → D → (t₁ : HTMLTag) → {T (t₀ .name == t₁ .name)} → Element
|
||||||
|
< t₀ > sub </ t₁ > = node t₀ (endom sub)
|
||||||
|
|
||||||
|
<_/> : HTMLTag → Element
|
||||||
|
<_/> t = node t L.[]
|
||||||
|
open Syntax public
|
|
@ -8,7 +8,7 @@ import Data.Vec as V
|
||||||
import Data.List as L
|
import Data.List as L
|
||||||
open S using (String)
|
open S using (String)
|
||||||
open L using (List)
|
open L using (List)
|
||||||
open import Data.Product using (_×_; proj₁; proj₂) renaming (_,_ to _,,,_)
|
open import Data.Product using (_×_; proj₁; proj₂; _,_)
|
||||||
open import Data.Nat hiding (_<_; _>_)
|
open import Data.Nat hiding (_<_; _>_)
|
||||||
open import Data.Unit using (⊤; tt)
|
open import Data.Unit using (⊤; tt)
|
||||||
open import Data.Bool
|
open import Data.Bool
|
||||||
|
@ -23,7 +23,9 @@ open import NonEmpty
|
||||||
open import Parse-HTTP
|
open import Parse-HTTP
|
||||||
open import Parse-HTTP.URL hiding (_/_)
|
open import Parse-HTTP.URL hiding (_/_)
|
||||||
open import Parse-HTTP.Methods
|
open import Parse-HTTP.Methods
|
||||||
import HTML
|
open import HTML
|
||||||
|
import HTML.Syntax
|
||||||
|
import HTML.Render
|
||||||
import UTF-8
|
import UTF-8
|
||||||
|
|
||||||
response-with-content : ℕ → S.String → String → List Byte → List Byte
|
response-with-content : ℕ → S.String → String → List Byte → List Byte
|
||||||
|
@ -60,23 +62,29 @@ response-without-content status-code status =
|
||||||
module Pages where
|
module Pages where
|
||||||
open HTML
|
open HTML
|
||||||
|
|
||||||
render-index : Maybe S.String → HTML.DOM
|
render-index : Maybe S.String → HTML.Element
|
||||||
render-index query =
|
render-index query =
|
||||||
< html & lang = "en" , style = "color: yellow; background: black; padding: 1em;" >
|
< html lang= "en" style= "color: white; background: black; padding: 1em;" >
|
||||||
< head >
|
< head >
|
||||||
< title > "Test page in Agda" </ title > ,
|
< title > "henttp index" </ title > ,
|
||||||
< meta & name = "description" , content = "Simple webpage rendered in Agda DSL" /> ,
|
< meta name= "description" content= "Simple webpage rendered in Agda DSL" /> ,
|
||||||
< meta & name = "author" , content = "xenia" />
|
< meta name= "author" content= "xenia" />
|
||||||
</ head > ,
|
</ head > ,
|
||||||
< body >
|
< body >
|
||||||
< h 1 > "hi" </ h 1 > ,
|
< h 1 >
|
||||||
|
< span style= "color: yellow;" > "hen" </ span > ,
|
||||||
|
< span style= "color: purple;" > "ttp" </ span >
|
||||||
|
</ h 1 > ,
|
||||||
< p >
|
< p >
|
||||||
"welcome to the " ,
|
"what is this?"
|
||||||
< span & style = "color: white;" > "index" </ span > ,
|
|
||||||
(((λ q → < span & style = "color: fuchsia;" > " (query = " , q , ")" </ span >) <$> query) or-else L.[])
|
|
||||||
</ p >
|
</ p >
|
||||||
</ body >
|
</ body > ,
|
||||||
|
< footer >
|
||||||
|
"meow"
|
||||||
|
</ footer >
|
||||||
</ html >
|
</ html >
|
||||||
|
where
|
||||||
|
open HTML.Syntax
|
||||||
|
|
||||||
handle-request : Request → IO (List Byte)
|
handle-request : Request → IO (List Byte)
|
||||||
handle-request req =
|
handle-request req =
|
||||||
|
@ -85,7 +93,7 @@ handle-request req =
|
||||||
200-ok
|
200-ok
|
||||||
"text/html"
|
"text/html"
|
||||||
(UTF-8.encode-string (S.toList (
|
(UTF-8.encode-string (S.toList (
|
||||||
HTML.render-dom (Pages.render-index (req .target .query))
|
HTML.Render.render-element (Pages.render-index (req .target .query))
|
||||||
)))
|
)))
|
||||||
)
|
)
|
||||||
else pure 404-not-found
|
else pure 404-not-found
|
||||||
|
|
Loading…
Reference in New Issue
Block a user