Skip to content

Commit

Permalink
added support self closing tag, use simpler decent parser AST
Browse files Browse the repository at this point in the history
  • Loading branch information
shahrul committed Oct 1, 2024
1 parent e3966be commit 5dde196
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 74 deletions.
21 changes: 20 additions & 1 deletion h.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,29 @@ setmetatable(_G, {
end
})

local function printTable(t, indent)
indent = indent or 0 -- Default to zero if no indent is provided
local tab = string.rep(" ", indent) -- Create indentation for readability

for key, value in pairs(t) do
if type(value) == "table" then
-- If the value is a table, recursively print it
print(tab .. tostring(key) .. ": ")
printTable(value, indent + 1) -- Increase indentation for nested tables
else
-- Print the key and value
print(tab .. tostring(key) .. ": " .. tostring(value))
end
end
end


local function h(element)
if type(element) ~= "table" then return element or "" end
if element.tag == nil then return element.children or "" end
local tkeys = {}
for k in pairs(element.atts) do table.insert(tkeys, k) end
table.sort(tkeys)
if #tkeys then table.sort(tkeys) end
local atts = ""
for _, k in ipairs(tkeys) do
local v = element.atts[k]
Expand Down
123 changes: 66 additions & 57 deletions luax.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,92 @@ local h = require('h')

local originalRequire = require

local function tokenize(input)
local function decentParserAST(input)
local pos = 1
local output = ""
local isNode
local isTag = 0
local isTextNode = 0

while pos <= #input do
local char = input:sub(pos, pos)

-- simple decent parser
-- opening tag
if char == "<" then
if input:sub(pos + 1, pos + 1) == "/" then
local tagName = input:match("</(%w+)>", pos)
isNode = nil
pos = pos + #tagName + 3 -- skip "</tag>"
output = output .. ")" -- close the Lua function call
else
local tagName = input:match("<(%w+)", pos)
if tagName then isNode = true end
pos = pos + #tagName + 1 -- skip "<tag>"
output = output .. tagName .. "({" -- opening the Lua function call

local tagEnd = input:find(">", pos)

if true then
local attributesString = input:sub(pos, tagEnd)
local attributes = {}
local attrPos = 1
while attrPos <= #attributesString do
local attrNameBracket, attrValueBracket, endPosBracket = attributesString:match(
'%s*(%w+)%s*=%s*{([^}]*)}%s*()', attrPos)
local attrName, attrValue, endPos = attributesString:match('%s*(%w+)%s*=%s*"([^\'"]*)"%s*()', attrPos)
if attrName then
table.insert(attributes, attrName .. ' = "' .. attrValue .. '"')
attrPos = endPos
pos = pos + #attrName + #attrValue + 3
elseif attrNameBracket then
table.insert(attributes, attrNameBracket .. ' = ' .. attrValueBracket)
attrPos = endPosBracket
pos = pos + #attrNameBracket + #attrValueBracket + 3
else
break
end
end
pos = pos + #attributes
output = output .. table.concat(attributes, ", ") -- add delimiter
local tagName = input:match("<(%w+)", pos)
local tagNameEnd = input:match("</(%w+)>", pos)
if isTag == 2 and tagName ~= nil then
-- children tag
output = output .. ", "
end
if tagName then
isTag = 1
output = output .. tagName .. "({"
pos = pos + #tagName + 1
elseif tagNameEnd then
isTag = 0
if isTextNode == 2 then
isTextNode = 0
output = output .. "\")"
else
output = output .. ")"
end
pos = pos + #tagNameEnd + 2
else
pos = pos + 1
end
elseif isNode and char == "{" then
-- handle content inside curly braces
local content = input:match("{(.-)}", pos)
output = output .. content:match("%s*(.*)%s*")
pos = pos + #content + 2 -- skip "{content}"
elseif isNode and char == "}" then
elseif char == ">" then
if isTag == 1 then
output = output .. " }"
isTag = 2
end
pos = pos + 1
elseif char == "/" then
-- self closing tag
isTag = 0
output = output .. " })"
pos = pos + 1
else
if isNode and char == ">" then
pos = pos + 1
output = output .. "}, " -- opening the Lua function call
local text = input:match("([^<]+)", pos)
local textEnd = input:find("<", pos)
local bracket = input:match("{(.-)}", pos)
if not bracket and text and pos < textEnd then
local str = text:match("^%s*(.-)%s*$")
output = output .. '"' .. str .. '"'
pos = pos + #text
local skip = false
if char and isTag == 2 then
isTextNode = 1
isTag = 3
output = output .. ", "
elseif isTag == 1 then
-- attributes
if char:match("%s") then
if output:sub(-1) ~= "{" and output:sub(-1) == "\"" then
output = output .. ","
elseif input:sub(pos -1, pos -1) == "}" then
output = output .. ","
end
skip = false
elseif char == "{" or char == "}" then
skip = true
end
else

end
if isTag ~= 0 then
if isTextNode == 1 and char == "{" or char == "}" then
skip = true
isTextNode = 3
elseif isTextNode == 1 then
isTextNode = 2
output = output .. "\""
end
end

if skip == false then
output = output .. char
pos = pos + 1
end
pos = pos + 1
end
end
return output
end

local function preprocessLuaFile(inputFile)
local inputCode = io.open(inputFile, "r"):read("*all")
local transformedCode = tokenize(inputCode)
local transformedCode = decentParserAST(inputCode)
return transformedCode
end

Expand Down
2 changes: 1 addition & 1 deletion test/element.luax
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
local attr = { class = "container", val = 'value' }
local attr = { class = "container", val = "value" }
local myElement = <div id="foo" bar="bar" d="1" class={attr.class} val={attr.val}>Hello, world!</div>
return myElement
5 changes: 2 additions & 3 deletions test/foo.luax
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
local foo = "foo"
local t = <div>{foo}</div>
return t
local foo = "foobar"
return <div children={foo}/>
20 changes: 15 additions & 5 deletions test/test_ast.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function getDir()
local function getDir()
local handle
local result
if os.getenv("OS") == "Windows_NT" then
Expand All @@ -20,16 +20,26 @@ package.path = package.path .. ";" .. getDir() .. "/?.lua"

local h = require('luax')

local foo = require('test.foo')
local div = require('test.1_div')

print(h(foo))
print(h(div))

local element = require('test.element')
local node_value = require('test.2_node_value')

print(element)
print(h(node_value))

-- local comment = require('test.3_comment')

-- print(h(comment))

local element = require('test.element')

print(h(element))

local varin = require('test.varin')

print(h(varin))

local foo = require('test.foo')

print(h(foo))
14 changes: 7 additions & 7 deletions test/test_spec.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function getDir()
function GetDir()
local handle
local result
if os.getenv("OS") == "Windows_NT" then
Expand All @@ -16,15 +16,15 @@ function getDir()
return result
end

package.path = package.path .. ";" .. getDir() .. "/?.lua"
package.path = package.path .. ";" .. GetDir() .. "/?.lua"

local h = require("luax")

describe("LuaX", function()
it("should return type function", function()
assert.is.equal("function", type(h))
end)
it("should return a HTML string with createElement DSL", function()
it("should return a HTML string with createElement", function()
local el = div(
{ class = "container" },
p({ class = "title" }, "Hello, world!"),
Expand All @@ -35,17 +35,17 @@ describe("LuaX", function()
h(el))
end)

it("should return a HTML string when given JSX like syntax", function()
it("should return a HTML string when given JSX like syntax 1", function()
local el = require("test.element")
assert.is.equal('<div bar="bar" class="container" d="1" id="foo" val="value">Hello, world!</div>', h(el))
end)

it("should return a HTML string when given JSX like syntax", function()
it("should return a HTML string when given JSX like syntax 2", function()
local el = require("test.foo")
assert.is.equal('<div>foo</div>', h(el))
assert.is.equal('<div>foobar</div>', h(el))
end)

it("should return a HTML string when given JSX like syntax", function()
it("should return a HTML string when given JSX like syntax 3", function()
local el = require("test.varin")
assert.is.equal(
'<div class="container" id="div_1"><p class="title" id="p_2" style="border: 1px solid red;">Hello, world!</p></div>',
Expand Down

0 comments on commit 5dde196

Please sign in to comment.