diff --git a/Markdown.lua b/Markdown.luau
similarity index 57%
rename from Markdown.lua
rename to Markdown.luau
index a24cd85..b43f780 100644
--- a/Markdown.lua
+++ b/Markdown.luau
@@ -1,3 +1,5 @@
+--!strict
+
------------------------------------------------------------------------------------------------------------------------
-- Name: Markdown.lua
-- Version: 1.0 (1/17/2021)
@@ -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("&", "&"):gsub("<", "<"):gsub(">", ">"):gsub("\"", """):gsub("'", "'")
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
@@ -66,20 +56,20 @@ 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] = {"", ""},
[ModifierType.Italic] = {"", ""},
[ModifierType.Strike] = {"", ""},
@@ -87,25 +77,25 @@ local function richText(md)
}
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
@@ -118,7 +108,7 @@ end
-- Document Parser
------------------------------------------------------------------------------------------------------------------------
-local BlockType = {
+local BlockType: {[string]: number} = {
None = 0,
Paragraph = 1,
Heading = 2,
@@ -128,7 +118,7 @@ local BlockType = {
Quote = 6,
}
-local CombinedBlocks = {
+local CombinedBlocks: {[number]: boolean} = {
[BlockType.None] = true,
[BlockType.Paragraph] = true,
[BlockType.Code] = true,
@@ -136,69 +126,71 @@ local CombinedBlocks = {
[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 ()
@@ -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
@@ -237,27 +229,33 @@ 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)
@@ -265,10 +263,10 @@ local function blocks(md, markup)
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