Compare commits
3 Commits
37510f6bb4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a3c126100 | |||
| 80ac2b7b37 | |||
| 7de1bb3ef0 |
203
helper.lua
Normal file
203
helper.lua
Normal file
@@ -0,0 +1,203 @@
|
||||
local old_print = print
|
||||
function print(...)
|
||||
local info = debug.getinfo(2)
|
||||
local line = info.currentline
|
||||
local name = info.source
|
||||
old_print(string.format("%i%s> ",line,name),...)
|
||||
end
|
||||
function log(...)
|
||||
old_print(...)
|
||||
end
|
||||
function map(tab,func)
|
||||
local new = {}
|
||||
for index,item in pairs(tab) do
|
||||
new[index] = func(item)
|
||||
end
|
||||
return new
|
||||
end
|
||||
-- HELPER FUNCTIONS
|
||||
-- Insert every item in t1 into t0
|
||||
function just(...)
|
||||
local items = {...}
|
||||
return function()
|
||||
return table.unpack(items)
|
||||
end
|
||||
end
|
||||
|
||||
id = function(...) return ... end
|
||||
|
||||
function union(t0,t1)
|
||||
for index,item in pairs(t0) do
|
||||
t0[index] = t1
|
||||
end
|
||||
return t0
|
||||
end
|
||||
|
||||
-- Iterate (ipairs without the index)
|
||||
function iterate(...)
|
||||
local iterator,tab,i = pairs(...)
|
||||
return function(...)
|
||||
local index,item = iterator(tab,i)
|
||||
i = index
|
||||
return item
|
||||
end,tab,i
|
||||
end
|
||||
|
||||
function indices(t)
|
||||
if type(t) ~= "table" then
|
||||
error(
|
||||
string.format(
|
||||
"Argument 1 '%s' must be a table",
|
||||
tostring(t)
|
||||
)
|
||||
,2)
|
||||
end
|
||||
local indices = {}
|
||||
for index,_ in pairs(t) do
|
||||
table.insert(indices,index)
|
||||
end
|
||||
return indices
|
||||
end
|
||||
|
||||
-- Ensures a table has a metatable
|
||||
function ensure_metatable(t)
|
||||
local meta = getmetatable(t)
|
||||
if not meta then meta = {} setmetatable(t,meta) end
|
||||
return meta
|
||||
end
|
||||
|
||||
function strings(t,item)
|
||||
ensure_metatable(t).__tostring = item
|
||||
end
|
||||
|
||||
-- Makes a table callable
|
||||
function calls(t,fun)
|
||||
ensure_metatable(t).__call = fun
|
||||
end
|
||||
|
||||
-- Makes a table record access
|
||||
function indexes(t,fun)
|
||||
ensure_metatable(t).__index = fun
|
||||
end
|
||||
|
||||
-- Makes a table record setting
|
||||
function modifies(t,fun)
|
||||
ensure_metatable(t).__newindex = fun
|
||||
end
|
||||
|
||||
-- Shallow table to string
|
||||
function table_tostring(tab,sep)
|
||||
local items = {}
|
||||
for item in iterate(tab) do
|
||||
table.insert(items,tostring(item))
|
||||
end
|
||||
return table.concat(items,sep)
|
||||
end
|
||||
|
||||
function type_of(item)
|
||||
local type_name = type(item)
|
||||
if type_name == "table" then
|
||||
local meta = getmetatable(item)
|
||||
if not meta then
|
||||
return "table"
|
||||
else
|
||||
return meta
|
||||
end
|
||||
else
|
||||
return type_name
|
||||
end
|
||||
end
|
||||
|
||||
-- DEBUGGING FUNCTIONS
|
||||
-- Runs a function
|
||||
function assert_meta(...)
|
||||
local metas = {...}
|
||||
return function(x,raise,handler)
|
||||
handler = handler or error
|
||||
local meta = getmetatable(x)
|
||||
for tab in iterate(metas) do
|
||||
if meta == tab then return end
|
||||
end
|
||||
local s_metas = {}
|
||||
for meta in iterate(metas) do
|
||||
table.insert(s_metas,tostring(meta))
|
||||
end
|
||||
local level = 2
|
||||
local msg = string.format(
|
||||
"Expected metatables '%s' but got '%s'",
|
||||
table.concat(s_metas,","),
|
||||
type(x)
|
||||
)
|
||||
if raise then
|
||||
level = 3
|
||||
msg = string.format(raise,msg)
|
||||
end
|
||||
handler(msg,level)
|
||||
end
|
||||
end
|
||||
|
||||
function assert_type(...)
|
||||
local types = {...}
|
||||
return function(x,raise)
|
||||
local t = type(x)
|
||||
for name in iterate(types) do
|
||||
if t == name then return end
|
||||
end
|
||||
local level = 2
|
||||
local msg = string.format(
|
||||
"Expected type '%s' but got '%s'",
|
||||
table_tostring(types," or "),
|
||||
type(x))
|
||||
-- For argument errors
|
||||
if raise then
|
||||
level = 3
|
||||
msg = string.format(raise,msg)
|
||||
end
|
||||
error(msg,level)
|
||||
end
|
||||
end
|
||||
|
||||
-- HELPER FUNCTIONS
|
||||
function case_of(t)
|
||||
return function(item,...)
|
||||
return (t[item] or error(
|
||||
string.format(
|
||||
"Couldn't match '%s' in case.",
|
||||
tostring(item)
|
||||
),2
|
||||
))(...)
|
||||
end
|
||||
end
|
||||
|
||||
-- Creates a (p)OOP class
|
||||
function class(name)
|
||||
local meta = {}
|
||||
union(meta,{restrict = {}})
|
||||
meta.__index = meta
|
||||
strings(meta,just(name))
|
||||
calls(meta,function(meta,...)
|
||||
local new = {}
|
||||
setmetatable(new,meta)
|
||||
if meta.new then new:new(...) end
|
||||
return new
|
||||
end)
|
||||
meta.__tostring = function(this)
|
||||
if meta.string then return this:string() end
|
||||
return name
|
||||
end
|
||||
return meta
|
||||
end
|
||||
|
||||
-- Read a file's full contents
|
||||
function read(path)
|
||||
local file = io.open(path)
|
||||
if not file then
|
||||
error(
|
||||
string.match("Could not open file '%s'.",path),3
|
||||
)
|
||||
end
|
||||
local content = file:read("a")
|
||||
return content
|
||||
end
|
||||
|
||||
return _G
|
||||
160
interpret.lua
Normal file
160
interpret.lua
Normal file
@@ -0,0 +1,160 @@
|
||||
require("parse")
|
||||
|
||||
-- TODO: This should really be a separate API and module
|
||||
-- please break it out
|
||||
-- Interpret some text
|
||||
function interpret(content,uri,chain)
|
||||
-- LOCALS
|
||||
-- The pattern for words inbetween expressions
|
||||
local expressionWordPattern = "(.*)"
|
||||
-- The patterns to open and close expressions,
|
||||
-- the words scattered inbetween them will be matches
|
||||
local expressionOpenPattern = "%("
|
||||
local expressionClosePattern = "%)"
|
||||
local subExpressionOpenPattern = "%:"
|
||||
local subExpressionClosePattern = "%,"
|
||||
local blockOpenPattern = "%{"
|
||||
local blockClosePattern = "%}"
|
||||
local blockDelimiterPattern = "[;]+"
|
||||
-- So that all functions have access to eachother
|
||||
local consume,
|
||||
consumeExpression,
|
||||
processWords,
|
||||
processClosure,
|
||||
processOpening,
|
||||
consumeBlock,
|
||||
consumeText,
|
||||
consumeNumber
|
||||
|
||||
|
||||
local consumer = Consumer(content)
|
||||
local function expression(...)
|
||||
return Expression(...):locate(
|
||||
string.format(
|
||||
"%i:%i",
|
||||
consumer.row,
|
||||
consumer.col
|
||||
),uri)
|
||||
end
|
||||
local baseExpression = expression()
|
||||
|
||||
-- FUNCTIONS
|
||||
-- Consume an expression, with its sub expressions
|
||||
-- given that an opening has just been consumed
|
||||
local singleLineStringPattern = "\""
|
||||
local function singleLineStringMeal(current,match)
|
||||
return function(words,_)
|
||||
current:insert(words,consumer:consume({
|
||||
[match] = function(words,_)
|
||||
return expression({"text",Expression({words})})
|
||||
end
|
||||
}))
|
||||
end
|
||||
end
|
||||
local multiLineStringOpenPattern = "%[%["
|
||||
local multiLineStringClosePattern = "%]%]"
|
||||
local function multiLineStringMeal(current,match)
|
||||
return function(words,_)
|
||||
current:insert(words,consumer:consume({
|
||||
["[\n\r]"] = function()
|
||||
error("Incomplete string literal")
|
||||
end,
|
||||
[match] = function(words,_)
|
||||
return expression({"text",Expression({words})})
|
||||
end
|
||||
}))
|
||||
end
|
||||
end
|
||||
local uriOpenPattern = "<"
|
||||
local uriClosePattern = ">"
|
||||
local function URIMeal(current,match)
|
||||
return function(words,_)
|
||||
current:insert(words,consumer:consume({
|
||||
["[\n\r]"] = function()
|
||||
current:error("Incomplete URI literal")
|
||||
end,
|
||||
[match] = function(path)
|
||||
return read(path)
|
||||
end
|
||||
}))
|
||||
end
|
||||
end
|
||||
local function expressionMeal(current)
|
||||
return function(words,_)
|
||||
current:insert(words,consumeExpression())
|
||||
end
|
||||
end
|
||||
function consumeBlock()
|
||||
local expressions = {}
|
||||
local current = expression()
|
||||
local loop = true
|
||||
while loop do
|
||||
local expr = consumer:consume({
|
||||
[uriOpenPattern] = URIMeal(current,uriClosePattern),
|
||||
[expressionOpenPattern] = expressionMeal(current),
|
||||
[multiLineStringOpenPattern] =
|
||||
multiLineStringMeal(
|
||||
current,multiLineStringClosePattern),
|
||||
[singleLineStringPattern] =
|
||||
singleLineStringMeal(
|
||||
current,singleLineStringPattern),
|
||||
[blockDelimiterPattern] = function(words,_)
|
||||
if current:empty() then
|
||||
error("Extravenous semicolon.")
|
||||
end
|
||||
table.insert(expressions,current)
|
||||
current = expression()
|
||||
end,
|
||||
[blockClosePattern] = function(words,_)
|
||||
current:insert(words)
|
||||
loop = false
|
||||
end
|
||||
})
|
||||
end
|
||||
if #current.items ~= 0 then
|
||||
table.insert(expressions,current)
|
||||
end
|
||||
return expression(expressions)
|
||||
end
|
||||
function consumeExpression()
|
||||
local current = expression()
|
||||
-- Loop, adding new expressions and words,
|
||||
-- until closing that is
|
||||
local loop = true
|
||||
while loop do
|
||||
local remaining = consumer:remaining()
|
||||
local expr = consumer:consume({
|
||||
[uriOpenPattern] = URIMeal(current,uriClosePattern),
|
||||
[expressionOpenPattern] = expressionMeal(current),
|
||||
[multiLineStringOpenPattern] =
|
||||
multiLineStringMeal(
|
||||
current,multiLineStringClosePattern),
|
||||
[singleLineStringPattern] =
|
||||
singleLineStringMeal(
|
||||
current,singleLineStringPattern),
|
||||
[expressionOpenPattern] = expressionMeal(current),
|
||||
[expressionClosePattern] = function(words,_)
|
||||
current:insert(words)
|
||||
loop = false
|
||||
end,
|
||||
[blockOpenPattern] = function(words,_)
|
||||
current:insert(
|
||||
words,
|
||||
consumeBlock()
|
||||
)
|
||||
end,
|
||||
["$"] = function(words,last)
|
||||
current:insert(remaining)
|
||||
loop = false
|
||||
end
|
||||
})
|
||||
-- This single line below made me procrastinate life for a total of 4 hours on instagram reels; lock in.
|
||||
-- if not expr then print("brk") break end -- Since now closing
|
||||
end
|
||||
return current
|
||||
end
|
||||
return consumeExpression()
|
||||
-- TODO: check for later expressions please?
|
||||
end
|
||||
|
||||
return _G
|
||||
225
main.lua
Normal file
225
main.lua
Normal file
@@ -0,0 +1,225 @@
|
||||
require("interpret")
|
||||
|
||||
local nelli_scope = Scope()
|
||||
local function B(abstract,callback)
|
||||
nelli_scope:insert(
|
||||
Binding(
|
||||
interpret(abstract),
|
||||
callback
|
||||
)
|
||||
)
|
||||
end
|
||||
local function R(scope,expression,chain,name)
|
||||
name = name or "?"
|
||||
assert_meta(Chain)(chain,"Arg 'chain' #1: %s")
|
||||
local chain = Chain(expression:link(name)):from(chain)
|
||||
return scope:run(expression,chain)
|
||||
end
|
||||
B("print (text)",function(s,e,c)
|
||||
log(R(s,e.text,c,"print: text"))
|
||||
end)
|
||||
B("text (literal)",function(s,e,c)
|
||||
return e.literal:text()
|
||||
end)
|
||||
B("do (expressions)",function(s,e,c)
|
||||
--assert_meta(Chain)(chain,"Arg 'chain' #3: %s")
|
||||
scope = Scope()
|
||||
scope.parent = s
|
||||
local res
|
||||
for item in iterate(e.expressions.items) do
|
||||
if type_of(item) ~= Expression then
|
||||
error(
|
||||
string.format(
|
||||
"Unexpected words '%s' in 'do' expression.",
|
||||
tostring(item)
|
||||
)
|
||||
)
|
||||
end
|
||||
res = R(s,item,c,"do: line")
|
||||
end
|
||||
return res
|
||||
end)
|
||||
B("error (text)",function(s,e,c)
|
||||
error("nel: "..R(s,e.text,c,"error: message"))
|
||||
end)
|
||||
B("if (predicate) then (expression)",function(s,e,c)
|
||||
if R(s,e.predicate,c,"if: predicate") then
|
||||
return R(s,e.expression,c,"if: result")
|
||||
end
|
||||
end)
|
||||
B("true",function(s,e,c) return true end)
|
||||
B("false",function(s,e,c) return false end)
|
||||
B("(constant) = (expression)",function(s,e,c)
|
||||
local value = R(s,e.expression,c,"constant: expression")
|
||||
s:insert(
|
||||
Binding(e.constant:text(),function(s,e,c)
|
||||
return value
|
||||
end)
|
||||
)
|
||||
end)
|
||||
B("format (string) with (terms)",function(s,e,c)
|
||||
local items = {}
|
||||
for expression in iterate(e.terms.items) do
|
||||
table.insert(items,R(s,expression),c,"format: term")
|
||||
end
|
||||
return string.format(
|
||||
R(s,e.string,c,"format: string"),
|
||||
table.unpack(items)
|
||||
)
|
||||
end)
|
||||
B("while (predicate) (expression)",function(s,e,c)
|
||||
while R(s,e.predicate,c,"while: predicate") do
|
||||
R(s,e.expression,c,"while: loop")
|
||||
end
|
||||
end)
|
||||
B("repeat (expression) while (predicate)",function(s,e,c)
|
||||
while true do
|
||||
R(s,e.expression,c,"repeat: expression")
|
||||
if not R(s,e.predicate,c,"repeat: predicate") then
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
B("repeat (expression) until (predicate)",function(s,e,c)
|
||||
while true do
|
||||
R(s,e.expression,c,"repeat: expression")
|
||||
if R(s,e.predicate,c,"repeat: predicate") then
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
B("new table",function(s,e,c)
|
||||
return {}
|
||||
end)
|
||||
B("(index) of (table)",function(s,e,c)
|
||||
return (R(s,e.table,"of: index"))[R(s,e.index,c,"of: table")]
|
||||
end)
|
||||
B("set (index) of (table) to (item)",function(s,e,c)
|
||||
R(s,e.table,c,"of-to: index")[R(s,e.index,c,"of-to: table")] = s:run(e.item,c,"of-to: item")
|
||||
end)
|
||||
B("remove (index) of (table)",function(s,e,c)
|
||||
(R(s,e.table,"remove: table"))[R(s,e.index,c,"remove: index")] = nil
|
||||
end)
|
||||
B("nil",function(s,e,c)
|
||||
return nil
|
||||
end)
|
||||
--[[ Note
|
||||
A macro executes in the same scope of the callee.
|
||||
A defintion executes in the scope it was defined.
|
||||
I would argue that macros are more unpredictable!
|
||||
However, they allow for some very clean code.
|
||||
--]]
|
||||
B("macro (macro) expands to (expression)",function(s,e,c)
|
||||
s:insert(Binding(e.abstract,function(_s,_e,_c)
|
||||
local scope = Scope()
|
||||
scope.parent = _s
|
||||
for identifier,expression in pairs(_e) do
|
||||
scope:insert(Binding(Expression({identifier}),R(_s,expression,_c)))
|
||||
end
|
||||
return R(scope,e.expression,_c,"macro: expression")
|
||||
end))
|
||||
end)
|
||||
B("define (abstract) as (expression)",function(s,e,c)
|
||||
s:insert(Binding(e.abstract,function(_s,_e,_c)
|
||||
local scope = Scope()
|
||||
scope.parent = s
|
||||
for identifier,expression in pairs(_e) do
|
||||
scope:insert(Binding(Expression({identifier}),R(_s,expression,_c)))
|
||||
end
|
||||
return R(scope,e.expression,_c,"define: expression")
|
||||
end))
|
||||
end)
|
||||
local List = class("List")
|
||||
function List:new(...)
|
||||
self.items = {...}
|
||||
end
|
||||
B("new list",function(s,e,c)
|
||||
return List()
|
||||
end)
|
||||
B("insert (item) into (list)",function(s,e,c)
|
||||
table.insert(R(s,e.list).items,R(s,e.item),c,c)
|
||||
end)
|
||||
B("insert (item) into (list) at (index)",function(s,e,c)
|
||||
table.insert(s:evaulate(e.list).items,R(s,e.index),R(s,item),c,c)
|
||||
end)
|
||||
B("remove (needle) from (haystack)",function(s,e,c)
|
||||
local tab = R(s,e.haystack,c)
|
||||
table.remove(tab,table.find(tab,R(s,e.needle)),c)
|
||||
end)
|
||||
B("remove from (haystack) at (index)",function(s,e,c)
|
||||
table.remove(R(s,e.list),R(s,e.index),c,c)
|
||||
end)
|
||||
|
||||
local Variable = class("Variable")
|
||||
function Variable:new()
|
||||
end
|
||||
function Variable:set(item)
|
||||
self.data = item
|
||||
end
|
||||
function Variable:get()
|
||||
return self.data
|
||||
end
|
||||
B("let (variable)",function(s,e,c)
|
||||
local variable = Variable()
|
||||
s:insert(
|
||||
Binding(e.variable,function(s,e,c)
|
||||
return variable
|
||||
end)
|
||||
)
|
||||
return variable
|
||||
end)
|
||||
B("set (variable) to (expression)",function(s,e,c)
|
||||
local var = R(s,e.variable,c,"set: identifier")
|
||||
local value = R(s,e.expression,c,"set: expression")
|
||||
assert_meta(Variable)(var)
|
||||
var:set(value)
|
||||
end)
|
||||
B("get (variable)",function(s,e,c)
|
||||
local var = R(s,e.variable,c)
|
||||
return var:get(value)
|
||||
end)
|
||||
|
||||
local args = {...}
|
||||
local function main()
|
||||
-- Take arguments as neli files to read and interpret
|
||||
local last
|
||||
local root = Chain(
|
||||
string.format(
|
||||
"in cmd 'nel {%s}'",
|
||||
table.concat(args,";")
|
||||
)
|
||||
)
|
||||
local success,result = xpcall(function()
|
||||
for _,parameter in pairs(args) do
|
||||
last = parameter
|
||||
local chain = Chain(
|
||||
string.format("in 'call: root' @'%s'",parameter)
|
||||
):from(root)
|
||||
local root = interpret(
|
||||
read(parameter),
|
||||
parameter,
|
||||
chain
|
||||
)
|
||||
assert_meta(Chain)(chain,"?")
|
||||
nelli_scope:run(root,chain)
|
||||
end
|
||||
end,function(msg)
|
||||
-- If this is a 'nel:' error, strip the line responsible
|
||||
-- Otherwise just return the whole traceback since something went really bad
|
||||
local match = msg:match("nel:%s(.*)")
|
||||
if match then
|
||||
return match
|
||||
else
|
||||
return "internal: "..debug.traceback(msg,2)
|
||||
end
|
||||
end)
|
||||
if not success then
|
||||
log(string.format(
|
||||
"nel: %s\nNEL traceback:\n\t%s",
|
||||
result,
|
||||
table.concat(root:flatten(),"\n\t")
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
||||
13
note
Normal file
13
note
Normal file
@@ -0,0 +1,13 @@
|
||||
I am thinking for the syntax in nel.
|
||||
How can we have multiple arguments for something?
|
||||
It feels rather stupid allowing this really.
|
||||
It's not very clear, a list is a list in any case.
|
||||
So maybe we really should just have another
|
||||
substatement, and forget about powering up
|
||||
the abstracts, for now? ...?
|
||||
|
||||
Well then... in that case.
|
||||
We only need to make sure scoping works.
|
||||
Which means, scopes inherit from eachother,
|
||||
and applying congruent bindings causes an error.
|
||||
I think.
|
||||
371
parse.lua
Normal file
371
parse.lua
Normal file
@@ -0,0 +1,371 @@
|
||||
require("helper")
|
||||
|
||||
-- CLASS DEFINITIONS
|
||||
-- A stream of text (kind of)
|
||||
-- with some helping methods
|
||||
Reader = class("Reader")
|
||||
-- Construct a reader from
|
||||
function Reader:new(text)
|
||||
assert_type("string")(text,"Arg 'text' #1: %s")
|
||||
self.text = text
|
||||
end
|
||||
-- Get the substring of the readers text in a bounds
|
||||
function Reader:get(first,final)
|
||||
assert_type("number")(first,"Arg 'first' #1: %s")
|
||||
assert_type("number","nil")(final,"Arg 'final' #2: %s")
|
||||
return self.text:sub(first,final)
|
||||
end
|
||||
-- Match on the reader's text from an index
|
||||
function Reader:find(pattern,init)
|
||||
assert_type("string")(pattern,"Arg 'pattern' #1: %s")
|
||||
assert_type("number","nil")(init,"Arg 'init' #2: %s")
|
||||
return self.text:find(pattern,init)
|
||||
end
|
||||
function Reader:match(pattern,init)
|
||||
assert_type("string")(pattern,"Arg 'pattern' #1: %s")
|
||||
assert_type("number")(init,"Arg 'init' #2: %s")
|
||||
return self.text:match(pattern,init)
|
||||
end
|
||||
|
||||
-- A range of text being considered
|
||||
-- like a cursor
|
||||
Range = class("Range")
|
||||
-- Construct a range of text from a reader and bounds
|
||||
function Range:new(reader,first,final)
|
||||
assert_meta(Reader)(reader)
|
||||
assert_type("number","nil")(first,"Arg 'first' #1: %s")
|
||||
assert_type("number","nil")(final,"Arg 'final' #2: %s")
|
||||
self.reader = reader
|
||||
self.first = first
|
||||
self.final = final
|
||||
end
|
||||
function Range:text()
|
||||
return self.reader:get(self.first or 1,self.final)
|
||||
end
|
||||
function Range:find(pattern)
|
||||
return self.reader:find(pattern,self.first)
|
||||
end
|
||||
function Range:move(first,final)
|
||||
assert_type("number","nil")(first,"Arg 'first' #1: %s")
|
||||
assert_type("number","nil")(final,"Arg 'final' #2: %s")
|
||||
self.first = first
|
||||
self.final = final
|
||||
-- note that this function returns itself
|
||||
return self
|
||||
end
|
||||
|
||||
Expression = class("Expression")
|
||||
-- Construct an expression from items (or not)
|
||||
function Expression:new(items)
|
||||
items = items or {}
|
||||
assert_type("table")(items)
|
||||
self.items = {}
|
||||
for item in iterate(items) do
|
||||
self:insert(item)
|
||||
end
|
||||
end
|
||||
function Expression:empty()
|
||||
return #self.items == 0
|
||||
end
|
||||
-- insert a word or sub expression into this one
|
||||
function Expression:insert(...)
|
||||
-- closures for nvim highlighting
|
||||
local function insert_word(word)
|
||||
-- Whitespace might confuse bindings, remove them
|
||||
local cleaned = word:match("^%s*(.-)%s*$")
|
||||
if cleaned ~= "" then
|
||||
table.insert(self.items,cleaned)
|
||||
end
|
||||
end
|
||||
local function insert_expression(expr)
|
||||
assert_meta(expr,Expression)
|
||||
table.insert(self.items,expr)
|
||||
end
|
||||
for item in iterate({...}) do
|
||||
case_of({
|
||||
table = function(expr)
|
||||
insert_expression(expr)
|
||||
end,
|
||||
string = function(word)
|
||||
insert_word(word)
|
||||
end
|
||||
})(type(item),item)
|
||||
end
|
||||
end
|
||||
function Expression:string()
|
||||
local parts = {}
|
||||
for index,item in pairs(self.items) do
|
||||
parts[index] = tostring(item)
|
||||
end
|
||||
return string.format("(%s)",table.concat(parts," "))
|
||||
end
|
||||
function Expression:abstract(infer)
|
||||
local parts = {}
|
||||
local count = 0
|
||||
for index,item in pairs(self.items) do
|
||||
parts[index] = case_of({
|
||||
[Expression] = function(item)
|
||||
local text
|
||||
if infer then
|
||||
text = item:text()
|
||||
if not text then
|
||||
count = count + 1
|
||||
text = tostring(count)
|
||||
end
|
||||
else
|
||||
text = "..."
|
||||
end
|
||||
return Expression({text})
|
||||
end,
|
||||
string = id
|
||||
})(type_of(item),item)
|
||||
end
|
||||
return Expression(parts)
|
||||
end
|
||||
function Expression:text()
|
||||
-- Get the text
|
||||
local text = self.items[#self.items]
|
||||
-- Ensure that the subexpression is valid abstract
|
||||
if type(text) ~= "string" then return end
|
||||
return text
|
||||
end
|
||||
function Expression:locate(str,uri)
|
||||
self.location = str
|
||||
self.source = uri
|
||||
return self
|
||||
end
|
||||
function Expression:link(msg)
|
||||
return string.format(
|
||||
"in '%s' @%s (%s)",
|
||||
msg,
|
||||
tostring(self),
|
||||
tostring(self.location or "?"),
|
||||
tostring(self.source or "?.nel")
|
||||
)
|
||||
end
|
||||
|
||||
Consumer = class("Consumer")
|
||||
function Consumer:new(content)
|
||||
self.range = Range(Reader(content))
|
||||
self.col = 1
|
||||
self.row = 1
|
||||
end
|
||||
function Consumer:remaining()
|
||||
return self.range:text()
|
||||
end
|
||||
function Consumer:mark()
|
||||
|
||||
end
|
||||
-- From a table of actions of patterns,
|
||||
-- consume the earliest pattern (index)
|
||||
-- and call the value
|
||||
function Consumer:consume(t)
|
||||
-- t: {string = function...}
|
||||
assert_type("table")(t,"Arg 't' #1: %s")
|
||||
if not next(t) then
|
||||
error("The match table cannot be empty!",2)
|
||||
end
|
||||
for index,func in pairs(t) do
|
||||
assert_type("string")(index,"Bad 't' index: %s")
|
||||
assert_type("function")(func,"Bad 't' item: %s")
|
||||
end
|
||||
local origin = self.range.first or 1
|
||||
-- Get the next earliest match
|
||||
local findex,ffinal,fpattern,ffunc,fexcess
|
||||
for pattern,new_func in pairs(t) do
|
||||
local new_index,new_final = self.range:find(pattern)
|
||||
if new_index and
|
||||
((not findex) or (findex > new_index))
|
||||
then
|
||||
if not new_index then return end
|
||||
findex = new_index
|
||||
ffinal = new_final
|
||||
fpattern = pattern
|
||||
ffunc = new_func
|
||||
fexcess = self.range.reader:get(
|
||||
self.range.first or 1,
|
||||
new_final - 1
|
||||
)
|
||||
end
|
||||
end
|
||||
-- Pass into the func
|
||||
if not ffunc then
|
||||
return nil
|
||||
end
|
||||
assert(findex) assert(ffinal)
|
||||
self.range:move(ffinal+1) -- Move range to after the match
|
||||
local fmatch = self.range.reader:match(fpattern,findex)
|
||||
-- Pass back consumed result to
|
||||
local sum = self.range.reader:get(origin+1,self.range.first)
|
||||
:gsub("\r\n","\n")
|
||||
:gsub("\r","\n")
|
||||
for char in sum:gmatch(".") do
|
||||
if char == "\n" then
|
||||
self.row = self.row + 1
|
||||
self.col = 1
|
||||
else
|
||||
self.col = self.col + 1
|
||||
end
|
||||
end
|
||||
return ffunc(fexcess,fmatch)
|
||||
end
|
||||
|
||||
Binding = class("Binding")
|
||||
-- Construct from a neli string to infer a binding
|
||||
-- note that you must wrap this definition in "(<def>)"!
|
||||
function Binding:new(expression,callback)
|
||||
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
|
||||
if type_of(callback) == "function" then
|
||||
self.callback = callback
|
||||
else
|
||||
self.value = callback
|
||||
end
|
||||
self.template = expression:abstract(true)
|
||||
end
|
||||
function Binding:string()
|
||||
return string.format("Binding %s: %s",self.template,tostring(self.value or self.callback))
|
||||
end
|
||||
function Binding:call(s,e,c)
|
||||
assert_meta(Scope)(s)
|
||||
assert_meta(Chain)(c)
|
||||
return self.value or self.callback(s,e,c)
|
||||
end
|
||||
function Binding:check(expression)
|
||||
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
|
||||
local candid = expression:abstract()
|
||||
local data = {}
|
||||
local index
|
||||
local loop = true
|
||||
if #candid.items ~= #self.template.items then
|
||||
return false
|
||||
end
|
||||
while loop do
|
||||
local old = index
|
||||
index,template_item = next(self.template.items,old)
|
||||
index,candid_item = next(candid.items,old)
|
||||
local result = case_of({
|
||||
[Expression] = function()
|
||||
-- item is not an expression, cannot bind
|
||||
if type_of(candid_item) ~= Expression then
|
||||
loop = false
|
||||
else
|
||||
data[template_item:text()] = expression.items[index]
|
||||
end
|
||||
end,
|
||||
string = function()
|
||||
-- words don't match, cannot bind
|
||||
if candid_item ~= template_item then
|
||||
loop = false
|
||||
end
|
||||
end,
|
||||
["nil"] = function()
|
||||
-- base case where both expressions terminate together
|
||||
if not candid_item then
|
||||
return data
|
||||
end
|
||||
end
|
||||
})(type_of(template_item))
|
||||
if result then return result end
|
||||
end
|
||||
end
|
||||
|
||||
Chain = class("Chain")
|
||||
function Chain:new(data)
|
||||
assert_type("string")(data,"Arg 'data' #1: %s")
|
||||
self.data = data
|
||||
end
|
||||
function Chain:from(previous)
|
||||
assert_meta(Chain)(previous,"Arg 'previous' #1: %s")
|
||||
previous.next = self
|
||||
self.previous = previous
|
||||
return self
|
||||
end
|
||||
function Chain:string()
|
||||
return string.format("Chain:%s",table.concat(self:flatten(),", "))
|
||||
end
|
||||
function Chain:flatten(tab)
|
||||
tab = tab or {}
|
||||
if self.next then
|
||||
self.next:flatten(tab)
|
||||
end
|
||||
table.insert(tab,self.data)
|
||||
return tab
|
||||
end
|
||||
|
||||
-- The scope of a neli expression under evaluation
|
||||
-- without exact faith to traditional language scopes
|
||||
Scope = class("Scope")
|
||||
function Scope:new(bindings)
|
||||
self.bindings = {}
|
||||
for binding in iterate(bindings or {}) do
|
||||
self:insert(binding)
|
||||
end
|
||||
end
|
||||
function Scope:string()
|
||||
return string.format("Scope: {\n\t%s\n}",table.concat(map(self.bindings,tostring),"\n\t"))
|
||||
end
|
||||
function Scope:insert(binding)
|
||||
assert_meta(Binding)(binding,"Arg 'binding' #1: %s")
|
||||
for competitor in iterate(self.bindings) do
|
||||
if competitor:check(binding.template) then
|
||||
nerror("Conflicting sibling bindings in scope.")
|
||||
end
|
||||
end
|
||||
table.insert(self.bindings,binding)
|
||||
end
|
||||
function Scope:run(expression,chain,original)
|
||||
original = original or self
|
||||
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
|
||||
assert_meta(Chain)(chain,"Arg 'chain' #2: %s")
|
||||
-- Could be multiple bindings so make a table
|
||||
local binds = {}
|
||||
for binding in iterate(self.bindings) do
|
||||
local bind = binding:check(expression)
|
||||
-- Insert into table
|
||||
if bind then binds[binding] = bind end
|
||||
end
|
||||
local binding,bind = next(binds)
|
||||
-- Check for multiple
|
||||
if not bind then
|
||||
if self.parent then
|
||||
return self.parent:run(expression,chain,self)
|
||||
else
|
||||
local extra = ""
|
||||
if #expression.items == 1 then
|
||||
local item = expression.items[1]
|
||||
if type_of(item) == Expression then
|
||||
extra = extra .. "\nNote: This expression is formed (just bracketed) of another single expression. Maybe too many brackets were used here?"
|
||||
else
|
||||
extra = extra .. "\n\tNote: This is a 'text' expression, it has no subexpressions. It is most likely that an expected object definition was not bound to the scope or there is a typo."
|
||||
end
|
||||
end
|
||||
error(
|
||||
string.format(
|
||||
"nel: No binding for '%s' in scope.",
|
||||
tostring(expression:abstract()),
|
||||
extra
|
||||
),3
|
||||
)
|
||||
end
|
||||
-- Should never happen!
|
||||
elseif next(binds,binding) then
|
||||
local candidates = {}
|
||||
for binding,bind in pairs(binds) do
|
||||
table.insert(candidates,tostring(binding.template))
|
||||
end
|
||||
error(
|
||||
string.format(
|
||||
"nel: Ambiguous bindings in scope for '%s': %s",
|
||||
tostring(expression:abstract()),
|
||||
table.concat(candidates)
|
||||
)
|
||||
,2)
|
||||
else
|
||||
return binding:call(
|
||||
original,
|
||||
bind,
|
||||
chain
|
||||
)
|
||||
end
|
||||
end
|
||||
return _G
|
||||
205
session.vim
Normal file
205
session.vim
Normal file
@@ -0,0 +1,205 @@
|
||||
let SessionLoad = 1
|
||||
let s:so_save = &g:so | let s:siso_save = &g:siso | setg so=0 siso=0 | setl so=-1 siso=-1
|
||||
let v:this_session=expand("<sfile>:p")
|
||||
silent only
|
||||
silent tabonly
|
||||
cd ~/Programming/nel/1
|
||||
if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''
|
||||
let s:wipebuf = bufnr('%')
|
||||
endif
|
||||
let s:shortmess_save = &shortmess
|
||||
if &shortmess =~ 'A'
|
||||
set shortmess=aoOA
|
||||
else
|
||||
set shortmess=aoO
|
||||
endif
|
||||
badd +167 main.lua
|
||||
badd +27 parse.lua
|
||||
badd +0 term://~/Programming/nel/1//1946:/usr/bin/zsh
|
||||
badd +0 interpret.lua
|
||||
badd +0 test.nel
|
||||
badd +191 helper.lua
|
||||
argglobal
|
||||
%argdel
|
||||
$argadd ~/Programming/nel/1
|
||||
edit parse.lua
|
||||
let s:save_splitbelow = &splitbelow
|
||||
let s:save_splitright = &splitright
|
||||
set splitbelow splitright
|
||||
wincmd _ | wincmd |
|
||||
vsplit
|
||||
1wincmd h
|
||||
wincmd _ | wincmd |
|
||||
split
|
||||
1wincmd k
|
||||
wincmd _ | wincmd |
|
||||
vsplit
|
||||
1wincmd h
|
||||
wincmd w
|
||||
wincmd w
|
||||
wincmd w
|
||||
wincmd _ | wincmd |
|
||||
split
|
||||
1wincmd k
|
||||
wincmd w
|
||||
let &splitbelow = s:save_splitbelow
|
||||
let &splitright = s:save_splitright
|
||||
wincmd t
|
||||
let s:save_winminheight = &winminheight
|
||||
let s:save_winminwidth = &winminwidth
|
||||
set winminheight=0
|
||||
set winheight=1
|
||||
set winminwidth=0
|
||||
set winwidth=1
|
||||
exe '1resize ' . ((&lines * 24 + 19) / 39)
|
||||
exe 'vert 1resize ' . ((&columns * 60 + 90) / 181)
|
||||
exe '2resize ' . ((&lines * 24 + 19) / 39)
|
||||
exe 'vert 2resize ' . ((&columns * 60 + 90) / 181)
|
||||
exe '3resize ' . ((&lines * 12 + 19) / 39)
|
||||
exe 'vert 3resize ' . ((&columns * 121 + 90) / 181)
|
||||
exe '4resize ' . ((&lines * 24 + 19) / 39)
|
||||
exe 'vert 4resize ' . ((&columns * 59 + 90) / 181)
|
||||
exe '5resize ' . ((&lines * 12 + 19) / 39)
|
||||
exe 'vert 5resize ' . ((&columns * 59 + 90) / 181)
|
||||
argglobal
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldexpr=v:lua.vim.treesitter.foldexpr()
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldenable
|
||||
silent! normal! zE
|
||||
let &fdl = &fdl
|
||||
let s:l = 267 - ((8 * winheight(0) + 12) / 24)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
keepjumps exe s:l
|
||||
normal! zt
|
||||
keepjumps 267
|
||||
normal! 019|
|
||||
lcd ~/Programming/nel/1
|
||||
wincmd w
|
||||
argglobal
|
||||
if bufexists(fnamemodify("~/Programming/nel/1/interpret.lua", ":p")) | buffer ~/Programming/nel/1/interpret.lua | else | edit ~/Programming/nel/1/interpret.lua | endif
|
||||
if &buftype ==# 'terminal'
|
||||
silent file ~/Programming/nel/1/interpret.lua
|
||||
endif
|
||||
balt ~/Programming/nel/1/parse.lua
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldexpr=v:lua.vim.treesitter.foldexpr()
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldenable
|
||||
silent! normal! zE
|
||||
let &fdl = &fdl
|
||||
let s:l = 58 - ((15 * winheight(0) + 12) / 24)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
keepjumps exe s:l
|
||||
normal! zt
|
||||
keepjumps 58
|
||||
normal! 034|
|
||||
lcd ~/Programming/nel/1
|
||||
wincmd w
|
||||
argglobal
|
||||
if bufexists(fnamemodify("term://~/Programming/nel/1//1946:/usr/bin/zsh", ":p")) | buffer term://~/Programming/nel/1//1946:/usr/bin/zsh | else | edit term://~/Programming/nel/1//1946:/usr/bin/zsh | endif
|
||||
if &buftype ==# 'terminal'
|
||||
silent file term://~/Programming/nel/1//1946:/usr/bin/zsh
|
||||
endif
|
||||
balt ~/Programming/nel/1/parse.lua
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldexpr=0
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldenable
|
||||
let s:l = 947 - ((11 * winheight(0) + 6) / 12)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
keepjumps exe s:l
|
||||
normal! zt
|
||||
keepjumps 947
|
||||
normal! 040|
|
||||
lcd ~/Programming/nel/1
|
||||
wincmd w
|
||||
argglobal
|
||||
if bufexists(fnamemodify("~/Programming/nel/1/main.lua", ":p")) | buffer ~/Programming/nel/1/main.lua | else | edit ~/Programming/nel/1/main.lua | endif
|
||||
if &buftype ==# 'terminal'
|
||||
silent file ~/Programming/nel/1/main.lua
|
||||
endif
|
||||
balt ~/Programming/nel/1/helper.lua
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldexpr=v:lua.vim.treesitter.foldexpr()
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldenable
|
||||
silent! normal! zE
|
||||
let &fdl = &fdl
|
||||
let s:l = 178 - ((21 * winheight(0) + 12) / 24)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
keepjumps exe s:l
|
||||
normal! zt
|
||||
keepjumps 178
|
||||
normal! 012|
|
||||
lcd ~/Programming/nel/1
|
||||
wincmd w
|
||||
argglobal
|
||||
if bufexists(fnamemodify("~/Programming/nel/1/test.nel", ":p")) | buffer ~/Programming/nel/1/test.nel | else | edit ~/Programming/nel/1/test.nel | endif
|
||||
if &buftype ==# 'terminal'
|
||||
silent file ~/Programming/nel/1/test.nel
|
||||
endif
|
||||
balt ~/Programming/nel/1/main.lua
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldexpr=0
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldenable
|
||||
silent! normal! zE
|
||||
let &fdl = &fdl
|
||||
let s:l = 5 - ((4 * winheight(0) + 6) / 12)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
keepjumps exe s:l
|
||||
normal! zt
|
||||
keepjumps 5
|
||||
normal! 0
|
||||
lcd ~/Programming/nel/1
|
||||
wincmd w
|
||||
3wincmd w
|
||||
exe '1resize ' . ((&lines * 24 + 19) / 39)
|
||||
exe 'vert 1resize ' . ((&columns * 60 + 90) / 181)
|
||||
exe '2resize ' . ((&lines * 24 + 19) / 39)
|
||||
exe 'vert 2resize ' . ((&columns * 60 + 90) / 181)
|
||||
exe '3resize ' . ((&lines * 12 + 19) / 39)
|
||||
exe 'vert 3resize ' . ((&columns * 121 + 90) / 181)
|
||||
exe '4resize ' . ((&lines * 24 + 19) / 39)
|
||||
exe 'vert 4resize ' . ((&columns * 59 + 90) / 181)
|
||||
exe '5resize ' . ((&lines * 12 + 19) / 39)
|
||||
exe 'vert 5resize ' . ((&columns * 59 + 90) / 181)
|
||||
tabnext 1
|
||||
if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0 && getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'
|
||||
silent exe 'bwipe ' . s:wipebuf
|
||||
endif
|
||||
unlet! s:wipebuf
|
||||
set winheight=1 winwidth=20
|
||||
let &shortmess = s:shortmess_save
|
||||
let &winminheight = s:save_winminheight
|
||||
let &winminwidth = s:save_winminwidth
|
||||
let s:sx = expand("<sfile>:p:r")."x.vim"
|
||||
if filereadable(s:sx)
|
||||
exe "source " . fnameescape(s:sx)
|
||||
endif
|
||||
let &g:so = s:so_save | let &g:siso = s:siso_save
|
||||
set hlsearch
|
||||
doautoall SessionLoadPost
|
||||
unlet SessionLoad
|
||||
" vim: set ft=vim :
|
||||
Reference in New Issue
Block a user