/ 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}