Proto: meaning etc.

This commit is contained in:
2026-03-08 18:59:45 +00:00
parent d7ca3739aa
commit e6f3b5c77f
11 changed files with 287 additions and 268 deletions

View File

@@ -1,3 +1,3 @@
# neli # neli
A silly language to help silly people understand silly things. A silly language to help silly people understand silly things.

View File

@@ -27,8 +27,15 @@ end
id = function(...) return ... end id = function(...) return ... end
function union(t0,t1) function union(t0,t1)
for index,item in pairs(t0) do for index,item in pairs(t1) do
t0[index] = t1 t0[index] = t1[index]
end
return t0
end
function union_all(t0,...)
for _,t1 in pairs({...}) do
union(t0,t1)
end end
return t0 return t0
end end
@@ -193,7 +200,7 @@ function read(path)
local file = io.open(path) local file = io.open(path)
if not file then if not file then
error( error(
string.match("Could not open file '%s'.",path),3 string.format("Could not open file '%s'.",path),3
) )
end end
local content = file:read("a") local content = file:read("a")

3
nellie/helper.nel Normal file
View File

@@ -0,0 +1,3 @@
out "huh?";
// proto:
(FUCK!)

View File

@@ -1,6 +1,7 @@
require("nelli") local path = ...
require("nellie.proto")
local function main(args) function Run(args)
-- Take arguments as neli files to read and interpret -- Take arguments as neli files to read and interpret
local root = Chain( local root = Chain(
string.format( string.format(
@@ -13,15 +14,14 @@ local function main(args)
string.format("in 'call: root' @'%s'",parameter) string.format("in 'call: root' @'%s'",parameter)
):from(root) ):from(root)
root:call(function() root:call(function()
local root = interpret( local root = Parse(
read(parameter), read(parameter),
parameter, parameter,
chain chain
) )
NelliScope:run(root,chain) for _,expression in pairs(root.items) do
NelliScope:run(expression,Chain(expression:link("root: do")):from(chain))
end
end) end)
end end
end end
-- A list of arguments passed into this
main({...})

View File

@@ -1,59 +1,6 @@
require("helper") require("nellie.helper")
-- CLASS DEFINITIONS -- 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") Expression = class("Expression")
-- Construct an expression from items (or not) -- Construct an expression from items (or not)
function Expression:new(items) function Expression:new(items)
@@ -191,76 +138,6 @@ function Expression:link(msg)
) )
end end
--[[
A class which parses text using the 'consumer-meal'
model.
]]
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") Binding = class("Binding")
-- Construct from a neli string to infer a binding -- Construct from a neli string to infer a binding
-- note that you must wrap this definition in "(<def>)"! -- note that you must wrap this definition in "(<def>)"!
@@ -362,6 +239,7 @@ function Chain:pcall(func)
local success,result = xpcall(func,function(msg) local success,result = xpcall(func,function(msg)
-- If this is a 'nel:' error, strip the line responsible -- If this is a 'nel:' error, strip the line responsible
-- Otherwise just return the whole traceback since something went really bad -- Otherwise just return the whole traceback since something went really bad
if not msg then msg = "no message provided" end
local match = msg:match("nel:%s(.*)") local match = msg:match("nel:%s(.*)")
if match then if match then
return match return match
@@ -400,7 +278,13 @@ function Scope:insert(binding)
assert_meta(Binding)(binding,"Arg 'binding' #1: %s") assert_meta(Binding)(binding,"Arg 'binding' #1: %s")
for competitor in iterate(self.bindings) do for competitor in iterate(self.bindings) do
if competitor:check(binding.template) then if competitor:check(binding.template) then
error(string.format("nel: Conflicting sibling bindings in scope for '%s'.",binding.template)) error(
string.format(
"nel: Conflicting sibling binding in scope for '%s': '%s'.",
binding.template,
competitor.template
)
)
end end
end end
table.insert(self.bindings,binding) table.insert(self.bindings,binding)

View File

@@ -1,31 +1,135 @@
require("interpreter") require("nellie.interpreter")
-- 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
--[[
A class which parses text using the 'consumer-meal'
model.
]]
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:consumePatterns(patterns,state)
-- t: {string = function...}
assert_type("table")(patterns,"Arg 't' #1: %s")
if not next(patterns) then
error("The match table cannot be empty!",2)
end
for index,func in pairs(patterns) 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 -- TODO: make this not be a bunch of locals. New class?
for pattern,new_func in pairs(patterns) 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,state)
end
-- TODO: This should really be a separate API and module
-- please break it out
-- Interpret some text -- Interpret some text
function Parse(content,uri,chain) function Parse(content,uri,chain)
-- LOCALS -- 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 -- So that all functions have access to eachother
local consume, local consume,
consumeExpression, consumeExpression,
processWords, consumeBlock
processClosure,
processOpening,
consumeBlock,
consumeText,
consumeNumber
local consumer = Consumer(content) local consumer = Consumer(content)
local function expression(...) local function expression(...)
@@ -38,93 +142,99 @@ function Parse(content,uri,chain)
end end
local baseExpression = expression() local baseExpression = expression()
-- FUNCTIONS local patterns = {}
-- Consume an expression, with its sub expressions patterns.contentClose = "$"
-- given that an opening has just been consumed -- The pattern for words inbetween expressions
local singleLineStringPattern = "\"" patterns.expressionWord = "(.*)"
local function singleLineStringMeal(current,match) -- The patterns to open and close expressions,
return function(words,_) -- the words scattered inbetween them will be matches
current:insert(words,consumer:consume({ patterns.expressionOpen = "%("
[match] = function(words,_) patterns.expressionClose = "%)"
patterns.subExpressionOpen = "%:"
patterns.subExpressionClose = "%,"
patterns.blockOpen = "%{"
patterns.blockClose = "%}"
patterns.blockDelimiter = "[;]+"
patterns.newLine = "[\n\r]+"
patterns.singleLineString = "\""
patterns.named = "'"
patterns.multiLineStringOpen = "%[%["
patterns.multiLineStringClose = "%]%]"
patterns.uriOpen = "<"
patterns.uriClose = ">"
patterns.singleLineComment = "//"
patterns.multiLineCommentOpen = "/%*"
patterns.multiLineCommentOpen = "%*/"
patterns.colonSyntax = ":"
-- TODO: I don't like that this structure does: open(stuff;close) instead of open(stuff)close
-- TODO: current:insert() is repeated in EVERY meal. I wonder if this should be fixed -__-
local expressionMeals = {
[patterns.singleLineString] = function(words,_,current)
current:insert(words,consumer:consumePatterns({
[patterns.singleLineString] = function(words,_)
return expression({"text",Expression({words})}) return expression({"text",Expression({words})})
end end
})) }))
end end,
end [patterns.named] = function(words,_,current)
local multiLineStringOpenPattern = "%[%[" current:insert(words,consumer:consumePatterns({
local multiLineStringClosePattern = "%]%]" [patterns.named] = function(words,_)
local function multiLineStringMeal(current,match) return expression({"the",Expression({words})})
return function(words,_) end
current:insert(words,consumer:consume({ }))
end,
[patterns.multiLineStringOpen] = function(words,_,current)
current:insert(words,consumer:consumePatterns({
["[\n\r]"] = function() ["[\n\r]"] = function()
error("Incomplete string literal") error("Incomplete string literal")
end, end,
[match] = function(words,_) [patterns.multiLineStringClose] = function(words,_)
return expression({"text",Expression({words})}) return expression({"text",Expression({words})})
end end
})) }))
end end,
end [patterns.uriOpen] = function(words,_,current)
local uriOpenPattern = "<" current:insert(words,consumer:consumePatterns({
local uriClosePattern = ">"
local function URIMeal(current,match)
return function(words,_)
current:insert(words,consumer:consume({
["[\n\r]"] = function() ["[\n\r]"] = function()
current:error("Incomplete URI literal") current:error("Incomplete URI literal")
end, end,
[match] = function(path) [patterns.uriClose] = function(path)
return read(path) return read(path)
end end
})) }))
end end,
end [patterns.expressionOpen] = function(words,_,current)
local function expressionMeal(current)
return function(words,_)
current:insert(words,consumeExpression()) current:insert(words,consumeExpression())
end end,
end [patterns.singleLineComment] = function(words,_,current)
local singleLineCommentPattern = "//" --current:insert(words) -- Consume what was left
local function singleLineCommentMeal(current) consumer:consumePatterns({
return function(words,_) [patterns.newLine] = function() end
current:insert(words) -- Consume what was left
consumer:consume({
["[\n\r]"] = function() end
}) })
end end,
end [patterns.multiLineCommentOpen] = function(words,_,current)
local multiLineCommentPattern = "/%*"
local function multiLineCommentMeal(current)
return function(words,_) -- consume what was left
current:insert(words) current:insert(words)
consumer:consume({ consumer:consumePatterns({
["%*/"] = function() end [patterns.multiLineCommentClose] = function() end
}) })
end end,
end [patterns.colonSyntax] = function(words,_,current)
local colonSyntaxPattern = ":" current:insert(words,consumeExpression())
local function colonSyntaxMeal(current) end,
return function(words,_) [patterns.blockOpen] = function(words,_,current)
current:insert(words,consumeColonExpression()) current:insert(words,consumeBlock())
end end,
end }
function consumeBlock() -- Consume a {} block of code
function consumeBlock(closing_pattern)
closing_pattern = closing_pattern or patterns.blockClose
local expressions = {} local expressions = {}
local current = expression() local current = expression()
local loop = true local loop = true
while loop do while loop do
local expr = consumer:consume({ local remaining = consumer:remaining()
[multiLineCommentPattern] = multiLineCommentMeal(current), local expr = consumer:consumePatterns(union(expressionMeals,{
[singleLineCommentPattern] = singleLineCommentMeal(current), [patterns.blockDelimiter] = function(words,_)
[uriOpenPattern] = URIMeal(current,uriClosePattern),
[expressionOpenPattern] = expressionMeal(current),
[multiLineStringOpenPattern] =
multiLineStringMeal(
current,multiLineStringClosePattern),
[singleLineStringPattern] =
singleLineStringMeal(
current,singleLineStringPattern),
[blockDelimiterPattern] = function(words,_)
current:insert(words) current:insert(words)
if current:empty() then if current:empty() then
--error("Extravenous semicolon.") --error("Extravenous semicolon.")
@@ -133,17 +243,11 @@ function Parse(content,uri,chain)
end end
current = expression() current = expression()
end, end,
[blockClosePattern] = function(words,_) [closing_pattern] = function(words,_)
current:insert(words) current:insert(words)
loop = false loop = false
end, end,
[blockOpenPattern] = function(words,_) }),current)
current:insert(
words,
consumeBlock()
)
end,
})
end end
if #current.items ~= 0 then if #current.items ~= 0 then
table.insert(expressions,current) table.insert(expressions,current)
@@ -157,38 +261,17 @@ function Parse(content,uri,chain)
local loop = true local loop = true
while loop do while loop do
local remaining = consumer:remaining() local remaining = consumer:remaining()
local expr = consumer:consume({ local expr = consumer:consumePatterns(union(expressionMeals,{
[multiLineCommentPattern] = multiLineCommentMeal(current), [patterns.expressionClose] = function(words,_,_current)
[singleLineCommentPattern] = singleLineCommentMeal(current),
[uriOpenPattern] = URIMeal(current,uriClosePattern),
[expressionOpenPattern] = expressionMeal(current),
[multiLineStringOpenPattern] =
multiLineStringMeal(
current,multiLineStringClosePattern),
[singleLineStringPattern] =
singleLineStringMeal(
current,singleLineStringPattern),
[expressionOpenPattern] = expressionMeal(current),
[expressionClosePattern] = function(words,_)
current:insert(words) current:insert(words)
loop = false loop = false
end,
[blockOpenPattern] = function(words,_)
current:insert(
words,
consumeBlock()
)
end,
["$"] = function(words,last)
current:insert(remaining)
loop = false
end end
}) }),current)
-- This single line below made me procrastinate life for a total of 4 hours on instagram reels; lock in. -- 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 -- if not expr then print("brk") break end -- Since now closing
end end
return current return current
end end
return consumeExpression() return consumeBlock(patterns.contentClose)
-- TODO: check for later expressions please? -- TODO: check for later expressions please?
end end

View File

@@ -1,13 +1,14 @@
-- Prototype for nel -- Prototype for nel
require("interpreter") require("nellie.parser")
function Bind(scope,abstract,callback) function Bind(scope,abstract,callback)
scope:insert( for _,expression in pairs(Parse(abstract)) do
Binding( assert_meta(Expression)(expression)
Parse(abstract), scope:insert(Binding(
expression,
callback callback
) ))
) end
end end
function Run(scope,expression,chain,name) function Run(scope,expression,chain,name)
name = name or "?" name = name or "?"
@@ -17,6 +18,13 @@ function Run(scope,expression,chain,name)
local chain = Chain(expression:link(name)):from(chain) local chain = Chain(expression:link(name)):from(chain)
return scope:run(expression,chain) return scope:run(expression,chain)
end end
function Do(scope,expression,chain,name)
local latest
for index,item in pairs(expression.items) do
latest = Run(scope,item,chain,"do: "+tostring(index))
end
return latest
end
NelliScope = Scope() NelliScope = Scope()
local ns = NelliScope local ns = NelliScope
@@ -270,13 +278,37 @@ end)
Bind(ns,"let (binding) in (scope)",function(s,e,c) Bind(ns,"let (binding) in (scope)",function(s,e,c)
end) end)
Bind(ns,"text (literal)",function(s,e,c) return e.literal:text() end)
Bind(ns,"let ((named (name)) be (value))",function(s,e,c) end)
Bind(ns,"new table",function(s,e,c) return {} end)
]] ]]
Bind(ns,"print (text)",function(s,e,c) Bind(ns,"(closed)",function(s,e,c) return Run(s,e.closed,c,"(...)") end)
log(Run(s,e.text,c,"print: text")) Bind(ns,"log (text)",function(s,e,c) log(Run(s,e.text,c,"print: text")) end)
Bind(ns,"this scope",function(s,e,c) return s end)
Bind(ns,"new scope",function(s,e,c) return Scope() end) -- TODO: chains?!!?
Bind(ns,"index (table) with (key)",function(s,e,c) return Run(s,e,c,"") end)
Bind(ns,[[
(input) means do (output);
(input) in (input_scope) means do (output);
(input) means do (output) in (output_scope);
(input) in (input_scope) means do (output) in (output_scope)
]],function(s,e,c1) -- A substitution
local input_scope = s
if e.input_scope then input_scope = Run(s,e.input_scope,c1,"means: input scope") end
local output_scope = s
if e.output_scope then output_scope = Run(s,e.output_scope,c2,"means: output scope") end
Bind(e.input_scope,e.input,function(s,e,c2)
return Do(e.output_scope,e.output,c2) -- TODO: chains?!
end)
end) end)
Bind(ns,"do (stuff) in (scope); do (stuff)",function(s,e,c)
s = s or Scope()
return Do(s,e.stuff,c)
end)
Bind(ns,"new table",function(s,e,c) return {} end)
local Hpath = "nel/helper.nel" local Hpath = "nellie/helper.nel"
local Hchain = Chain("in internal: "..Hpath) local Hchain = Chain("in internal: "..Hpath)
local success,result = Hchain:call(function(chain) local success,result = Hchain:call(function(chain)
Run(NelliScope,Parse(read(Hpath),Hpath,chain),chain) Run(NelliScope,Parse(read(Hpath),Hpath,chain),chain)
@@ -284,8 +316,6 @@ end)
--[[ Documentation: --[[ Documentation:
(log (text)) -> Log text to the output stream (print?) (log (text)) -> Log text to the output stream (print?)
(a new scope) -> Becomes a created new scope (a new scope) -> Becomes a created new scope
(this scope) -> Becomes the current scope (this scope) -> Becomes the current scope
(macro) expands to (expansion) -> Becomes a binding which makes (macro) expands to (expansion) -> Becomes a binding which makes

View File

@@ -4,5 +4,5 @@ then
echo "lua could not be found" echo "lua could not be found"
exit 1 exit 1
else else
lua index.lua "$@" lua -i -v -W -l nellie -e Run "$@"
fi fi

0
test
View File

8
test.sh Executable file
View File

@@ -0,0 +1,8 @@
#/bin/sh
if ! command -v lua &> /dev/null
then
echo "lua could not be found"
exit 1
else
lua -i -v -W -l nellie tests/test.lua
fi

View File

@@ -1 +1,5 @@
require("") print("doing some tests")
local function test(file,result)
end
test("print.nel",[[hello world!]])