198 lines
8.8 KiB
Fennel
198 lines
8.8 KiB
Fennel
(local fennel (require :fennel))
|
|
(fn _G.pp [x] (print (fennel.view x)))
|
|
|
|
(fn general-contents-fn [pattern typ str current]
|
|
(if (string.match str pattern)
|
|
(let [(start end contents) (string.find str pattern current)]
|
|
(values
|
|
{:type typ
|
|
:text contents}
|
|
(+ 1 end)))))
|
|
|
|
(local contents-grammar
|
|
{:match-first "[%*%_%~%[%>%`]"
|
|
:Italics (partial general-contents-fn "_(.-)_" "Italics")
|
|
:Bold (partial general-contents-fn "%*(.-)%*" "Bold")
|
|
:Strikethrough (partial general-contents-fn "%~(.-)%~" "Strikethrough")
|
|
:Monospace (partial general-contents-fn "%`(.-)%`" "Monospace")
|
|
:Link (fn link-fn [str current]
|
|
(let [patterns {:In-Link "%[%[(.-)%]%]"
|
|
:Named-In-Link "%[%[(.-)%|(.-)%]%]"
|
|
:Out-Link "%[(.-)%]%((.-)%)"
|
|
:Footnote "%[(.-)%]%_"}
|
|
(find-char _) (string.find str "%[")
|
|
next-char (string.sub str (+ 1 find-char) (+ 1 find-char))]
|
|
; (_G.pp (.. "find-char " find-char))
|
|
; (_G.pp (.. "next-char " next-char))
|
|
(if (= next-char "[")
|
|
(if (not (= nil (string.match str (. patterns :Named-In-Link) current)))
|
|
(let [(start end filename title) (string.find str (. patterns :Named-In-Link) current)]
|
|
(values {:type "Link"
|
|
:title title
|
|
:class "internal"
|
|
:destination filename}
|
|
end))
|
|
(let [(start end filename) (string.find str (. patterns :In-Link) current)]
|
|
(values {:type "Link"
|
|
:title filename
|
|
:class "internal"
|
|
:destination filename}
|
|
end)))
|
|
; If it's not a [[ type link
|
|
; We need to check the next char after the ]
|
|
; if it's _ it's a footnote
|
|
; if it's ) it's an out-link
|
|
(let [(_ close-bracket) (string.find str "%]" current)
|
|
next-close-char (string.sub str (+ 1 close-bracket) (+ 1 close-bracket))]
|
|
; (_G.pp "we think it's not a in-link")
|
|
; (_G.pp (.. "next-close-char " next-close-char))
|
|
(match next-close-char
|
|
"(" (let [(start end title destination) (string.find str (. patterns :Out-Link) current)]
|
|
(values {:type "Link"
|
|
:class "external"
|
|
:title title
|
|
:destination destination}
|
|
end))
|
|
"_" (let [(start end footnote-number) (string.find str (. patterns :Footnote) current)]
|
|
; (_G.pp "In footnote")
|
|
; (_G.pp (.. "end " end))
|
|
(values {:type "Footnote-Link"
|
|
:class "footnote"
|
|
:number footnote-number}
|
|
end))
|
|
_ (values nil next-close-char))))))}) ; Could not parse type of link, just skip it
|
|
; :Spoiler
|
|
; :Censor})
|
|
|
|
(fn contents-lexer [contents ?grammar]
|
|
(let [grammar (or ?grammar contents-grammar)]
|
|
(var current 1)
|
|
(var lexed [])
|
|
(while (and (not (= nil current)) (> (length contents) current))
|
|
(let [found (string.find contents (. grammar :match-first) current)]
|
|
; (_G.pp (.. "current " current))
|
|
; (_G.pp (.. "found " (tostring found)))
|
|
(if (not (= nil found))
|
|
(let [char (string.sub contents found found)]
|
|
; (_G.pp (.. "char " char))
|
|
(if (> found 1)
|
|
(table.insert lexed {:type "raw-text"
|
|
:text (string.sub contents current (- found 1))}))
|
|
(match char
|
|
; This would be better if we had a reverse lookup table
|
|
"_" (let [(block new-end) ((. grammar :Italics) contents current)]
|
|
(table.insert lexed block)
|
|
(set current new-end)) ; Italics
|
|
"*" (let [(block new-end) ((. grammar :Bold) contents current)]
|
|
(table.insert lexed block)
|
|
(set current new-end)) ; Bold
|
|
"~" (let [(block new-end) ((. grammar :Strikethrough) contents current)]
|
|
(table.insert lexed block)
|
|
(set current new-end)) ; Strikethrough
|
|
"`" (let [(block new-end) ((. grammar :Monospace) contents current)]
|
|
(table.insert lexed block)
|
|
(set current new-end)); Monospace
|
|
"[" (let [(block new-end) ((. grammar :Link) contents current)]
|
|
(table.insert lexed block)
|
|
(set current new-end)))) ; Links
|
|
(do
|
|
(table.insert lexed {:type "raw-text"
|
|
:text (string.sub contents current (length contents))})
|
|
(set current (length contents)))))) ; No decoration at all
|
|
(if (> (length lexed) 0) ; If lexed is nil, the only case is we had a single \n paragraph?
|
|
lexed
|
|
[{:type "raw-text"
|
|
:text "\n"}])))
|
|
; ">" (let [(block new-end) ((. grammar :Spoiler) contents current)]
|
|
; (table.insert lexed block)
|
|
; (set current new-end))))
|
|
|
|
; (string.match "*This* other thing" "%*(.*)%*")
|
|
; (general-contents-fn "%*(.-)%*" "Bold" "*This* other thing" 1)
|
|
; ((. contents-grammar :Bold) "*This* other thing" 1)
|
|
; (contents-lexer "*This* is a paragraph.")
|
|
|
|
; (contents-lexer "Some footnote string [#]_")
|
|
|
|
(fn list-rule [str ?regex]
|
|
(let [regex (or ?regex "(%s*)%-%s*(.-)\n")]
|
|
(var list-elems [])
|
|
(each [indents line (string.gmatch str regex)]
|
|
(table.insert list-elems {:indent-level (length indents)
|
|
:contents (contents-lexer line)}))
|
|
{:type "List"
|
|
:elements list-elems}))
|
|
|
|
(local inline-grammar
|
|
{:Metadata { :rule (fn metadata-rule [str ?regex]
|
|
(let [regex (or ?regex "%%(.-)%s(.-)\n")
|
|
(key val) (string.match str regex)]
|
|
{:type "Metadata"
|
|
: key
|
|
: val
|
|
:is-metadata true}))}
|
|
:Code { :rule (fn code-rule [str ?regex]
|
|
(let [regex (or ?regex "{{{(.-)\n(.-)}}}\n")
|
|
(hint code) (string.match str regex)]
|
|
{:type "Code"
|
|
:codehint hint
|
|
:contents code}))}
|
|
:Header { :rule (fn header-rule [str ?regex]
|
|
(let [regex (or ?regex "(=*)%s*(.-)%s*=*\n")
|
|
(header-size contents) (string.match str regex)]
|
|
{:type "Header"
|
|
:size (- 4 (length header-size))
|
|
:contents (contents-lexer contents)}))}
|
|
:Image {:rule (fn image-rule [str ?regex]
|
|
(let [regex (or ?regex "%!%[(.-)%]%((.-)%)\n")
|
|
(alt link) (string.match str regex)]
|
|
{:type "Image"
|
|
:contents (contents-lexer alt)
|
|
: link}))}
|
|
:List {:rule list-rule}
|
|
:Footnote {:rule (fn footnote-rule [str ?regex]
|
|
(let [regex (or ?regex "::%s-\n%s*(.-)\n::\n")
|
|
list (string.match str regex)]
|
|
{:type "Footnote"
|
|
:elements (. (list-rule list) :elements)}))}
|
|
; :is-metadata false}))}
|
|
:Tag {:rule (fn tag-rule [str ?regex]
|
|
(let [regex (or ?regex "%:(.-):")]
|
|
(var tags [])
|
|
(each [tag (string.gmatch str regex)]
|
|
(table.insert tags tag))
|
|
{:type "Tag"
|
|
:tags tags
|
|
:is-metadata true}))}
|
|
:Quote {:rule (fn quote-rule [str ?regex]
|
|
(var total "")
|
|
(let [regex (or ?regex "%s*>%s*(.-)\n")]
|
|
(each [m (string.gmatch str regex)]
|
|
(set total (.. total m "\n"))))
|
|
{:type "Quote"
|
|
:contents (contents-lexer total)})}
|
|
:Paragraph {:rule (fn paragraph-rule [str]
|
|
{:type "Paragraph"
|
|
:contents (contents-lexer str)})}})
|
|
|
|
(fn inline-lexer [blocks grammar]
|
|
(var lexed [])
|
|
(var section [])
|
|
(each [_ block (ipairs blocks)]
|
|
; (_G.pp block)
|
|
(let [rule-set (. grammar (. block :type))]
|
|
(if (= "Section" (. block :type))
|
|
(do ; Each section gets it's own list
|
|
(table.insert lexed section)
|
|
(set section []))
|
|
(table.insert section ((. rule-set :rule) (. block :block))))))
|
|
; Final section
|
|
(if (> (length section) 0)
|
|
(table.insert lexed section))
|
|
lexed)
|
|
|
|
{: inline-grammar
|
|
: inline-lexer
|
|
: contents-lexer
|
|
: contents-grammar}
|