Skip to content

Commit

Permalink
fix table parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
shahrul committed Oct 12, 2024
1 parent 63289ea commit 64624d8
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 86 deletions.
111 changes: 111 additions & 0 deletions .rockspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package = "luax"
version = "1.0.1-1"

source = {
url = "https://github.com/syarul/luax/archive/refs/tags/v1.0.1.tar.gz",
dir = "luax-1.0.1"
}
description = {
summary = "HTML parse in Lua",
detailed = [[## LuaX
Decent parse for HTML, so you don't have to write as concatenates string, in short a React JSX implementation in LUA.
<a href="https://luarocks.org/modules/syarul/luax" rel="nofollow"><img alt="Luarocks Package" src="https://img.shields.io/badge/Luarocks-1.0.1-blue.svg" style="max-width:100%;"></a>
[![Lua CI](https://github.com/syarul/luax/actions/workflows/lua.yml/badge.svg)](https://github.com/syarul/luax/actions/workflows/lua.yml)
### Usage
```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))
```
You'll get,
```html
<div class="container"><p class="title">Hello, world!</p><span style="color: red;">This is a span</span></div>
```
### Usage with JSX like syntax (HTML inside Lua)
first create a `*.luax` file, then import the `LuaX` pragma `h`
```lua
-- el.luax
local class = "container"
local el = <div id="hello" class={class}>Hello, world!</div>
return el
```
import it on to the main
```lua
local h = require('luax')
local el = require('el')
print(h(el))
```
You'll get,
```html
<div class="container" id="hello">Hello, world!</div>
```
Sample usage with list/table structure
```lua
local function map(a, fcn)
local b = {}
for _, v in ipairs(a) do
table.insert(b, fcn(v))
end
return b
end
local filters = {
{ url = "#/", name = "All", selected = true },
{ url = "#/active", name = "Active", selected = false },
{ url = "#/completed", name = "Completed", selected = false },
}
local content = table.concat(map(filters, function(filter)
return h(<li>
<a
class={filter.selected and 'selected' or nil}
href={filter.url}
_="on click add .selected to me"
>
{filter.name}
</a>
</li>)
end), '\n')
return <ul class="filters" _="on load set $filter to me">
{content}
</ul>
```
See the test folder to see more usage cases.
> Inspired by https://bvisness.me/luax/.
]],
homepage = "https://github.com/syarul/luax",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
luax = "luax.lua"
}
}
45 changes: 39 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## LuaX
Decent parse for HTML, so you don't have to write as concatenates string, in short a React JSX implementation in LUA.

<a href="https://luarocks.org/modules/syarul/luax" rel="nofollow"><img alt="Luarocks Package" src="https://img.shields.io/badge/Luarocks-1.0.0-blue.svg" style="max-width:100%;"></a>
<a href="https://luarocks.org/modules/syarul/luax" rel="nofollow"><img alt="Luarocks Package" src="https://img.shields.io/badge/Luarocks-1.0.1-blue.svg" style="max-width:100%;"></a>
[![Lua CI](https://github.com/syarul/luax/actions/workflows/lua.yml/badge.svg)](https://github.com/syarul/luax/actions/workflows/lua.yml)

### Usage
Expand All @@ -24,11 +24,9 @@ You'll get,
<div class="container"><p class="title">Hello, world!</p><span style="color: red;">This is a span</span></div>
```

### Usage with JSX like syntax
### Usage with JSX like syntax (HTML inside Lua)

This require parsing it to the createElement.

first create a luax file
first create a `*.luax` file, then import the `LuaX` pragma `h`

```lua
-- el.luax
Expand All @@ -39,7 +37,6 @@ return el

import it on to the main
```lua

local h = require('luax')

local el = require('el')
Expand All @@ -53,4 +50,40 @@ You'll get,
<div class="container" id="hello">Hello, world!</div>
```

Sample usage with list/table structure

```lua
local function map(a, fcn)
local b = {}
for _, v in ipairs(a) do
table.insert(b, fcn(v))
end
return b
end

local filters = {
{ url = "#/", name = "All", selected = true },
{ url = "#/active", name = "Active", selected = false },
{ url = "#/completed", name = "Completed", selected = false },
}

local content = table.concat(map(filters, function(filter)
return h(<li>
<a
class={filter.selected and 'selected' or nil}
href={filter.url}
_="on click add .selected to me"
>
{filter.name}
</a>
</li>)
end), '\n')

return <ul class="filters" _="on load set $filter to me">
{content}
</ul>
```

See the test folder to see more usage cases.

> Inspired by https://bvisness.me/luax/.
126 changes: 78 additions & 48 deletions luax.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,75 @@ local h = require('h')

local originalRequire = require

local function resetTable(store, data)
for key, value in pairs(data) do
store[key] = value
end
end

local function decentParserAST(input)
local output = ""
local pos = 1
local deepNode = 0
local deepString = false
local deepStringApos = false
local isTag = false
local textNode = false
local textNodeStart = false
local var = false
local s = {
deepNode = 0,
deepString = false,
deepStringApos = false,
isTag = false,
textNode = false,
textNodeStart = false
}
local resetStore = {}
resetTable(resetStore, s)
local varStore = {}

while pos <= #input do
local tok = input:sub(pos, pos)
-- simple decent parser
-- escape " ' encapsulation
-- opening tag
if tok == "<" and not deepString and not deepStringApos then
if tok == "<" and not s.deepString and not s.deepStringApos then
local nextSpacingPos = input:find("%s", pos)
local tagRange = input:sub(pos, nextSpacingPos)
local tagName = tagRange:match("<(%w+)", 0)
local tagNameEnd = tagRange:match("</(%w+)>", 0)
if tagName then deepNode = deepNode + 1 end
if tagName then s.deepNode = s.deepNode + 1 end
pos = pos + 1

if tagName and not deepString then
isTag = true
textNode = false
if textNodeStart then
textNodeStart = not textNodeStart
if tagName and not s.deepString then
s.isTag = true
s.textNode = false
if s.textNodeStart then
s.textNodeStart = not s.textNodeStart
output = output .. "]]"
end
if deepNode > 1 then
output = output .. ", " .. tagName .. "({"
if s.deepNode > 1 then
-- handle internal return function
local ret = input:sub(pos-8, pos):gsub("%s\r\n", ""):sub(0, 6) == "return"
if ret then
output = output .. tagName .. "({"
else
output = output .. ", " .. tagName .. "({"
end
else
output = output .. tagName .. "({"
end
pos = pos + #tagName
elseif tagNameEnd then
deepNode = deepNode - 1
if isTag and not textNode then
isTag = not isTag
s.deepNode = s.deepNode - 1
if s.isTag and not s.textNode then
s.isTag = not s.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
elseif s.isTag and s.textNode then
output = output .. "]])"
else
if textNodeStart then
textNodeStart = not textNodeStart
if s.textNodeStart then
s.textNodeStart = not s.textNodeStart
output = output .. "]])"
else
output = output .. ")"
Expand All @@ -64,67 +81,78 @@ local function decentParserAST(input)
output = output .. tok
pos = pos + 1
end
elseif tok == '"' and deepNode > 0 then
deepString = not deepString
elseif tok == '"' and s.deepNode > 0 then
s.deepString = not s.deepString
output = output .. tok
pos = pos + 1
elseif tok == "'" and deepNode > 0 then
deepStringApos = not deepStringApos
elseif tok == "'" and s.deepNode > 0 then
s.deepStringApos = not s.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
elseif tok == ">" and s.deepNode > 0 and not s.deepString and not s.deepStringApos then
if not s.textNode and s.isTag and input:sub(pos - 1, pos - 1) ~= "/" then
s.isTag = not s.isTag
s.textNode = not s.textNode
output = output .. "}"
else
isTag = not isTag
deepNode = deepNode - 1
s.isTag = not s.isTag
s.deepNode = s.deepNode - 1
output = output .. "})"
end
pos = pos + 1
elseif tok == "/" and input:sub(pos + 1, pos + 1) == ">" and not deepString and not deepStringApos then
deepNode = deepNode - 1
elseif tok == "/" and input:sub(pos + 1, pos + 1) == ">" and not s.deepString and not s.deepStringApos then
s.deepNode = s.deepNode - 1
output = output .. "})"
pos = pos + 2
elseif tok == "{" and deepNode > 0 and not deepString and not deepStringApos then
elseif tok == "{" and s.deepNode > 0 and not s.deepString and not s.deepStringApos then
var = not var
if not isTag then
output = output .. ","
if var then
-- snapshot currentState
resetTable(varStore, s)
-- reset currentState
resetTable(s, resetStore)
end
local trail = input:sub(pos - 20, pos-1):gsub("[%s\r\n]", "")
if trail:sub(#trail) == ">" or trail:sub(#trail) == "}" then
output = output .. ", "
end
pos = pos + 1
elseif tok == "}" and deepNode > 0 and not deepString and not deepStringApos then
elseif tok == "}" and var then
var = not var
if not var then
-- restore currentState from snapshot
resetTable(s, varStore)
end
pos = pos + 1
elseif deepNode > 0 and not deepString and not deepStringApos then
elseif s.deepNode > 0 and not s.deepString and not s.deepStringApos then
if tok:match("%s") then
if not var and isTag and output:sub(-1) ~= "{" and output:sub(-1) == "\"" or
isTag and input:sub(pos - 1, pos - 1) == "}" then
if not var and s.isTag and output:sub(-1) ~= "{" and output:sub(-1) == "\"" or
s.isTag and input:sub(pos - 1, pos - 1) == "}" then
output = output .. ","
end
end

if textNode and not textNodeStart then
if s.textNode and not s.textNodeStart then
local subNode = input:match("^%s*<(%w+)", pos) or input:match("^%s*{(%w+)", pos)
if not isTag and not subNode and not var then
textNodeStart = not textNodeStart
if not s.isTag and not subNode and not var then
s.textNodeStart = not s.textNodeStart
output = output .. ", [["
end
end

output = output .. tok
pos = pos + 1
else
if not textNode and not deepString and not deepStringApos then
textNode = not textNode
if textNode then
if not s.textNode and not s.deepString and not s.deepStringApos then
s.textNode = not s.textNode
if s.textNode then
local subNode = input:match("%s*<(%w+)", pos)
local trail = input:sub(pos - 10, pos):gsub("[%s\r\n]", "")
if isTag and not subNode then
if s.isTag and not subNode then
if trail:sub(#trail, #trail) ~= ">" then
output = output .. "}, [["
end
elseif deepNode > 0 and not subNode then
elseif s.deepNode > 0 and not subNode then
output = output .. "[["
end
end
Expand All @@ -141,7 +169,9 @@ local function preprocessLuaFile(inputFile)
local transformedCode = decentParserAST(inputCode)
-- this to add [] bracket to table attributes
transformedCode = transformedCode:gsub('([%w%-_]+)%=([^%s]+)', '["%1"]=%2')
-- print("===================")
-- print(transformedCode)
-- print("===================")
return transformedCode
end

Expand Down
Loading

0 comments on commit 64624d8

Please sign in to comment.