philomena/lib/textile/lexer.ex

258 lines
5.2 KiB
Elixir

defmodule Textile.Lexer do
import NimbleParsec
import Textile.Helpers
import Textile.MarkupLexer
import Textile.UrlLexer
# Structural tags
# Literals enclosed via [== ==]
# Will never contain any markup
bracketed_literal =
ignore(string("[=="))
|> repeat(lookahead_not(string("==]")) |> utf8_char([]))
|> ignore(string("==]"))
unbracketed_literal =
ignore(string("=="))
|> repeat(lookahead_not(string("==")) |> utf8_char([]))
|> ignore(string("=="))
literal =
choice([
bracketed_literal,
unbracketed_literal
])
|> reduce({List, :to_string, []})
|> unwrap_and_tag(:literal)
blockquote_cite =
lookahead_not(string("\""))
|> choice([
literal |> reduce(:unwrap),
utf8_char([])
])
|> repeat()
# Blockquote opening tag with cite: [bq="the author"]
# Cite can contain bracketed literals or text
blockquote_open_cite =
ignore(string("[bq=\""))
|> concat(blockquote_cite)
|> ignore(string("\"]"))
|> reduce({List, :to_string, []})
|> unwrap_and_tag(:blockquote_open_cite)
# Blockquote opening tag
blockquote_open =
string("[bq]")
|> unwrap_and_tag(:blockquote_open)
# Blockquote closing tag
blockquote_close =
string("[/bq]")
|> unwrap_and_tag(:blockquote_close)
# Spoiler open tag
spoiler_open =
string("[spoiler]")
|> unwrap_and_tag(:spoiler_open)
# Spoiler close tag
spoiler_close =
string("[/spoiler]")
|> unwrap_and_tag(:spoiler_close)
# Images
image_url_with_title =
url_ending_in(string("("))
|> unwrap_and_tag(:image_url)
|> concat(
ignore(string("("))
|> repeat(utf8_char(not: ?)))
|> ignore(string(")"))
|> lookahead(string("!"))
|> reduce({List, :to_string, []})
|> unwrap_and_tag(:image_title)
)
image_url_without_title =
url_ending_in(string("!"))
|> unwrap_and_tag(:image_url)
image_url =
choice([
image_url_with_title,
image_url_without_title
])
bracketed_image_with_link =
ignore(string("[!"))
|> concat(image_url)
|> ignore(string("!:"))
|> concat(
url_ending_in(string("]"))
|> unwrap_and_tag(:image_link_url)
)
bracketed_image_without_link =
ignore(string("[!"))
|> concat(image_url)
|> ignore(string("!]"))
image_with_link =
ignore(string("!"))
|> concat(image_url)
|> ignore(string("!:"))
|> concat(
url_ending_in(space())
|> unwrap_and_tag(:image_link_url)
)
image_without_link =
ignore(string("!"))
|> concat(image_url)
|> ignore(string("!"))
image =
choice([
bracketed_image_with_link,
bracketed_image_without_link,
image_with_link,
image_without_link
])
# Links
{link_markup_start, link_markup_element} = markup_ending_in(string("\""))
link_url_stop =
choice([
string("*"),
string("@"),
string("^"),
string("~"),
string(".") |> concat(choice([space(), eos()])),
string("!") |> concat(choice([space(), eos()])),
string(",") |> concat(choice([space(), eos()])),
string("_") |> concat(choice([space(), eos()])),
string("?") |> concat(choice([space(), eos()])),
string(";") |> concat(choice([space(), eos()])),
space(),
eos()
])
link_contents_start =
choice([
image,
spoiler_open,
spoiler_close,
blockquote_open,
blockquote_open_cite,
blockquote_close,
literal,
link_markup_start
])
link_contents_element =
choice([
image,
spoiler_open,
spoiler_close,
blockquote_open,
blockquote_open_cite,
blockquote_close,
literal,
link_markup_element
])
link_contents =
optional(link_contents_start)
|> repeat(link_contents_element)
bracketed_link_end =
string("\":")
|> unwrap_and_tag(:link_end)
|> concat(
url_ending_in(string("]"))
|> ignore(string("]"))
|> unwrap_and_tag(:link_url)
)
bracketed_link =
string("[\"")
|> unwrap_and_tag(:link_start)
|> concat(link_contents)
|> concat(bracketed_link_end)
unbracketed_link_end =
string("\":")
|> unwrap_and_tag(:link_end)
|> concat(
url_ending_in(link_url_stop)
|> unwrap_and_tag(:link_url)
)
unbracketed_link =
string("\"")
|> unwrap_and_tag(:link_start)
|> concat(link_contents)
|> concat(unbracketed_link_end)
link =
choice([
bracketed_link,
unbracketed_link
])
# Textile
markup_ends =
choice([
spoiler_close,
blockquote_close,
eos()
])
{markup_start, markup_element} = markup_ending_in(markup_ends)
textile_default =
choice([
literal,
blockquote_open_cite |> optional(markup_start),
blockquote_open |> optional(markup_start),
blockquote_close,
spoiler_open |> optional(markup_start),
spoiler_close,
link,
image
])
textile_main =
choice([
textile_default,
markup_element
])
textile_start =
choice([
textile_default,
markup_start
])
textile =
optional(textile_start)
|> repeat(textile_main)
|> eos()
defparsec :lex, textile
end