md2gmi/preproc.go

156 lines
3.3 KiB
Go
Raw Normal View History

2021-07-03 18:58:03 +03:00
package main
2021-07-04 15:31:45 +03:00
import (
"bytes"
"regexp"
)
2021-07-03 18:58:03 +03:00
// state function
type stateFn func(*fsm, []byte) stateFn
// state machine
type fsm struct {
2021-07-04 08:26:30 +03:00
state stateFn
2021-07-04 15:31:45 +03:00
i int
out chan WorkItem
2021-07-03 19:18:57 +03:00
2021-07-04 08:26:30 +03:00
// combining multiple input lines
2021-07-04 15:31:45 +03:00
blockBuffer []byte
sendBuffer []byte
2021-07-04 07:31:51 +03:00
// if we have a termination rule to abide, e.g. implied code fences
2021-07-03 19:18:57 +03:00
pending []byte
2021-07-03 18:58:03 +03:00
}
2021-07-04 09:16:55 +03:00
func NewPreproc() *fsm {
2021-07-04 08:26:30 +03:00
return &fsm{}
2021-07-03 18:58:03 +03:00
}
2021-07-04 15:31:45 +03:00
func (m *fsm) Process(in chan WorkItem) chan WorkItem {
m.out = make(chan WorkItem)
2021-07-04 08:26:30 +03:00
go func() {
for m.state = normal; m.state != nil; {
b, ok := <-in
if !ok {
2021-07-04 15:31:45 +03:00
m.blockFlush()
m.sync()
2021-07-04 08:26:30 +03:00
close(m.out)
m.state = nil
continue
}
2021-07-04 15:31:45 +03:00
m.state = m.state(m, b.Payload())
m.sync()
2021-07-03 18:58:03 +03:00
}
2021-07-04 08:26:30 +03:00
}()
return m.out
2021-07-03 18:58:03 +03:00
}
2021-07-04 15:31:45 +03:00
func (m *fsm) sync() {
if len(m.sendBuffer) > 0 {
m.sendBuffer = append(m.sendBuffer, '\n')
m.out <- New(m.i, m.sendBuffer)
m.sendBuffer = m.sendBuffer[:0]
m.i += 1
}
}
func (m *fsm) blockFlush() {
// blockBuffer to sendbuffer
m.sendBuffer = append(m.sendBuffer, m.blockBuffer...)
m.blockBuffer = m.blockBuffer[:0]
2021-07-03 19:18:57 +03:00
if len(m.pending) > 0 {
2021-07-04 15:31:45 +03:00
m.sendBuffer = append(m.sendBuffer, m.pending...)
m.sendBuffer = append(m.sendBuffer, '\n')
2021-07-03 19:18:57 +03:00
m.pending = m.pending[:0]
}
}
2021-07-04 15:31:45 +03:00
func triggerBreak(data []byte) bool {
return len(data) == 0 || data[len(data)-1] == '.'
}
2021-07-04 15:31:45 +03:00
func isTerminated(data []byte) bool {
return len(data) > 0 && data[len(data)-1] != '.'
}
2021-07-04 15:31:45 +03:00
func handleList(data []byte) ([]byte, bool) {
re := regexp.MustCompile(`^([ ]*[-*^]{1,1})[^*-]`)
2021-07-04 15:31:45 +03:00
sub := re.FindSubmatch(data)
// if lists, collapse to single level
if len(sub) > 1 {
return bytes.Replace(data, sub[1], []byte("-"), 1), true
}
return data, false
}
func isFence(data []byte) bool {
return len(data) >= 3 && string(data[0:3]) == "```"
}
func needsFence(data []byte) bool {
return len(data) >= 4 && string(data[0:4]) == " "
}
2021-07-03 19:18:57 +03:00
func normal(m *fsm, data []byte) stateFn {
2021-07-04 15:31:45 +03:00
if data, isList := handleList(data); isList {
m.blockBuffer = append(data, '\n')
m.blockFlush()
2021-07-03 19:18:57 +03:00
return normal
}
if isFence(data) {
2021-07-04 15:31:45 +03:00
m.blockBuffer = append(data, '\n')
2021-07-03 19:18:57 +03:00
return fence
}
if needsFence(data) {
2021-07-04 15:31:45 +03:00
m.blockBuffer = append(m.blockBuffer, []byte("```\n")...)
m.blockBuffer = append(m.blockBuffer, append(data[4:], '\n')...)
2021-07-04 10:50:08 +03:00
m.pending = []byte("```\n")
return toFence
2021-07-03 19:18:57 +03:00
}
2021-07-04 15:31:45 +03:00
if isTerminated(data) {
m.blockBuffer = append(m.blockBuffer, data...)
m.blockBuffer = append(m.blockBuffer, ' ')
2021-07-03 19:18:57 +03:00
return paragraph
}
2021-07-03 18:58:03 +03:00
// TODO
// collapse lists
2021-07-04 15:31:45 +03:00
m.blockBuffer = append(m.blockBuffer, append(data, '\n')...)
m.blockFlush()
2021-07-03 19:18:57 +03:00
return normal
}
func fence(m *fsm, data []byte) stateFn {
2021-07-04 15:31:45 +03:00
m.blockBuffer = append(m.blockBuffer, append(data, '\n')...)
// second fence returns to normal
if isFence(data) {
2021-07-04 15:31:45 +03:00
m.blockFlush()
2021-07-03 19:18:57 +03:00
return normal
}
return fence
}
func toFence(m *fsm, data []byte) stateFn {
if needsFence(data) {
2021-07-04 15:31:45 +03:00
m.blockBuffer = append(m.blockBuffer, append(data[4:], '\n')...)
return toFence
2021-07-03 19:18:57 +03:00
}
2021-07-04 15:31:45 +03:00
m.blockFlush()
m.blockBuffer = append(m.blockBuffer, append(data, '\n')...)
2021-07-03 19:18:57 +03:00
return normal
}
func paragraph(m *fsm, data []byte) stateFn {
if triggerBreak(data) {
2021-07-04 15:31:45 +03:00
m.blockBuffer = append(m.blockBuffer, data...)
m.blockBuffer = bytes.TrimSpace(m.blockBuffer)
m.blockBuffer = append(m.blockBuffer, '\n')
m.blockFlush()
2021-07-03 19:18:57 +03:00
return normal
}
2021-07-04 15:31:45 +03:00
m.blockBuffer = append(m.blockBuffer, data...)
m.blockBuffer = append(m.blockBuffer, []byte(" ")...)
2021-07-03 19:18:57 +03:00
return paragraph
2021-07-03 18:58:03 +03:00
}