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 @@ + + + + + +

My Document

+ My Other Document +

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;