Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ luarocks install mods
| Module | Description |
| -------------- | -------------------------------------------------------------- |
| [`is`] | Type predicates for Lua values and filesystem path kinds. |
| [`keyword`] | Lua keyword helpers for reserved-word checks. |
| [`List`] | Python-style list helpers for mapping, filtering, and slicing. |
| [`operator`] | Operator helpers as functions. |
| [`Set`] | Set operations and helpers for unique values. |
Expand All @@ -72,6 +73,7 @@ Thanks to these Lua ecosystem projects:
- [busted](https://github.com/lunarmodules/busted) for test framework support.

[`is`]: https://luamod.github.io/mods/modules/is
[`keyword`]: https://luamod.github.io/mods/modules/keyword
[`List`]: https://luamod.github.io/mods/modules/list
[`operator`]: https://luamod.github.io/mods/modules/operator
[`Set`]: https://luamod.github.io/mods/modules/set
Expand Down
1 change: 1 addition & 0 deletions docs/src/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ description: Overview of available Mods modules and their purpose.
| Module | Description |
| ----------------------------------- | -------------------------------------------------------------- |
| [`is`](/modules/is) | Type predicates for Lua values and filesystem path kinds. |
| [`keyword`](/modules/keyword) | Lua keyword helpers for reserved-word checks. |
| [`List`](/modules/list) | Python-style list helpers for mapping, filtering, and slicing. |
| [`operator`](/modules/operator) | Operator helpers as functions. |
| [`Set`](/modules/set) | Set operations and helpers for unique values. |
Expand Down
92 changes: 92 additions & 0 deletions docs/src/modules/keyword.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
description: Lua keyword helpers for reserved-word checks.
---

# `keyword`

Lua keyword helpers.

## Import

```lua
local kw = require("mods.keyword")
```

## Dependencies

- [`mods.List`] is used by `kwlist()`.
- [`mods.Set`] is used by `kwset()`.

> [!NOTE]
>
> These dependencies are lazy-loaded internally 💤, so requiring `mods.keyword`
> does not immediately load them.

## Quick Reference

| Function | Description |
| ------------------------------------------------------ | -------------------------------------------------- |
| [`iskeyword(s)`](#fn-iskeywords) | Return `true` when `s` is a reserved Lua keyword. |
| [`isidentifier(s)`](#fn-isidentifiers) | Return `true` for valid non-keyword identifiers. |
| [`kwlist()`](#fn-kwlist) | Return Lua keywords as a [`mods.List`] of strings. |
| [`kwset()`](#fn-kwset) | Return Lua keywords as a [`mods.Set`] of strings. |
| [`normalize_identifier(s)`](#fn-normalize_identifiers) | Normalize input to a safe identifier. |

## Functions

### `iskeyword(s)` {#fn-iskeywords}

Return `true` when `s` is a reserved Lua keyword.

> [!NOTE]
>
> `goto` is treated as a keyword on Lua 5.2+ and not on Lua 5.1/LuaJIT.

```lua
print(kw.iskeyword("function")) --> true
print(kw.iskeyword("hello")) --> false
```

### `kwlist()` {#fn-kwlist}

Return Lua keywords as a [`mods.List`] of strings.

```lua
local keywords = kw.kwlist()
print(keywords[1]) --> "and"
print(keywords[#keywords]) --> "while"
```

### `kwset()` {#fn-kwset}

Return Lua keywords as a [`mods.Set`] of strings.

```lua
local words = kw.kwset()
print(words["and"]) --> true
print(words["hello"]) --> nil
```

### `isidentifier(s)` {#fn-isidentifiers}

Return `true` when `s` is a valid non-keyword Lua identifier.

```lua
print(kw.isidentifier("hello_world")) --> true
print(kw.isidentifier("local")) --> false
```

### `normalize_identifier(s)` {#fn-normalize_identifiers}

Normalize input to a safe Lua identifier.

```lua
print(kw.normalize_identifier(" 2 bad-name ")) --> "_2_bad_name"
print(kw.normalize_identifier("local")) --> "local_"
print(kw.normalize_identifier("end")) --> "end_"
print(kw.normalize_identifier(" ")) --> "_"
print(kw.normalize_identifier(false)) --> "false_"
```

[`mods.List`]: /modules/list
[`mods.Set`]: /modules/set
16 changes: 0 additions & 16 deletions docs/src/modules/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,10 @@ local utils = require("mods.utils")

| Function | Description |
| -------------------------------------- | ----------------------------------------------------- |
| [`isidentifier(s)`](#fn-isidentifiers) | Checks if a string is a valid non-keyword identifier. |
| [`quote(v)`](#fn-quotev) | Smart-quotes a string for readable Lua-like output. |

## Functions

### `isidentifier(s)` {#fn-isidentifiers}

Returns `true` when `s` is a valid Lua identifier and not a reserved keyword.

```lua
print(utils.isidentifier("hello_world"))
-- true

print(utils.isidentifier("local"))
-- false

print(utils.isidentifier("2bad"))
-- false
```

### `quote(v)` {#fn-quotev}

Smart-quotes a string for readable Lua-like output.
Expand Down
3 changes: 2 additions & 1 deletion mods.rockspec.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ description = {
license = "MIT",
summary = "Pure Lua modules",
detailed = [[
Mods provides small, focused Lua modules: List, Set, is, operator, str, stringcase, tbl, template, and validate.
Mods provides small, focused Lua modules: List, Set, is, keyword, operator, str, stringcase, tbl, template, and validate.
]],
}

Expand All @@ -26,6 +26,7 @@ build = {
modules = {
["mods"] = "src/mods/init.lua",
["mods.is"] = "src/mods/is.lua",
["mods.keyword"] = "src/mods/keyword.lua",
["mods.List"] = "src/mods/List.lua",
["mods.operator"] = "src/mods/operator.lua",
["mods.Set"] = "src/mods/Set.lua",
Expand Down
149 changes: 149 additions & 0 deletions spec/keyword_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
local List = require("mods.List")
local Set = require("mods.Set")
local kw = require("mods.keyword")

local fmt = string.format
local is_lua51 = _VERSION == "Lua 5.1"

describe("mods.keyword", function()
local fn = function() end
local co = coroutine.create(fn)
-- stylua: ignore
local kwlist = List({
"and" , "break" , "do" , "else" , "elseif",
"end" , "false" , "for" , "function", "if" ,
"in" , "local" , "nil" , "not" , "or" ,
"repeat", "return", "then", "true" , "until" , "while"
})

if not is_lua51 then
kwlist:append("goto"):sort()
end

local kwset = kwlist:setify()
local tests

-------------------
--- iskeyword() ---
-------------------

local non_keywords = {
"_",
"",
"Function",
"goto1",
"hello",
"local_var",
"nil?",
"while_",
{},
123,
false,
}

for i = 1, #kwlist do
local input = kwlist[i]
it(fmt("iskeyword(%s) returns true", inspect(input)), function()
assert.is_true(kw.iskeyword(input))
end)
end

for i = 1, #non_keywords do
local input = non_keywords[i]
it(fmt("iskeyword(%s) returns false", inspect(input)), function()
assert.is_false(kw.iskeyword(input))
end)
end

----------------------
--- isidentifier() ---
----------------------

-- stylua: ignore
tests = {
{ "hello" , true },
{ "hello_world" , true },
{ "_name2" , true },
{ "goto" , is_lua51 },
{ "(var" , false },
{ "[var" , false },
{ "local" , false },
{ "function" , false },
{ "2bad" , false },
{ "bad-name" , false },
{ false , false },
}

for i = 1, #tests do
local input, expected = unpack(tests[i], 1, 2)
it(fmt("isidentifier(%s)", inspect(input)), function()
assert.are_equal(expected, kw.isidentifier(input))
end)
end

------------------------------
--- normalize_identifier() ---
------------------------------

-- stylua: ignore
tests = {
------input------|----expected----
{ " 2 bad-name " , "_2_bad_name" },
{ "local" , "local_" },
{ "" , "_" },
{ " " , "_" },
{ false , "false_" },
{ fn , "function_" },
{ {} , "table_" },
{ co , "thread_" },
}

for i = 1, #tests do
local input, expected = unpack(tests[i], 1, 2)
it(fmt("normalize_identifier(%s)", inspect(input)), function()
assert.are_equal(expected, kw.normalize_identifier(input))
end)
end

----------------
--- kwlist() ---
----------------

describe("kwlist()", function()
it("returns all keywords in order", function()
assert.are_same(kwlist, kw.kwlist())
end)

it("returns a mods.List instance", function()
local kw = kw.kwlist()
assert.are_equal(List, getmetatable(kw))
end)

it("returns a fresh copy on each call", function()
local l1 = kw.kwlist()
local l2 = kw.kwlist()
assert.are_not_equal(l1, l2)
end)
end)

---------------
--- kwset() ---
---------------

describe("kwset()", function()
it("returns all keywords", function()
assert.are_same(kwset, kw.kwset())
end)

it("returns a mods.Set instance", function()
local kw = kw.kwset()
assert.are_equal(Set, getmetatable(kw))
end)

it("returns a fresh copy on each call", function()
local s1 = kw.kwset()
local s2 = kw.kwset()
assert.are_not_equal(s1, s2)
end)
end)
end)
25 changes: 0 additions & 25 deletions spec/utils_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,6 @@ local utils = require("mods.utils")
describe("mods.utils", function()
local tests

--------------------
--- isidentifier ---
--------------------

-- stylua: ignore
tests = {
-----input---|-expected---
{ "_" , true },
{ "var" , true },
{ "var_2" , true },
{ "2var" , false },
{ "var)" , false },
{ "[var" , false },
{ "local" , false },
{ "function" , false },
{ "nil" , false },
}

for i = 1, #tests do
local input, expected = unpack(tests[i], 1, 2)
it(("isidentifier(%q)"):format(input), function()
assert.are_equal(expected, utils.isidentifier(input))
end)
end

-------------
--- quote ---
-------------
Expand Down
3 changes: 2 additions & 1 deletion src/mods/init.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local mods = {}

("is List operator Set str stringcase tbl template utils validate"):gsub("%S+", function(name)
([[is keyword List operator Set str stringcase
tbl template utils validate]]):gsub("%S+", function(name)
mods[name] = "mods." .. name
end)

Expand Down
Loading
Loading