diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..70fff33
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,6 @@
+## Update 1.1.0
+
+- Add string split operator `/`
+- Add function union operator `+`
+- Fix Queue data structure
+- Add lots of LDoc documentation
\ No newline at end of file
diff --git a/README.md b/README.md
index b44b726..2415b4f 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,17 @@ print(-"abcdefg") --> gfedcba
-- repeat
print("xyz" * 4) --> xyzxyzxyzxyz
+-- split
+luay.util.repr("foo.bar.baz.luay" / ".") --> {"foo", "bar", "baz", "luay"}
+
+-- character indexing
+local str = "abc"
+print(str[2]) --> b
+
+-- substrings
+local str = "hello world"
+print(str[{1;5}]) --> hello
+
-- iteration
for char in ~"hello world" do
io.write(char) --> hello world
@@ -92,7 +103,7 @@ end
## Object Oriented
-Object oriented programming is made easy in Luay using a set of functions to create classes. Since Luay is only an embedded version of Lua, changing the grammar could cause more problems than just incompatibility with Main Methods. Thus, we have these functions in Luay: `class(name: string) -> Class`, `extend(class: Class, super: instanceof Class) -> void`, `constructor(class: Class, body?: function) -> ClassInstance`, and `namespace(name: string) -> ((body: table) -> {alias = (name: string) -> void})`. Here's each one of them in use, to show you the syntax:
+Object oriented programming is made easy in Luay using a set of functions to create classes. Since Luay is only an embedded version of Lua, changing the grammar could cause more problems than just incompatibility with Main Methods. Thus, we have these functions in Luay: `class(name: string) -> Class`, `extend(class: Class, super: instanceof Class) -> void`, `constructor(class: Class, body?: function) -> ClassInstance`, `namespace(name: string) -> (body: table) -> {alias = (name: string) -> void}`, and `singleton(name: string)`. Here's each one of them in use, to show you the syntax:
1. Single Class
```lua
@@ -173,6 +184,7 @@ end
3. Classes with Static and Regular Methods
```lua
using(luay.std)
+using(luay.util)
Array = class "Array" do
function Array.new()
@@ -187,7 +199,7 @@ Array = class "Array" do
arr:Add(v)
end
return arr
- end
+ end
function Array:Add(value)
table.insert(self.cache, value)
@@ -211,6 +223,41 @@ arr:Remove(2)
repr(arr)--> {"foo", "baz", "luay"}
```
+4. Namespace Binding
+```lua
+...
+
+namespace "MyStuff" {
+ Animal = Animal;
+ Dog = Dog;
+ Array = Array;
+}
+
+local arr = MyStuff.Array()
+arr:Add("foo")
+repr(arr) --> {"foo"}
+```
+
+## Function Unions
+
+Using the `luay.util` namespace, you can utilize the `+` operator to create a union of two or more functions. Obviously you can't use the operator on the normal `function` type, as that would require magic. However, you can easily create a new `Function` using `luay::util::Function`. Here's an example that's not so practical, but is certainly cool:
+```lua
+using(luay.util)
+
+function main()
+ local half = Function(function(x)
+ return x / 2
+ end)
+
+ local double = Function(function(x)
+ return x * 2
+ end)
+
+ local halfAndDouble = half + double
+ print(halfAndDouble(10)) --> 5 20
+end
+```
+
## Lambdas
Yes, you read that right. Lambdas are a shorthand way of writing an anonymous function that represents data. For example, say I have a list of numbers. I want to double each value in that list. Normally, you could do it like this in standard Luay:
diff --git a/bin/luay.exe b/bin/luay.exe
index 2a63f5a..cf30d42 100644
Binary files a/bin/luay.exe and b/bin/luay.exe differ
diff --git a/examples/html_generator/main.lua b/examples/html_generator/main.lua
new file mode 100644
index 0000000..c226bdf
--- /dev/null
+++ b/examples/html_generator/main.lua
@@ -0,0 +1,50 @@
+using(luay.util)
+
+function main()
+ local line = HTML.Newline
+
+ HTML:BeginBlock()
+
+ HTML:BeginBlock()
+ local headContent =
+ line() +
+ HTML:StylesheetLink("styles.css") +
+ line()
+ HTML:EndBlock()
+
+ local head = line() + HTML:Head(headContent)
+
+ HTML:BeginBlock()
+
+ HTML:BeginBlock()
+ local formContent =
+ line() +
+ HTML:Input {'type="button"', 'value="Open my Other Document in New Window"', 'onclick="openWindow()"'} +
+ line()
+ HTML:EndBlock()
+
+ local bodyContent =
+ line() +
+ HTML:H1("My Document") +
+ line() +
+ HTML:A("https://document.com", "My Other Document") +
+ line() +
+ HTML:P("This document will go over some very important things.") +
+ line() +
+ HTML:Script([[
+ function openWindow() {
+ window.open("https://document.com");
+ }
+ ]]) +
+ line() +
+ HTML:Form(formContent) +
+ line()
+ HTML:EndBlock()
+
+ local body = line() + HTML:Body(bodyContent)
+
+ HTML:EndBlock()
+ local html = HTML:Html(head + body + line())
+
+ print(html)
+end
\ No newline at end of file
diff --git a/examples/html_generator/result.html b/examples/html_generator/result.html
new file mode 100644
index 0000000..172cec4
--- /dev/null
+++ b/examples/html_generator/result.html
@@ -0,0 +1,18 @@
+
+
This document will go over some very important things.
+
+
+
+
diff --git a/examples/queues/main.lua b/examples/queues/main.lua
new file mode 100644
index 0000000..baed14c
--- /dev/null
+++ b/examples/queues/main.lua
@@ -0,0 +1,11 @@
+using(luay.std)
+
+function main()
+ local q = Queue()
+ q:Enqueue("lil uzi vert")
+ q:Enqueue("j cole")
+ q:Enqueue("smokepurpp")
+ repr(q)
+ q:Dequeue()
+ repr(q)
+end
\ No newline at end of file
diff --git a/lib/core.lua b/lib/core.lua
index 4da3615..f2190bc 100644
--- a/lib/core.lua
+++ b/lib/core.lua
@@ -8,10 +8,10 @@ end
function import(module)
local data = require(module)
using(data)
- return
end
----
+---@param name string
+---@return table
function singleton(name)
local body = _ENV[name]
local lib = body[1]
@@ -46,7 +46,6 @@ function namespace(name)
__newindex = function(self, k, v)
throw(luay.std.Error("cannot write to namespace"))
end;
-
---@return string
__tostring = function()
return (""):format(state.name);
@@ -120,6 +119,7 @@ end
function constructor(body, initializer)
local self = instance(body)
;(initializer or function() end)(self)
+ self.meta.__metatable = {}
return self
end
@@ -160,6 +160,14 @@ function throw(err, level)
error(colors("%{red}" + err.message), 2 + (level or 0))
end
+---@param message string
+---@vararg ...
+---@return void
+function warn(message, ...)
+ local args = luay.std.Vector("string", {message, ...})
+ print(args:Map(lambda "|m| -> colors('%{yellow}' + m)"):Join("\t"))
+end
+
---@param name string
function enum(name)
return function(body)
@@ -202,10 +210,12 @@ function setfenv(fn, env)
end
---@param content string
+---@return Function
function lambda(content)
assert(typeof(content) == "string", "lambda function converts a string to a function expression")
assert(content:Includes("|") and not content:IsBlank(), "malformed lambda")
+ local env = _ENV
local components = content:Trim():Split("|")
local body = components:At(1):Trim()
local params = luay.std.List()
@@ -216,8 +226,7 @@ function lambda(content)
local luaString = body:Replace("->", "return")
return luay.util.Function(function(...)
- local env = _ENV
- for i, v in varargs(...) do
+ for i, v in pairs {...} do
local name = params:At(i)
if name then
env[name] = v
diff --git a/lib/data.lua b/lib/data.lua
index 21fcf25..f909a7a 100644
--- a/lib/data.lua
+++ b/lib/data.lua
@@ -1,3 +1,7 @@
+---@param condition any
+---@param err string
+---@param lvl integer
+---@return void
_G.assert = function(condition, err, lvl)
if not condition then
error(err, 3 + (lvl or 0))
@@ -6,27 +10,51 @@ end
--string util
local mtstr = getmetatable("")
+---@param a string
+---@param b string
+---@return string
function mtstr.__add(a, b)
return a .. b
end
+---@param a string
+---@param b string
+---@return string
function mtstr.__shr(a, b)
return b .. a
end
+---@param a string
+---@return function
function mtstr.__bnot(a)
return a:Chars()
end
+---@param a string
+---@param b string
+---@return string
function mtstr.__mul(a, b)
return a:rep(b)
end
+---@param a string
+---@param b string
+---@return string
function mtstr.__unm(a, b)
return a:reverse()
end
+---@param a string
+---@param b string
+---@return Vector
+function mtstr.__div(a, b)
+ return a:Split(b)
+end
+
local old_mtIndex = mtstr.__index
+---@param str string
+---@param i integer | table
+---@return string
function mtstr.__index(str, i)
if type(i) == "number" then
local char = str:sub(i, i)
@@ -44,6 +72,8 @@ function mtstr.__index(str, i)
end
--table util
+---@param t table
+---@return table
function table.inverse(t)
assert(type(t) == "table", "cannot inverse table of type '" + typeof(t) + "'")
local r = {}
@@ -53,8 +83,11 @@ function table.inverse(t)
return r
end
+---@param t table
+---@return function
function values(t)
local i = 0
+ ---@return any
return function()
i = i + 1
if i <= #t then
@@ -63,8 +96,10 @@ function values(t)
end
end
+---@vararg ...
+---@return function
function varargs(...)
- return pairs {...}
+ return values {...}
end
colors = require "ansicolors"
\ No newline at end of file
diff --git a/lib/main.lua b/lib/main.lua
index 7914ab6..e64a83d 100644
--- a/lib/main.lua
+++ b/lib/main.lua
@@ -1,3 +1,5 @@
+---@alias void nil
+
require "data"
require "core"
require "util"
diff --git a/lib/std.lua b/lib/std.lua
index 1af6226..4309bc8 100644
--- a/lib/std.lua
+++ b/lib/std.lua
@@ -39,28 +39,27 @@ do
local h = 1
for i, v in iter_func(data) do
- if has_table or string_keys then
- io.write('\n ')
- for j=1, level do
- if j > 1 then
- io.write(' ')
+ if i ~= "meta" then
+ if has_table or string_keys then
+ io.write('\n ')
+ for j=1, level do
+ if j > 1 then
+ io.write(' ')
+ end
end
end
+ if string_keys then
+ io.write('[')
+ repr(i, level + 1)
+ io.write('] = ')
+ end
+ if type(v) == 'table' then
+ repr(v, level + 1)
+ else
+ repr(v, level + 1)
+ end
+ h = h + 1
end
- if string_keys then
- io.write('[')
- repr(i, level + 1)
- io.write('] = ')
- end
- if type(v) == 'table' then
- repr(v, level + 1)
- else
- repr(v, level + 1)
- end
- if h < length then
- io.write(', ')
- end
- h = h + 1
end
if has_table or string_keys then
io.write('\n')
@@ -70,7 +69,7 @@ do
end
end
end
- io.write('}')
+ io.write('}\n')
elseif data_type == 'string' then
io.write("'")
for char in data:gmatch('.') do
@@ -98,13 +97,11 @@ do
io.write((not tostring(data) or tostring(data) == "") and "nil" or tostring(data))
end
- if level == 1 or data_type == 'table' then
- io.write('\n')
- end
-
io.flush()
end
+ ---@class Error
+ ---@field message string
local Error = class "Error" do
function Error.new(message)
return constructor(Error, function(self)
@@ -113,7 +110,13 @@ do
end
end
+ ---@class Vector
+ ---@field type type
+ ---@field cache table
local Vector = class "Vector" do
+ ---@param T type
+ ---@param base table
+ ---@return Vector
function Vector.new(T, base)
assert(T and typeof(T) == "string", "cannot create std::Vector with no type")
return constructor(Vector, function(self)
@@ -157,6 +160,17 @@ do
VectorTypeError(value, self.type)
end
end
+
+ function Vector:Join(sep)
+ local res = ""
+ for v in ~self do
+ res = res + v
+ if self:IndexOf(v) ~= #self then
+ res = res + sep
+ end
+ end
+ return res
+ end
function Vector:Fill(amount, callback)
for i = 1, amount do
@@ -292,7 +306,10 @@ do
end
end
+ ---@class List
+ ---@field cache table
local List = class "List" do
+ ---@param base table
function List.new(base)
return constructor(List, function(self)
self.cache = base or {}
@@ -333,7 +350,9 @@ do
end
function List:Shift()
- self:Remove(self:First())
+ local val = self:First()
+ self:Remove(val)
+ return val
end
function List:Filter(predicate)
@@ -441,13 +460,24 @@ do
end
end
+ ---@class String
+ ---@field content? string
local String = class "String" do
+ ---@param content string
+ ---@return string
function String.new(content)
return constructor(String, function(self)
self.content = content
end)
end
+ --- Calls `predicate` for each
+ --- character in the string. If
+ --- `predicate` returns true,
+ --- append the current character
+ --- to the result returned by `:Filter`.
+ ---@param predicate function
+ ---@return string
function String:Filter(predicate)
local res = ""
for i = 1, #self do
@@ -459,6 +489,12 @@ do
return res
end
+ --- Calls `transform` for each
+ --- character in the string and
+ --- appends the string returned
+ --- by `transform`.
+ ---@param transform function
+ ---@return string
function String:Map(transform)
local res = ""
for i = 1, #self do
@@ -467,6 +503,9 @@ do
return res
end
+ --- Trims a string from leading
+ --- and trailing whitespaces.
+ ---@return string
function String:Trim()
local s = self:GetContent()
local _, i1 = s:find("^%s*")
@@ -474,16 +513,29 @@ do
return self[{i1 + 1;i2 - 1}]
end
+ --- Returns itself
+ ---@return string
function String:GetContent()
return self.content or self
end
+ --- Capitalizes the first
+ --- character of the string
+ --- and returns the result.
+ ---@return string
function String:CapitalizeFirst()
local first = self[1]
local rest = self[{2;#self}]
return first:upper() + rest
end
+
+ --- Returns an iterator that
+ --- returns each character of
+ --- the string. Use the unary `~`
+ --- operator on a string as
+ --- an alias for this method.
+ ---@return function
function String:Chars()
local i = 0
return function()
@@ -494,8 +546,13 @@ do
end
end
+ --- Splits a string into a
+ --- `List` of strings, each
+ --- element delimited by `sep`.
+ ---@param sep string
+ ---@return List
function String:Split(sep)
- local res = List()
+ local res = Vector("string")
if not sep then
for c in ~self do
res:Add(c)
@@ -508,70 +565,126 @@ do
return res
end
+ --- Returns a tuple of
+ --- each character inside
+ --- of the string.
+ ---@return ...
function String:Unpack()
- local charList = List()
+ local charList = Vector("string")
for c in ~self do
charList:Add(c)
end
return charList:Unpack()
end
- function String:Occurences(sub)
- return select(2, self:gsub(sub, sub))
- end
-
+ --- Replaces each occurence of
+ --- `content` with `replacement`
+ --- and returns the result.
+ ---@param content string
+ ---@param replacement string
+ ---@return string
function String:Replace(content, replacement)
- local res = self:GetContent():gsub(content, replacement)
- return res
+ return self:GetContent():gsub(content, replacement)
end
+ --- Returns true if there are
+ --- any occurences of `sub`
+ --- inside of the string,
+ --- false otherwise.
+ ---@param sub string
+ ---@return boolean
function String:Includes(sub)
return self:GetContent():find(sub) and true or false
end
+ --- Returns true if the string
+ --- is a valid e-mail address,
+ --- false otherwise.
+ ---@return boolean
function String:IsEmail()
return self:GetContent():match("[A-Za-z0-9%.%%%+%-%_]+@[A-Za-z0-9%.%%%+%-%_]+%.%w%w%w?%w?") ~= nil
end
+ --- Returns true if each character
+ --- in the string is uppercase, false
+ --- otherwise.
+ ---@return boolean
function String:IsUpper()
return not self:GetContent():find("%l")
end
+ --- Returns true if each character
+ --- in the string is lowercase, false
+ --- otherwise.
+ ---@return boolean
function String:IsLower()
return not self:GetContent():find("%u")
end
+ --- Returns true if each character
+ --- in the string is a whitespace
+ --- (\n, \r, \t, and space), false
+ --- otherwise.
+ ---@return boolean
function String:IsBlank()
return not self:IsAlphaNumeric() and self:Includes("%s+") or self == ""
end
+ ---@return boolean
String.IsEmpty = String.IsBlank
+ ---@return boolean
String.IsWhite = String.IsBlank
+ --- Returns true if each character
+ --- in the string is a letter, false
+ --- otherwise.
+ ---@return boolean
function String:IsAlpha()
return self:GetContent():find("%A")
end
+ --- Returns true if the string is
+ --- a valid number, false otherwise.
+ ---@return boolean
function String:IsNumeric()
return tonumber(self:GetContent()) and true or false
end
+ ---@return boolean
String.IsDigit = String.IsNumeric
+ --- Returns true if each character
+ --- in the string is a letter or
+ --- the string is a valid number,
+ --- false otherwise.
+ ---@return boolean
function String:IsAlphaNumeric()
return self:IsAlpha() or self:IsNumeric()
end
+ ---@return boolean
String.IsAlphaNum = String.IsAlphaNumeric
- function String:Surround(wrap)
+ --- Returns a new string enclosed in
+ --- `wrap` repeated `repetitions` times.
+ ---@param wrap string
+ ---@param repetitions? integer
+ ---@return string
+ function String:Surround(wrap, repetitions)
+ wrap = tostring(wrap):rep(repetitions or 1)
return wrap + self:GetContent() + wrap
end
+ --- Encloses a string in quotation marks
+ ---@return string
function String:Quote()
- return ("%q"):format(self:GetContent())
+ return self:GetContent():Wrap('"')
end
+ --- Returns the character at the
+ --- position `idx` in the string.
+ ---@param idx integer
+ ---@return string
function String:CharAt(idx)
return self:GetContent()[idx]
end
@@ -579,7 +692,10 @@ do
setmetatable(string, { __index = String })
+ ---@class Stack
+ ---@field cache table
local Stack = class "Stack" do
+ ---@param base table
function Stack.new(base)
return constructor(Stack, function(self)
self.cache = base or {}
@@ -655,7 +771,14 @@ do
end
end
+ ---@class Map
+ ---@field K type
+ ---@field V type
+ ---@field cache table
local Map = class "Map" do
+ ---@param K type
+ ---@param V type
+ ---@param cache table
function Map.new(K, V, base)
assert(K and typeof(K) == "string", "Map must have key type")
assert(V and typeof(V) == "string", "Map must have value type")
@@ -752,7 +875,10 @@ do
end
end
+ ---@class Map
+ ---@field cache table
local Queue = class "Queue" do
+ ---@field base table
function Queue.new(base)
return constructor(Queue, function(self)
self.cache = base or {}
@@ -796,16 +922,26 @@ do
return res
end
+ function Queue:First()
+ return self:At(1)
+ end
+
+ function Queue:Last()
+ return self:At(#self)
+ end
+
function Queue:At(idx)
return self.cache[idx]
end
- function Queue:Add(value)
+ function Queue:Enqueue(value)
table.insert(self.cache, value)
end
- function Queue:Remove()
- table.remove(self.cache, #self.cache)
+ function Queue:Dequeue()
+ local v = self:First()
+ table.remove(self.cache, 1)
+ return v
end
function Queue:Indices()
@@ -833,9 +969,12 @@ do
end
end
+ ---@class Deque
+ ---@field cache table
local Deque = class "Deque" do
- function Deque.new()
- extend(Deque, Queue.new())
+ ---@param base table
+ function Deque.new(base)
+ extend(Deque, Queue.new(base))
return constructor(Deque, function(self)
function self.meta.__tostring()
return self:ToString()
@@ -909,6 +1048,9 @@ do
end
end
+ ---@class Pair
+ ---@field first any
+ ---@field second any
local Pair = class "Pair" do
function Pair.new(first, second)
return constructor(Pair, function(self)
@@ -930,6 +1072,9 @@ do
end
end
+ ---@class KeyValuePair
+ ---@field first string
+ ---@field second any
local KeyValuePair = class "KeyValuePair" do
function KeyValuePair.new(first, second)
assert(typeof(first) == "string", "key in KeyValuePair must be a string")
@@ -952,6 +1097,12 @@ do
end
end
+
+ ---@class StrictPair
+ ---@field T1 type
+ ---@field T2 type
+ ---@field first T1
+ ---@field second T2
local StrictPair = class "StrictPair" do
function StrictPair.new(T1, T2, first, second)
assert(typeof(first) == T1, "first value in StrictPair must be of type '" + T1 + "'")
@@ -975,6 +1126,10 @@ do
end
end
+ ---@class StrictKeyValuePair
+ ---@field V type
+ ---@field first string
+ ---@field second V
local StrictKeyValuePair = class "StrictKeyValuePair" do
function StrictKeyValuePair.new(V, first, second)
assert(typeof(first) == "string", "key in StrictKeyValuePair must be a string")
@@ -998,7 +1153,10 @@ do
end
end
+ ---@class Set
+ ---@field cache table
local Set = class "Set" do
+ ---@param base table
function Set.new(base)
return constructor(Set, function(self)
self.cache = base or {}
@@ -1149,14 +1307,220 @@ do
repr(self.cache)
end
end
+
+ ---@class LinkedNode
+ ---@field value any
+ ---@field next? LinkedNode
+ local LinkedNode = class "LinkedNode" do
+ ---@param value any
+ ---@return LinkedNode
+ function LinkedNode.new(value)
+ return constructor(LinkedNode, function(self)
+ self.value = value
+ self.next = nil
+
+ function self.meta.__tostring()
+ return self:ToString()
+ end
+ end)
+ end
+
+ function LinkedNode:Next()
+ return self.next
+ end
+
+ function LinkedNode:ToString()
+ return ("LinkedNode( value=%s next=%s )"):format(self.value, self.next)
+ end
+ end
+
+ ---@class LinkedList
+ ---@field root? LinkedNode
+ local LinkedList = class "LinkedList" do
+ ---@return LinkedList
+ function LinkedList.new()
+ return constructor(LinkedList, function(self)
+ self.root = nil
+
+ function self.meta.__len()
+ return self:Size()
+ end
+
+ function self.meta.__tostring()
+ return self:ToString()
+ end
+
+ function self.meta.__bnot()
+ return self:Nodes()
+ end
+ end)
+ end
+
+ function LinkedList:Remove(value)
+ local parent
+ for node in ~self do
+ if node.value == value then
+ if parent == nil then
+ self.root = nil
+ else
+ parent.next = nil
+ end
+ end
+ parent = node
+ end
+ end
+
+ function LinkedList:GetNodes()
+ local nodes = Vector("LinkedNode")
+ local current = self.root
+
+ while true do
+ if current ~= nil then
+ nodes:Add(current)
+ current = current:Next()
+ else
+ break
+ end
+ end
+
+ return nodes
+ end
+
+ function LinkedList:Nodes()
+ return self:GetNodes():Values()
+ end
+
+ function LinkedList:ForEach(callback)
+ local nodes = self:GetNodes()
+ for node in ~nodes do
+ callback(node)
+ end
+ end
+
+ ---@param value any
+ ---@return LinkedList
+ function LinkedList:Add(value)
+ if self.root == nil then
+ self.root = LinkedNode(value, nil)
+ else
+ local current = self.root
+ local node = current
+ while true do
+ current = current:Next()
+ if current == nil then
+ break
+ else
+ node = current
+ end
+ end
+ node.next = LinkedNode(value, nil)
+ end
+ return self
+ end
+
+ ---@return LinkedNode
+ function LinkedList:Next()
+ return self.root
+ end
+
+ ---@return integer
+ function LinkedList:Size()
+ local size = 1
+ local current
+ while true do
+ if self.root == nil then
+ break
+ else
+ current = (current or self.root):Next()
+ if current ~= nil then
+ size = size + 1
+ else
+ break
+ end
+ end
+ end
+ return size
+ end
+
+ ---@return string
+ function LinkedList:ToString()
+ return ("LinkedList( size=%s root=%s )"):format(#self, self.root)
+ end
+
+ ---@return void
+ function LinkedList:__repr()
+ repr(self.root)
+ end
+ end
+
+ local MultiMap = class "MultiMap" do
+ function MultiMap.new(K, V)
+ return constructor(MultiMap, function(self)
+ self.cache = {}
+ self.K = K
+ self.V = V
+ end)
+ end
+
+ local function TypeEquals(value, expected)
+ if typeof(value) == expected then
+ return true
+ end
+ return false
+ end
+
+ local function MapTypeError(value, expected)
+ throw(Error(("MultiMapTypeError: \n\tgot: %s\n\texpected: %s"):format(typeof(value), expected)), 2)
+ end
+
+ local function AssertType(value, expected)
+ if not TypeEquals(value, expected) then
+ MapTypeError(value, expected)
+ end
+ end
+
+ function MultiMap:Delete(key)
+ AssertType(key, self.K)
+ self.cache[key] = nil
+ return self
+ end
+
+ ---@return Vector
+ function MultiMap:Get(key)
+ AssertType(key, self.K)
+ return self.cache[key]
+ end
+
+ function MultiMap:Set(key, ...)
+ AssertType(key, self.K)
+ if self.cache[key] == nil then
+ self.cache[key] = Vector(self.V)
+ end
+
+ for v in varargs(...) do
+ AssertType(v, self.V)
+ self.cache[key]:Add(v)
+ end
+ return self
+ end
+
+ function MultiMap:__repr()
+ repr(self.cache)
+ end
+ end
+ ---@class EventEmitter
+ ---@field listeners Map
local EventEmitter = class "EventEmitter" do
+ ---@return EventEmitter
function EventEmitter.new()
return constructor(EventEmitter, function(self)
self.listeners = Map("string", "Vector")
end)
end
+ ---@param event string
+ ---@return Vector
function EventEmitter:GetListener(event)
local callbacks = self.listeners:Get(event)
if not callbacks then
@@ -1166,20 +1530,31 @@ do
return self.listeners:Get(event)
end
+ ---@param event string
+ ---@return integer
function EventEmitter:ListenerCount(event)
return #self:GetListener(event)
end
+ ---@param event string
+ ---@param callback function
+ ---@return EventEmitter
function EventEmitter:AddListener(event, callback)
self:GetListener(event):Add(callback)
return self
end
+ ---@param event string
+ ---@param callback function
+ ---@return EventEmitter
function EventEmitter:RemoveListener(event, callback)
self:GetListener(event):Remove(callback)
return self
end
+ ---@param event string
+ ---@vararg ...
+ ---@return EventEmitter
function EventEmitter:Emit(event, ...)
local callbacks = self:GetListener(event)
for callback in ~callbacks do
@@ -1188,10 +1563,16 @@ do
return self
end
+ ---@param event string
+ ---@param callback function
+ ---@return EventEmitter
function EventEmitter:On(event, callback)
return self:AddListener(event, callback)
end
+ ---@param event string
+ ---@param callback function
+ ---@return EventEmitter
function EventEmitter:Once(event, callback)
local function doOnce(...)
callback(...)
@@ -1200,10 +1581,14 @@ do
return self:On(event, doOnce)
end
- function EventEmitter:Off(event, callbackThread)
- return self:RemoveListener(event, callbackThread)
+ ---@param event string
+ ---@param callback function
+ ---@return EventEmitter
+ function EventEmitter:Off(event, callback)
+ return self:RemoveListener(event, callback)
end
+ ---@return void
function EventEmitter:Destroy()
self.listeners = nil
self.meta.__index = function()
@@ -1216,6 +1601,8 @@ do
end
end
+ ---@param fn function
+ ---@vararg ...
function spawn(fn, ...)
local e = EventEmitter()
e:Once("callFn", fn)
@@ -1223,7 +1610,9 @@ do
e:Destroy()
end
+ ---@class Input
local Input = class "Input" do
+ ---@return Input
function Input.new()
return constructor(Input, function(self)
function self.meta.__shr(_, prompt)
@@ -1239,7 +1628,9 @@ do
end
end
+ ---@class Output
local Output = class "Output" do
+ ---@return Output
function Output.new()
return constructor(Output, function(self)
function self.meta.__shl(output, content)
@@ -1254,14 +1645,14 @@ do
end
end
- local lout = Output();
- local lin = Input();
-
+ local lout = Output()
+ local lin = Input()
do
extend(Process, EventEmitter())
Process.stdout = lout
Process.stdin = lin
+ ---@param code? integer
function Process:Exit(code)
self:Emit("exit", code or 1)
throw(Error(""))
@@ -1635,7 +2026,7 @@ do
state.pipesCount = 0
state.flowing = false
- for i in util.range(1, len, 1) do
+ for i in luay.util.range(1, len, 1) do
dests[i]:Emit("unpipe", self)
end
@@ -1741,7 +2132,7 @@ do
local events = {"error", "close", "destroy", "resume", "pause"}
for v in values(events) do
- stream:On(v, util.bind(self.Emit, self, v))
+ stream:On(v, luay.util.bind(self.Emit, self, v))
end
self._Read = function()
@@ -1781,7 +2172,7 @@ do
else
local tmp = {}
local c = 0
- for i in util.range(1, len(list), 1) do
+ for i in luay.util.range(1, len(list), 1) do
if n - c >= len(list[1]) then
c = c + list[1]
table.insert(tmp, table.remove(list, 1))
@@ -1998,7 +2389,7 @@ do
end
end
- if util.isNaN(n) or not n then
+ if luay.util.isNaN(n) or not n then
if state.flowing and len(state.buffer) > 0 then
return len(state.buffer[1])
else
@@ -2055,6 +2446,9 @@ do
Queue = Queue;
Deque = Deque;
Set = Set;
+ LinkedList = LinkedList;
+ LinkedNode = LinkedNode;
+ MultiMap = MultiMap;
stream = stream;
diff --git a/lib/util.lua b/lib/util.lua
index 7d3761f..06ce034 100644
--- a/lib/util.lua
+++ b/lib/util.lua
@@ -1,13 +1,26 @@
do
+ ---@param v any
+ ---@return boolean
local function isNaN(v)
return v ~= v
end
+ ---@param from number
+ ---@param to number
+ ---@param step number
+ ---@return function
+ ---@return nil
+ ---@return number
local function range(from, to, step)
step = step or 1
+ ---@return number
return function(_, lastvalue)
local nextvalue = lastvalue + step
- if step > 0 and nextvalue <= to or step < 0 and nextvalue >= to or
+ if
+ step > 0 and
+ nextvalue <= to or
+ step < 0 and
+ nextvalue >= to or
step == 0
then
return nextvalue
@@ -42,8 +55,11 @@ do
end
end
+ ---@class StringBuilder
local StringBuilder = class "StringBuilder" do
local AssertString
+ ---@param originalContent
+ ---@return StringBuilder
function StringBuilder.new(originalContent)
return constructor(StringBuilder, function(self)
AssertString(originalContent or "")
@@ -59,111 +75,212 @@ do
end)
end
+ ---@param str string
+ ---@return void
function AssertString(str)
assert(typeof(str) == "string", "cannot append/prepend a non-string value")
end
+ ---@param str string
+ ---@return StringBuilder
function StringBuilder:Append(str)
AssertString(str)
self.content = self.content + str
return self
end
+ ---@param str string
+ ---@return StringBuilder
function StringBuilder:AppendLine(str)
str = str or ""
- return self:Append(str + std.endl)
+ return self:Append(str + luay.std.endl)
end
+ ---@param str string
+ ---@return StringBuilder
function StringBuilder:Prepend(str)
AssertString(str)
self.content = str + self.content
return self
end
+ ---@param str string
+ ---@return StringBuilder
function StringBuilder:PrependLine(str)
str = str or ""
- return self:Prepend(str + std.endl)
+ return self:Prepend(str + luay.std.endl)
end
+ ---@return string
function StringBuilder:ToString()
return self.content
end
end
+ ---@class HTML
local HTML = class "HTML" do
- function arrows(str)
+ local blockLevel = 0
+
+ ---@param str string
+ ---@return string
+ local function arrows(str)
return ("<%s>"):format(str)
end
- function shortTag(name, attributes)
+ ---@param name string
+ ---@param attributes table
+ ---@return string
+ local function shortTag(name, attributes)
assert(typeof(name) == "string", "expected tag name to be string")
- assert(attributes ~= nil and (typeof(attributes) == "Vector" and attributes.type == "string") or true, "expected Vector of attributes")
- local tag = StringBuilder.new("<")
- :Append(name)
+ local tag = StringBuilder((" "):rep(blockLevel))
+ :Append("<" + name)
if attributes then
- for attr in attributes:Values() do
+ for attr in ~luay.std.Vector("string", attributes) do
tag:Append(" " + attr)
end
end
-
- tag:Append(" />")
- return tostring(tag)
+ return tostring(tag:Append(" />"))
end
- function htmlTag(name, content, attributes)
+ ---@param name string
+ ---@param content string
+ ---@param attributes table
+ ---@return string
+ local function htmlTag(name, content, attributes)
assert(typeof(name) == "string", "expected tag name to be string")
- assert(content ~= nil and typeof(content) == "string" or true, "expected content to be string")
+ assert(content ~= nil and typeof(content) == "string", "expected content to be string")
assert(attributes ~= nil and (typeof(attributes) == "Vector" and attributes.type == "string") or true, "expected Vector of attributes")
- local tag = StringBuilder.new(arrows(name))
- :Append(content or "")
-
+ local tag = StringBuilder((" "):rep(blockLevel))
+ :Append("<" + name)
+
if attributes then
for attr in attributes:Values() do
tag:Append(" " + attr)
end
end
+ return tostring(
+ tag
+ :Append(">" + content or "")
+ :Append(arrows("/" + name))
+ )
+ end
+
+ function HTML:Input(attributes)
+ return shortTag("input", attributes)
+ end
+
+ function HTML:Form(content, attributes)
+ return htmlTag("form", content, attributes)
+ end
+
+ function HTML:Script(content, src)
+ return htmlTag("script", "\n" + content, src and {f'src="{src}"'} or nil)
+ end
+
+ function HTML:Img(attributes)
+ return shortTag("img", attributes)
+ end
+
+ function HTML:A(link, content, attributes)
+ local fullAttributes = luay.std.Vector.new("string", {f'href="{link}"'})
+ if attributes then
+ fullAttributes = fullAttributes & attributes
+ end
+ return htmlTag("a", content, fullAttributes)
+ end
- tag:Append(arrows("/" + name))
- return tostring(tag)
+ function HTML:Newline()
+ return "\n" + (" "):rep(blockLevel)
end
+ function HTML:BeginBlock()
+ blockLevel = blockLevel + 1
+ end
+
+ function HTML:EndBlock()
+ blockLevel = blockLevel - 1
+ end
+
+ function HTML:H4(content, attributes)
+ return htmlTag("h4", content, attributes)
+ end
+
+ function HTML:H3(content, attributes)
+ return htmlTag("h3", content, attributes)
+ end
+
+ function HTML:H2(content, attributes)
+ return htmlTag("h2", content, attributes)
+ end
+
+ function HTML:H1(content, attributes)
+ return htmlTag("h1", content, attributes)
+ end
+
+ ---@param content string
+ ---@param attributes table
function HTML:Html(content, attributes)
return htmlTag("html", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Head(content, attributes)
return htmlTag("head", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Header(content, attributes)
return htmlTag("header", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Body(content, attributes)
return htmlTag("body", content, attributes)
end
+ ---@param attributes table
function HTML:Link(attributes)
return shortTag("link", attributes)
end
+ ---@param iconPath string
+ ---@param attributes table
function HTML:FavIconLink(iconPath, attributes)
- return self:Link(std.Vector.new("string", {'rel="icon"', ('href="%s"'):format(iconPath)}):Merge(attributes))
+ local fullAttributes = luay.std.Vector.new("string", {'rel="icon"', ('href="%s"'):format(iconPath)})
+ if attributes then
+ fullAttributes = fullAttributes & attributes
+ end
+ return self:Link(fullAttributes:ToTable())
end
+ ---@param cssPath string
+ ---@param attributes table
function HTML:StylesheetLink(cssPath, attributes)
- return self:Link(std.Vector.new("string", {'rel="stylesheet"', ('href="%s"'):format(cssPath)}):Merge(attributes))
+ local fullAttributes = luay.std.Vector.new("string", {'rel="stylesheet"', ('href="%s"'):format(cssPath)})
+ if attributes then
+ fullAttributes = fullAttributes & attributes
+ end
+ return self:Link(fullAttributes:ToTable())
end
+ ---@param content string
+ ---@param attributes table
function HTML:P(content, attributes)
return htmlTag("p", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:B(content, attributes)
return htmlTag("b", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:I(content, attributes)
return htmlTag("i", content, attributes)
end
@@ -188,65 +305,114 @@ do
return ">"
end
+ ---@param content string
+ ---@param attributes table
function HTML:Code(content, attributes)
return htmlTag("code", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Pre(content, attributes)
return htmlTag("pre", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Title(content, attributes)
return htmlTag("title", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Table(content, attributes)
return htmlTag("table", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Ul(content, attributes)
return htmlTag("ul", content, attributes)
end
+ ---@param content string
+ ---@param attributes table
function HTML:Li(content, attributes)
return htmlTag("li", content, attributes)
end
end
+ ---@class Function
+ ---@field callback function | Function
local Function = class "Function" do
+ ---@param callback function | Function
+ ---@return Function
function Function.new(callback)
return constructor(Function, function(self)
self.callback = callback
+ ---@param first Function
+ ---@param second Function
+ ---@return function
+ function self.meta.__band(first, second)
+ ---@vararg ...
+ ---@return ...
+ return function(...)
+ local res = luay.std.List()
+ for v in varargs(first(...)) do
+ res:Add(v)
+ end
+ for v in varargs(second(...)) do
+ res:Add(v)
+ end
+ return res:Unpack()
+ end
+ end
+
+ ---@vararg ...
+ ---@return ...
function self.meta.__call(_, ...)
return self:Call(...)
end
+ ---@return string
function self.meta.__tostring()
- return tostring(self.callback)
+ return tostring(self.callback):CapitalizeFirst()
end
end)
end
+ ---@param args table | Vector | List | Set | Stack
+ ---@return ...
function Function:Apply(args)
- return self:Call(table.unpack(args))
+ return self:Call(args.Unpack and args:Unpack() or table.unpack(args))
end
+ ---@param selfValue unknown
+ ---@vararg ...
+ ---@return Function
function Function:Bind(selfValue, ...)
self.callback = self.callback.Bind and self:Bind(selfValue, ...) or bind(self.callback, selfValue, ...)
return self
end
+ ---@vararg ...
+ ---@return ...
function Function:Call(...)
return self.callback(...)
end
+ ---@return void
function Function:__repr()
- return tostring(self)
+ print(tostring(self))
end
end
+ ---@class EventSubscription
+ ---@field event Event
+ ---@field callback function | Function
local EventSubscription = class "EventSubscription" do
+ ---@return EventSubscription
function EventSubscription.new(event, callback)
return constructor(EventSubscription, function(self)
self.event = event
@@ -254,24 +420,32 @@ do
end)
end
+ ---@return void
function EventSubscription:Unsubscribe()
self.event.listeners:Remove(self.callback)
end
end
+ ---@class Event
+ ---@field listeners Vector
local Event = class "Event" do
+ ---@return Event
function Event.new()
return constructor(Event, function(self)
self.listeners = luay.std.Vector("function")
end)
end
+ ---@vararg ...
+ ---@return void
function Event:Fire(...)
for callback in ~self.listeners do
callback(...)
end
end
+ ---@param callback function | Function
+ ---@return EventSubscription
function Event:Subscribe(callback)
self.listeners:Add(callback)
return EventSubscription(self, callback)
diff --git a/main.cpp b/main.cpp
index f2c0091..ad6b287 100644
--- a/main.cpp
+++ b/main.cpp
@@ -329,7 +329,7 @@ int main(int argc, char** argv)
Luay luay(argc, argv);
char* fileDir = luay.argv[1];
- if (luay.argc < 1 || luay.argc > 2)
+ if (luay.argc < 1)
{
puts("Usage: luay \n");
return 0;