import std/strutils
import utils/twtstr
type BracketState = enum
bsNone, bsInBracket
proc getId(line: openArray[char]): string =
result = ""
var i = 0
var bs = bsNone
while i < line.len:
case (let c = line[i]; c)
of AsciiAlphaNumeric, '-', '_', '.': result &= c.toLowerAscii()
of ' ': result &= '-'
of '[':
bs = bsInBracket
of ']':
if bs == bsInBracket:
if i + 1 < line.len and line[i + 1] == '(':
inc i
while i < line.len:
let c = line[i]
if c == '\\':
inc i
elif c == ')':
break
inc i
bs = bsNone
else: discard
inc i
type InlineFlag = enum
ifItalic, ifBold, ifDel
func startsWithScheme(s: string): bool =
for i, c in s:
if i > 0 and c == ':':
return true
if c notin AsciiAlphaNumeric:
break
false
type ParseInlineContext = object
i: int
bracketChars: string
bs: BracketState
bracketRef: bool
flags: set[InlineFlag]
proc parseInTag(ctx: var ParseInlineContext; line: openArray[char]) =
var buf = ""
var i = ctx.i + 1
while i < line.len:
let c = line[i]
if c == '>': # done
if buf.startsWithScheme(): # link
stdout.write("" & buf & "")
else: # tag
stdout.write('<' & buf & '>')
buf = ""
break
elif c == '<':
stdout.write('<' & buf)
buf = ""
dec i
break
else:
buf &= c
inc i
stdout.write(buf)
ctx.i = i
proc append(ctx: var ParseInlineContext; s: string) =
if ctx.bs == bsInBracket:
ctx.bracketChars &= s
else:
stdout.write(s)
proc append(ctx: var ParseInlineContext; c: char) =
if ctx.bs == bsInBracket:
ctx.bracketChars &= c
else:
stdout.write(c)
type CommentState = enum
csNone, csDash, csDashDash
proc parseComment(ctx: var ParseInlineContext; line: openArray[char]) =
var i = ctx.i
var cs = csNone
var buf = ""
while i < line.len:
let c = line[i]
if cs in {csNone, csDash} and c == '-':
inc cs
elif cs == csDashDash and c == '>':
buf &= '>'
break
else:
cs = csNone
buf &= c
inc i
ctx.append(buf)
ctx.i = i
proc parseCode(ctx: var ParseInlineContext; line: openArray[char]) =
let i = ctx.i + 1
let j = line.toOpenArray(i, line.high).find('`')
if j != -1:
ctx.append("")
ctx.append(line.toOpenArray(i, i + j - 1).htmlEscape())
ctx.append("
")
ctx.i = i + j
else:
ctx.append('`')
proc parseLinkDestination(url: var string; line: openArray[char]; i: int): int =
var i = i
var quote = false
var parens = 0
let sc = line[i]
if sc == '<':
inc i
while i < line.len:
let c = line[i]
if quote:
quote = false
elif sc == '<' and c == '>' or sc != '<' and c in AsciiWhitespace + {')'}:
break
elif c in {'<', '\n'} or c in Controls and sc != '<':
return -1
elif c == '\\':
quote = true
elif c == '(':
inc parens
url &= c
elif c == ')' and sc != '>':
if parens == 0:
break
dec parens
url &= c
else:
url &= c
inc i
if sc != '>' and parens != 0 or quote:
return -1
return line.skipBlanks(i)
proc parseTitle(title: var string; line: openArray[char]; i: int): int =
let ec = line[i]
var i = i + 1
var quote = false
while i < line.len:
let c = line[i]
if quote:
quote = false
elif c == '\\':
quote = true
elif c == ec:
inc i
break
else:
title &= c
inc i
return line.skipBlanks(i)
proc parseLink(ctx: var ParseInlineContext; line: openArray[char]) =
let i = ctx.i + 1
if i >= line.len or line[i] != '(':
#TODO reference links
stdout.write('[' & ctx.bracketChars & ']')
return
var url = ""
var j = url.parseLinkDestination(line, line.skipBlanks(i + 1))
var title = ""
if j != -1 and j < line.len and line[j] in {'(', '"', '\''}:
j = title.parseTitle(line, j)
if j == -1 or j >= line.len or line[j] != ')':
stdout.write('[' & ctx.bracketChars & ']')
else:
let url = url.htmlEscape()
stdout.write("")
stdout.write(ctx.bracketChars)
stdout.write("")
ctx.i = j
proc parseImageAlt(text: var string; line: openArray[char]; i: int): int =
var i = i
var brackets = 0
while i < line.len:
let c = line[i]
if c == '\\':
inc i
elif c == '<':
while i < line.len and line[i] != '>':
text &= c
inc i
elif c == '[':
inc brackets
text &= c
elif line[i] == ']':
if brackets == 0:
break
dec brackets
text &= c
else:
text &= c
inc i
return i
proc parseImage(ctx: var ParseInlineContext; line: openArray[char]) =
var text = ""
let i = text.parseImageAlt(line, ctx.i + 2)
if i == -1 or i + 1 >= line.len or line[i] != ']' or line[i + 1] != '(':
ctx.append("![")
return
var url = ""
var j = url.parseLinkDestination(line, line.skipBlanks(i + 2))
var title = ""
if j != -1 and j < line.len and line[j] in {'(', '"', '\''}:
j = title.parseTitle(line, j)
if j == -1 or j >= line.len or line[j] != ')':
ctx.append("![")
else:
ctx.append("")
ctx.i = j
proc appendToggle(ctx: var ParseInlineContext; f: InlineFlag; s, e: string) =
if f notin ctx.flags:
ctx.flags.incl(f)
ctx.append(s)
else:
ctx.flags.excl(f)
ctx.append(e)
proc parseInline(line: openArray[char]) =
var ctx = ParseInlineContext()
while ctx.i < line.len:
let c = line[ctx.i]
if c == '\\':
inc ctx.i
if ctx.i < line.len:
ctx.append(line[ctx.i])
elif (ctx.i > 0 and line[ctx.i - 1] notin AsciiWhitespace or
ctx.i + 1 < line.len and line[ctx.i + 1] notin AsciiWhitespace) and
(c == '*' or
c == '_' and
(ctx.i == 0 or line[ctx.i - 1] notin AsciiAlphaNumeric or
ctx.i + 1 >= line.len or
line[ctx.i + 1] notin AsciiAlphaNumeric + {'_'})):
if ctx.i + 1 < line.len and line[ctx.i + 1] == c:
ctx.appendToggle(ifBold, "", "")
inc ctx.i
else:
ctx.appendToggle(ifItalic, "", "")
elif c == '`':
ctx.parseCode(line)
elif c == '~' and ctx.i + 1 < line.len and line[ctx.i + 1] == '~':
ctx.appendToggle(ifDel, "", "")
inc ctx.i
elif c == '!' and ctx.i + 1 < line.len and line[ctx.i + 1] == '[':
ctx.parseImage(line)
elif c == '[':
if ctx.bs == bsInBracket:
stdout.write('[' & ctx.bracketChars)
ctx.bracketChars = ""
ctx.bs = bsInBracket
ctx.bracketRef = ctx.i + 1 < line.len and line[ctx.i + 1] == '^'
if ctx.bracketRef:
inc ctx.i
elif c == ']' and ctx.bs == bsInBracket:
if ctx.bracketRef:
let id = ctx.bracketChars.getId()
stdout.write("" & ctx.bracketChars & "")
else:
ctx.parseLink(line)
ctx.bracketChars = ""
ctx.bracketRef = false
ctx.bs = bsNone
elif c == '<':
ctx.parseInTag(line)
elif ctx.i + 4 < line.len and line.toOpenArray(ctx.i, ctx.i + 3) == "")
if i != -1:
stdout.write(line.substr(0, i + 2))
state.blockType = btNone
line.toOpenArray(i + 3, line.high).parseInline()
else:
stdout.write(line & '\n')
proc main() =
var line: string
var state = ParseState(listDepth: -1)
while state.reprocess or stdin.readLine(line):
state.reprocess = false
case state.blockType
of btNone: state.parseNone(line)
of btPre: state.parsePre(line)
of btTabPre: state.parseTabPre(line)
of btSpacePre: state.parseSpacePre(line)
of btBlockquote: state.parseBlockquote(line)
of btList: state.parseList(line)
of btPar: state.parsePar(line)
of btHTML: state.parseHTML(line)
of btHTMLPre: state.parseHTMLPre(line)
of btComment: state.parseComment(line)
state.blockData.parseInline()
main()