From 662790669efcea63f2e558aeb3936ca9e4080dd1 Mon Sep 17 00:00:00 2001 From: shahrul Date: Fri, 11 Oct 2024 00:16:40 +0800 Subject: [PATCH] handle deep nodes, linebreaks --- h.lua | 2 - luax.lua | 164 +++++++++++++++++++++++++++------------------ test/props.luax | 5 ++ test/test.lua | 31 --------- test/test.luax | 30 +++++++++ test/test_ast.lua | 28 +++++--- test/test_spec.lua | 37 +++++++--- 7 files changed, 183 insertions(+), 114 deletions(-) create mode 100644 test/props.luax delete mode 100644 test/test.lua create mode 100644 test/test.luax diff --git a/h.lua b/h.lua index 9689d60..6892408 100644 --- a/h.lua +++ b/h.lua @@ -23,8 +23,6 @@ function printTable(t, indent) end end - - local voidTags = { "area", "base", "basefont", "br", "col", "frame", "hr", "img", "input", "link", diff --git a/luax.lua b/luax.lua index 00589fb..5951a10 100644 --- a/luax.lua +++ b/luax.lua @@ -3,96 +3,129 @@ local h = require('h') local originalRequire = require local function decentParserAST(input) - local pos = 1 local output = "" - local isTag = 0 - local isTextNode = 0 + local pos = 1 local deepNode = 0 + local deepString = false + local deepStringApos = false + local isTag = false + local textNode = false + local textNodeStart = false + local var = false while pos <= #input do - local char = input:sub(pos, pos) + local tok = input:sub(pos, pos) -- simple decent parser + -- escape " ' encapsulation -- opening tag - if char == "<" then - local tagName = input:match("<(%w+)", pos) - local tagNameEnd = input:match("", pos) - if isTag == 2 and tagName ~= nil then - -- children tag - output = output .. ", " - end - if tagName then - deepNode = deepNode + 1 - isTag = 1 - output = output .. tagName .. "({" - pos = pos + #tagName + 1 - elseif tagNameEnd then - deepNode = deepNode - 1 - if deepNode == 0 then - isTag = 0 + if tok == "<" and not deepString and not deepStringApos then + local nextSpacingPos = input:find('%s',pos) + local tagRange = input:sub(pos, nextSpacingPos) + local tagName = tagRange:match("<(%w+)", 0) + local tagNameEnd = tagRange:match("", 0) + if tagName then deepNode = deepNode + 1 end + if tagNameEnd then deepNode = deepNode - 1 end + pos = pos + 1 + + if tagName and not deepString then + isTag = true + textNode = false + if deepNode > 1 then + output = output .. ", " .. tagName .. "({" + else + output = output .. tagName .. "({" end - if isTextNode == 2 then - isTextNode = 0 + local step = 1 + -- enclose attributes if it empty + if tagRange:sub(#tagRange-1, #tagRange):gsub("[\r\n]", ""):match("^%s*(.-)$") == '>' then step = 0 end + pos = pos + #tagName + step + elseif tagNameEnd then + if isTag and not textNode then + isTag = not isTag + local trail = input:sub(0, pos-2):gsub("[%s\r\n]", "") + if trail:sub(#trail-1, #trail-1) == '/' then + output = output .. ")" + else + output = output .. "})" + end + elseif isTag and textNode then output = output .. "]])" else - output = output .. ")" + if textNodeStart then + textNodeStart = not textNodeStart + output = output .. "]])" + else + output = output .. ")" + end end pos = pos + #tagNameEnd + 2 else + output = output .. tok pos = pos + 1 end - elseif char == ">" then - if isTag == 1 then - output = output .. " }" - isTag = 2 + elseif tok == '"' and deepNode > 0 then + deepString = not deepString + output = output .. tok + pos = pos + 1 + elseif tok == "'" and deepNode > 0 then + deepStringApos = not deepStringApos + output = output .. tok + pos = pos + 1 + elseif tok == ">" and deepNode > 0 and not deepString and not deepStringApos then + if not textNode and isTag and input:sub(pos-1, pos-1) ~= "/" then + isTag = not isTag + textNode = not textNode + output = output .. '}' + else + isTag = not isTag + -- textNode = not textNode + output = output .. '})' end pos = pos + 1 - elseif char == "/" then + elseif tok == "/" and input:sub(pos+1, pos+1) == '>' and not deepString and not deepStringApos then deepNode = deepNode - 1 - -- self closing tag - if deepNode == 0 then - isTag = 0 + output = output .. '})' + pos = pos + 2 + elseif tok == '{' and deepNode > 0 and not deepString and not deepStringApos then + var = not var + if not isTag then + output = output .. ',' end - output = output .. " })" pos = pos + 1 - else - 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 + elseif tok == '}' and deepNode > 0 and not deepString and not deepStringApos then + var = not var + pos = pos + 1 + elseif deepNode > 0 and not deepString and not deepStringApos then + if tok:match("%s") then + if isTag and output:sub(-1) ~= "{" and output:sub(-1) == "\"" or + isTag and input:sub(pos -1, pos -1) == "}" then + output = output .. "," end end - if isTag ~= 0 then - -- add bracket to all attributes key - if isTextNode == 1 and char == "{" or char == "}" then - skip = true - isTextNode = 3 - elseif isTextNode == 1 then - isTextNode = 2 - if char ~= '\n' then - output = output .. "[[" - end + if textNode and not textNodeStart then + local subNode = input:match("%s*<(%w+)", pos) + if not isTag and not subNode and not var then + textNodeStart = not textNodeStart + output = output .. ", [[" end end - if skip == false then - output = output .. char - end - if char:match("%=") then - output = output:gsub('([%w%-_]+)%=', '["%1"]=') + output = output .. tok + pos = pos + 1 + else + if not textNode and not deepString and not deepStringApos then + textNode = not textNode + if textNode then + local subNode = input:match("%s*<(%w+)", pos) + if isTag and not subNode then + output = output .. "}, [[" + elseif deepNode > 0 and not subNode then + output = output .. "[[" + end + end end + output = output .. tok pos = pos + 1 end end @@ -102,6 +135,9 @@ end local function preprocessLuaFile(inputFile) local inputCode = io.open(inputFile, "r"):read("*all") local transformedCode = decentParserAST(inputCode) + -- this to add [] bracket to table attributes + transformedCode = transformedCode:gsub('([%w%-_]+)%=([^%s]+)', '["%1"]=%2') + -- print(transformedCode) return transformedCode end diff --git a/test/props.luax b/test/props.luax new file mode 100644 index 0000000..36d8b46 --- /dev/null +++ b/test/props.luax @@ -0,0 +1,5 @@ +return
+ test +
\ No newline at end of file diff --git a/test/test.lua b/test/test.lua deleted file mode 100644 index da74c6c..0000000 --- a/test/test.lua +++ /dev/null @@ -1,31 +0,0 @@ -function getDir() - local handle - local result - - if os.getenv("OS") == "Windows_NT" then - handle = io.popen("cd") - else - handle = io.popen("pwd") - end - - if handle then - result = handle:read("*a"):gsub("%s+", "") - handle:close() - else - result = "Failed to get directory" - end - - return result -end - -package.path = package.path .. ";" .. getDir() .. "/?.lua" - -local h = require('h') - -local el = div( - { class = "container" }, - p({ class = "title" }, "Hello, world!"), - span({ style = "color: red;" }, "This is a span") -) - -print(h(el)) diff --git a/test/test.luax b/test/test.luax new file mode 100644 index 0000000..af5ccd6 --- /dev/null +++ b/test/test.luax @@ -0,0 +1,30 @@ +local id = '1' +local class = "test" +local val = "todo A" +local val2 = "todo A Label" +local val3 = "todo A Value" +local val4 = "install Destroy" +local val5 = "install TodoDblclick" +return
  • +
    + {val} + +
    + {val3} +
  • \ No newline at end of file diff --git a/test/test_ast.lua b/test/test_ast.lua index f697f99..11e5a00 100644 --- a/test/test_ast.lua +++ b/test/test_ast.lua @@ -22,36 +22,46 @@ local h = require('luax') local div = require('test.1_div') -print(h(div)) +h(div) local node_value = require('test.2_node_value') -print(h(node_value)) +h(node_value) local element = require('test.element') -print(h(element)) +h(element) local varin = require('test.varin') -print(h(varin)) +h(varin) local foo = require('test.foo') -print(h(foo)) +h(foo) local content = require('test.content') -print(h(content)) +h(content) local input = require('test.input') -print(h(input)) +h(input) local input_with_con = require('test.input_with_con') -print(h(input_with_con)) +h(input_with_con) + +local props = require('test.props') + +h(props) local linebreak = require('test.line_break') -print(h(linebreak)) +h(linebreak) +print("========================") + +local test = require('test.test') + +h(test) + diff --git a/test/test_spec.lua b/test/test_spec.lua index 345fbd6..d0fa4ea 100644 --- a/test/test_spec.lua +++ b/test/test_spec.lua @@ -35,23 +35,31 @@ describe("LuaX", function() h(el)) end) + it("should return a HTML string when given JSX like syntax", function() + local el = require("test.1_div") + assert.is.equal('
    ', h(el)) + end) + + it("should return a HTML string when given JSX like syntax", function() + local el = require("test.2_node_value") + assert.is.equal('
    xxxx
    ', h(el)) + end) + it("should return a HTML string when given JSX like syntax", function() local el = require("test.element") assert.is.equal('
    Hello, world!
    ', h(el)) end) + it("should return a HTML string when given JSX like syntax with nested node", function() + local el = require("test.varin") + assert.is.equal('

    Hello, world!

    ', h(el)) + end) + it("should return a HTML string when given children prop", function() local el = require("test.foo") assert.is.equal('
    foobar
    ', h(el)) end) - it("should return a HTML string when given JSX like syntax with nested node", function() - local el = require("test.varin") - assert.is.equal( - '

    Hello, world!

    ', - h(el)) - end) - it("should return a HTML string when given attributes with special characters", function() local el = require("test.content") assert.is.equal( @@ -73,10 +81,23 @@ describe("LuaX", function() h(el)) end) + it("should return a HTML string when given JSX like syntax", function() + local el = require("test.props") + assert.is.equal([[
    test +
    ]], h(el)) + end) + it("should return a HTML string with multi breakline", function() local el = require("test.line_break") assert.is.equal( - '

    foobar!

    ', + '

    foobar!

    ', + h(el)) + end) + + it("should return a HTML string with deep node tree", function() + local el = require("test.test") + assert.is.equal( + '
  • todo A
    todo A Value
  • ', h(el)) end) end)