/ inline.fnl
inline.fnl
1 (local fennel (require :fennel)) 2 (fn _G.pp [x] (print (fennel.view x))) 3 4 (fn general-contents-fn [pattern typ str current] 5 (if (string.match str pattern) 6 (let [(start end contents) (string.find str pattern current)] 7 (values 8 {:type typ 9 :text contents} 10 (+ 1 end))))) 11 12 (local contents-grammar 13 {:match-first "[%*%_%~%[%>%`]" 14 :Italics (partial general-contents-fn "_(.-)_" "Italics") 15 :Bold (partial general-contents-fn "%*(.-)%*" "Bold") 16 :Strikethrough (partial general-contents-fn "%~(.-)%~" "Strikethrough") 17 :Monospace (partial general-contents-fn "%`(.-)%`" "Monospace") 18 :Link (fn link-fn [str current] 19 (let [patterns {:In-Link "%[%[(.-)%]%]" 20 :Named-In-Link "%[%[(.-)%|(.-)%]%]" 21 :Out-Link "%[(.-)%]%((.-)%)" 22 :Footnote "%[(.-)%]%_"} 23 (find-char _) (string.find str "%[") 24 next-char (string.sub str (+ 1 find-char) (+ 1 find-char))] 25 ; (_G.pp (.. "find-char " find-char)) 26 ; (_G.pp (.. "next-char " next-char)) 27 (if (= next-char "[") 28 (if (not (= nil (string.match str (. patterns :Named-In-Link) current))) 29 (let [(start end filename title) (string.find str (. patterns :Named-In-Link) current)] 30 (values {:type "Link" 31 :title title 32 :class "internal" 33 :destination filename} 34 end)) 35 (let [(start end filename) (string.find str (. patterns :In-Link) current)] 36 (values {:type "Link" 37 :title filename 38 :class "internal" 39 :destination filename} 40 end))) 41 ; If it's not a [[ type link 42 ; We need to check the next char after the ] 43 ; if it's _ it's a footnote 44 ; if it's ) it's an out-link 45 (let [(_ close-bracket) (string.find str "%]" current) 46 next-close-char (string.sub str (+ 1 close-bracket) (+ 1 close-bracket))] 47 ; (_G.pp "we think it's not a in-link") 48 ; (_G.pp (.. "next-close-char " next-close-char)) 49 (match next-close-char 50 "(" (let [(start end title destination) (string.find str (. patterns :Out-Link) current)] 51 (values {:type "Link" 52 :class "external" 53 :title title 54 :destination destination} 55 end)) 56 "_" (let [(start end footnote-number) (string.find str (. patterns :Footnote) current)] 57 ; (_G.pp "In footnote") 58 ; (_G.pp (.. "end " end)) 59 (values {:type "Footnote-Link" 60 :class "footnote" 61 :number footnote-number} 62 end)) 63 _ (values nil next-close-char))))))}) ; Could not parse type of link, just skip it 64 ; :Spoiler 65 ; :Censor}) 66 67 (fn contents-lexer [contents ?grammar] 68 (let [grammar (or ?grammar contents-grammar)] 69 (var current 1) 70 (var lexed []) 71 (while (and (not (= nil current)) (> (length contents) current)) 72 (let [found (string.find contents (. grammar :match-first) current)] 73 ; (_G.pp (.. "current " current)) 74 ; (_G.pp (.. "found " (tostring found))) 75 (if (not (= nil found)) 76 (let [char (string.sub contents found found)] 77 ; (_G.pp (.. "char " char)) 78 (if (> found 1) 79 (table.insert lexed {:type "raw-text" 80 :text (string.sub contents current (- found 1))})) 81 (match char 82 ; This would be better if we had a reverse lookup table 83 "_" (let [(block new-end) ((. grammar :Italics) contents current)] 84 (table.insert lexed block) 85 (set current new-end)) ; Italics 86 "*" (let [(block new-end) ((. grammar :Bold) contents current)] 87 (table.insert lexed block) 88 (set current new-end)) ; Bold 89 "~" (let [(block new-end) ((. grammar :Strikethrough) contents current)] 90 (table.insert lexed block) 91 (set current new-end)) ; Strikethrough 92 "`" (let [(block new-end) ((. grammar :Monospace) contents current)] 93 (table.insert lexed block) 94 (set current new-end)); Monospace 95 "[" (let [(block new-end) ((. grammar :Link) contents current)] 96 (table.insert lexed block) 97 (set current new-end)))) ; Links 98 (do 99 (table.insert lexed {:type "raw-text" 100 :text (string.sub contents current (length contents))}) 101 (set current (length contents)))))) ; No decoration at all 102 (if (> (length lexed) 0) ; If lexed is nil, the only case is we had a single \n paragraph? 103 lexed 104 [{:type "raw-text" 105 :text "\n"}]))) 106 ; ">" (let [(block new-end) ((. grammar :Spoiler) contents current)] 107 ; (table.insert lexed block) 108 ; (set current new-end)))) 109 110 ; (string.match "*This* other thing" "%*(.*)%*") 111 ; (general-contents-fn "%*(.-)%*" "Bold" "*This* other thing" 1) 112 ; ((. contents-grammar :Bold) "*This* other thing" 1) 113 ; (contents-lexer "*This* is a paragraph.") 114 115 ; (contents-lexer "Some footnote string [#]_") 116 117 (fn list-rule [str ?regex] 118 (let [regex (or ?regex "(%s*)%-%s*(.-)\n")] 119 (var list-elems []) 120 (each [indents line (string.gmatch str regex)] 121 (table.insert list-elems {:indent-level (length indents) 122 :contents (contents-lexer line)})) 123 {:type "List" 124 :elements list-elems})) 125 126 (local inline-grammar 127 {:Metadata { :rule (fn metadata-rule [str ?regex] 128 (let [regex (or ?regex "%%(.-)%s(.-)\n") 129 (key val) (string.match str regex)] 130 {:type "Metadata" 131 : key 132 : val 133 :is-metadata true}))} 134 :Code { :rule (fn code-rule [str ?regex] 135 (let [regex (or ?regex "{{{(.-)\n(.-)}}}\n") 136 (hint code) (string.match str regex)] 137 {:type "Code" 138 :codehint hint 139 :contents code}))} 140 :Header { :rule (fn header-rule [str ?regex] 141 (let [regex (or ?regex "(=*)%s*(.-)%s*=*\n") 142 (header-size contents) (string.match str regex)] 143 {:type "Header" 144 :size (- 4 (length header-size)) 145 :contents (contents-lexer contents)}))} 146 :Image {:rule (fn image-rule [str ?regex] 147 (let [regex (or ?regex "%!%[(.-)%]%((.-)%)\n") 148 (alt link) (string.match str regex)] 149 {:type "Image" 150 :contents (contents-lexer alt) 151 : link}))} 152 :List {:rule list-rule} 153 :Footnote {:rule (fn footnote-rule [str ?regex] 154 (let [regex (or ?regex "::%s-\n%s*(.-)\n::\n") 155 list (string.match str regex)] 156 {:type "Footnote" 157 :elements (. (list-rule list) :elements)}))} 158 ; :is-metadata false}))} 159 :Tag {:rule (fn tag-rule [str ?regex] 160 (let [regex (or ?regex "%:(.-):")] 161 (var tags []) 162 (each [tag (string.gmatch str regex)] 163 (table.insert tags tag)) 164 {:type "Tag" 165 :tags tags 166 :is-metadata true}))} 167 :Quote {:rule (fn quote-rule [str ?regex] 168 (var total "") 169 (let [regex (or ?regex "%s*>%s*(.-)\n")] 170 (each [m (string.gmatch str regex)] 171 (set total (.. total m "\n")))) 172 {:type "Quote" 173 :contents (contents-lexer total)})} 174 :Paragraph {:rule (fn paragraph-rule [str] 175 {:type "Paragraph" 176 :contents (contents-lexer str)})}}) 177 178 (fn inline-lexer [blocks grammar] 179 (var lexed []) 180 (var section []) 181 (each [_ block (ipairs blocks)] 182 ; (_G.pp block) 183 (let [rule-set (. grammar (. block :type))] 184 (if (= "Section" (. block :type)) 185 (do ; Each section gets it's own list 186 (table.insert lexed section) 187 (set section [])) 188 (table.insert section ((. rule-set :rule) (. block :block)))))) 189 ; Final section 190 (if (> (length section) 0) 191 (table.insert lexed section)) 192 lexed) 193 194 {: inline-grammar 195 : inline-lexer 196 : contents-lexer 197 : contents-grammar}