Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to luau #1

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 101 additions & 103 deletions Markdown.lua → Markdown.luau
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
--!strict

------------------------------------------------------------------------------------------------------------------------
-- Name: Markdown.lua
-- Version: 1.0 (1/17/2021)
Expand All @@ -15,49 +17,37 @@ local Markdown = {}
-- Text Parser
------------------------------------------------------------------------------------------------------------------------

local InlineType = {
local InlineType: {[string]: number} = {
Text = 0,
Ref = 1,
}

local ModifierType = {
local ModifierType: {[string]: number} = {
Bold = 0,
Italic = 1,
Strike = 2,
Code = 3,
}

local function sanitize(s)
local function sanitize(s: string): string
return s:gsub("&", "&amp;"):gsub("<", "&lt;"):gsub(">", "&gt;"):gsub("\"", "&quot;"):gsub("'", "&apos;")
end

local function characters(s)
return s:gmatch(".")
end

local function last(t)
return t[#t]
end

local function getModifiers(stack)
local modifiers = {}
for _, modifierType in pairs(stack) do
modifiers[modifierType] = true
end
return modifiers
end
type tokenIterator = { flag: boolean, text: string }

local function parseModifierTokens(md)
local index = 1
return function ()
local text, newIndex = md:match("^([^%*_~`]+)()", index)
if text then
index = newIndex
return false, text
local function parseModifierTokens(md: string): () -> tokenIterator?
local index: number = 1
return function (): tokenIterator?
local text: string?, textIndex: number? = md:match("^([^%*_~`]+)()", index)
if text and textIndex then
index = textIndex
return {flag=false, text=text}
elseif index <= md:len() then
local text, newIndex = md:match("^(%" .. md:sub(index, index) .. "+)()", index)
index = newIndex
return true, text
local modifier: string, modifierIndex: any = md:match("^(%" .. md:sub(index, index) .. "+)()", index)
index = modifierIndex :: number
return {flag=true, text=modifier}
else
return nil
end
end
end
Expand All @@ -66,46 +56,46 @@ local function parseText(md)

end

local richTextLookup = {
local richTextLookup: {[string]: number} = {
["*"] = ModifierType.Bold,
["_"] = ModifierType.Italic,
["~"] = ModifierType.Strike,
["`"] = ModifierType.Code,
}

local function getRichTextModifierType(symbols)
local function getRichTextModifierType(symbols: string): number
return richTextLookup[symbols:sub(1, 1)]
end

local function richText(md)
local function richText(md: string): string
md = sanitize(md)
local tags = {
local tags: {[number]: {[number]: string}} = {
[ModifierType.Bold] = {"<b>", "</b>"},
[ModifierType.Italic] = {"<i>", "</i>"},
[ModifierType.Strike] = {"<s>", "</s>"},
[ModifierType.Code] = {"<font face=\"RobotoMono\">", "</font>"},
}
local state = {}
local output = ""
for token, text in parseModifierTokens(md) do
if token then
local modifierType = getRichTextModifierType(text)
for token in parseModifierTokens(md) do
if token.flag then
local modifierType = getRichTextModifierType(token.text)
if state[ModifierType.Code] and modifierType ~= ModifierType.Code then
output = output .. text
output = output .. token.text
continue
end
local symbolState = state[modifierType]
if not symbolState then
output = output .. tags[modifierType][1]
state[modifierType] = text
elseif text == symbolState then
state[modifierType] = token.text
elseif token.text == symbolState then
output = output .. tags[modifierType][2]
state[modifierType] = nil
else
output = output .. text
output = output .. token.text
end
else
output = output .. text
output = output .. token.text
end
end
for modifierType in pairs(state) do
Expand All @@ -118,7 +108,7 @@ end
-- Document Parser
------------------------------------------------------------------------------------------------------------------------

local BlockType = {
local BlockType: {[string]: number} = {
None = 0,
Paragraph = 1,
Heading = 2,
Expand All @@ -128,77 +118,79 @@ local BlockType = {
Quote = 6,
}

local CombinedBlocks = {
local CombinedBlocks: {[number]: boolean} = {
[BlockType.None] = true,
[BlockType.Paragraph] = true,
[BlockType.Code] = true,
[BlockType.List] = true,
[BlockType.Quote] = true,
}

local function cleanup(s)
local function cleanup(s: string): string
return s:gsub("\t", " ")
end

local function getTextWithIndentation(line)
local indent, text = line:match("^%s*()(.*)")
return text, math.floor(indent / 2)
local function getTextWithIndentation(line: string): (string, number)
local indent: number?, text: string? = line:match("^%s*()(.*)")
return text :: string, math.floor(indent :: number / 2)
end

-- Iterator: Iterates the string line-by-line
local function lines(s)
local function lines(s: string): () -> string | nil
return (s .. "\n"):gmatch("(.-)\n")
end

-- Iterator: Categorize each line and allows iteration
local function blockLines(md)

local function blockLines(md: string): () -> (number | nil, string | nil)
local blockType = BlockType.None
local nextLine = lines(md)
local function it()
local function it(): (number | nil, string | nil)
local line = nextLine()
if not line then
return
end
-- Code
if blockType == BlockType.Code then
if line:match("^```") then
blockType = BlockType.None
if line then
-- Code
if blockType == BlockType.Code then
if line:match("^```") then
blockType = BlockType.None
end
return BlockType.Code, line
end
return BlockType.Code, line
end
-- Blank line
if line:match("^%s*$") then
return BlockType.None, ""
end
-- Ruler
if line:match("^%-%-%-+") or line:match("^===+") then
return BlockType.Ruler, ""
end
-- Heading
if line:match("^#") then
return BlockType.Heading, line
end
-- Code
if line:match("^%s*```") then
blockType = BlockType.Code
return blockType, line
end
-- Quote
if line:match("^%s*>") then
return BlockType.Quote, line
end
-- List
if line:match("^%s*%-%s+") or line:match("^%s*%*%s+") or line:match("^%s*[%u%d]+%.%s+") or line:match("^%s*%+%s+") then
return BlockType.List, line
-- Blank line
if line:match("^%s*$") then
return BlockType.None, ""
end
-- Ruler
if line:match("^%-%-%-+") or line:match("^===+") then
return BlockType.Ruler, ""
end
-- Heading
if line:match("^#") then
return BlockType.Heading, line
end
-- Code
if line:match("^%s*```") then
blockType = BlockType.Code
return blockType, line
end
-- Quote
if line:match("^%s*>") then
return BlockType.Quote, line
end
-- List
if line:match("^%s*%-%s+") or line:match("^%s*%*%s+") or line:match("^%s*[%u%d]+%.%s+") or line:match("^%s*%+%s+") then
return BlockType.List, line
end
-- Paragraph
return BlockType.Paragraph, line -- should take into account indentation of first-line
else
return nil
end
-- Paragraph
return BlockType.Paragraph, line -- should take into account indentation of first-line
end
return it
end

-- Iterator: Joins lines of the same type into a single element
local function textBlocks(md)
local function textBlocks(md: string): () -> (number, string)
local it = blockLines(md)
local lastBlockType, lastLine = it()
return function ()
Expand All @@ -223,9 +215,9 @@ local function textBlocks(md)
end

-- Iterator: Transforms raw blocks into sections with data
local function blocks(md, markup)
local function blocks(md: string, markup: (string) -> string)
local nextTextBlock = textBlocks(md)
local function it()
local function it(): (number, {[string]: any})
local blockType, blockText = nextTextBlock()
if blockType == BlockType.None then
return it() -- skip this block type
Expand All @@ -237,38 +229,44 @@ local function blocks(md, markup)
if blockType == BlockType.Paragraph then
block.Text = markup(text)
elseif blockType == BlockType.Heading then
local level, text = blockText:match("^#+()%s*(.*)")
block.Level, block.Text = level - 1, markup(text)
local level: number?, heading: string? = blockText:match("^#+()%s*(.*)")
if level and heading then
block.Level, block.Text = level - 1, markup(heading)
end
elseif blockType == BlockType.Code then
local syntax, code = text:match("^```(.-)\n(.*)\n```$")
block.Syntax, block.Code = syntax, syntax == "raw" and code or sanitize(code)
local syntax: string?, code: string? = text:match("^```(.-)\n(.*)\n```$")
if syntax and code then
block.Syntax, block.Code = syntax, syntax == "raw" and code or sanitize(code)
end
elseif blockType == BlockType.List then
local lines = blockText:split("\n")
local lines: {[number]: any} = blockText:split("\n")
for i, line in ipairs(lines) do
local text, indent = getTextWithIndentation(line)
local symbol, text = text:match("^(.-)%s+(.*)")
lines[i] = {
Level = indent,
Text = markup(text),
Symbol = symbol,
}
local lineText: string, lineIndent: number = getTextWithIndentation(line)
local symbol: string?, item: string? = lineText:match("^(.-)%s+(.*)")
if symbol and item then
lines[i] = {
Level = lineIndent,
Text = markup(item),
Symbol = symbol,
}
end
end
block.Lines = lines
elseif blockType == BlockType.Quote then
local lines = blockText:split("\n")
local lines: {[number]: string} = blockText:split("\n")
for i = 1, #lines do
lines[i] = lines[i]:match("^%s*>%s*(.*)")
lines[i] = lines[i]:match("^%s*>%s*(.*)") :: string
end
local rawText = table.concat(lines, "\n")
block.RawText, block.Iterator = rawText, blocks(rawText, markup)
end
end
return blockType, block
end
return it
return it
end

local function parseDocument(md, inlineParser)
local function parseDocument(md: string, inlineParser: any)
return blocks(cleanup(md), inlineParser or richText)
end

Expand Down