From e6f3b5c77f26db4d3bf5bcadef5685f581c14ec0 Mon Sep 17 00:00:00 2001 From: Christian Lincoln Date: Sun, 8 Mar 2026 18:59:45 +0000 Subject: [PATCH] Proto: meaning etc. --- README.md | 2 +- nellie/helper.lua | 13 +- nellie/helper.nel | 3 + nellie/{index.lua => init.lua} | 16 +- nellie/interpreter.lua | 134 +------------- nellie/parser.lua | 319 +++++++++++++++++++++------------ nellie/proto.lua | 52 ++++-- nellie/nellie => run.sh | 2 +- test | 0 test.sh | 8 + tests/test.lua | 6 +- 11 files changed, 287 insertions(+), 268 deletions(-) create mode 100644 nellie/helper.nel rename nellie/{index.lua => init.lua} (63%) rename nellie/nellie => run.sh (70%) delete mode 100644 test create mode 100755 test.sh diff --git a/README.md b/README.md index 6c996ff..450792d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # neli -A silly language to help silly people understand silly things. +A silly language to help silly people understand silly things. \ No newline at end of file diff --git a/nellie/helper.lua b/nellie/helper.lua index f93d9c3..a3376e1 100644 --- a/nellie/helper.lua +++ b/nellie/helper.lua @@ -27,8 +27,15 @@ end id = function(...) return ... end function union(t0,t1) - for index,item in pairs(t0) do - t0[index] = t1 + for index,item in pairs(t1) do + t0[index] = t1[index] + end + return t0 +end + +function union_all(t0,...) + for _,t1 in pairs({...}) do + union(t0,t1) end return t0 end @@ -193,7 +200,7 @@ function read(path) local file = io.open(path) if not file then error( - string.match("Could not open file '%s'.",path),3 + string.format("Could not open file '%s'.",path),3 ) end local content = file:read("a") diff --git a/nellie/helper.nel b/nellie/helper.nel new file mode 100644 index 0000000..eacd290 --- /dev/null +++ b/nellie/helper.nel @@ -0,0 +1,3 @@ +out "huh?"; +// proto: +(FUCK!) \ No newline at end of file diff --git a/nellie/index.lua b/nellie/init.lua similarity index 63% rename from nellie/index.lua rename to nellie/init.lua index b71cb67..03ef478 100644 --- a/nellie/index.lua +++ b/nellie/init.lua @@ -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 local root = Chain( string.format( @@ -13,15 +14,14 @@ local function main(args) string.format("in 'call: root' @'%s'",parameter) ):from(root) root:call(function() - local root = interpret( + local root = Parse( read(parameter), parameter, 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 - --- A list of arguments passed into this -main({...}) \ No newline at end of file +end \ No newline at end of file diff --git a/nellie/interpreter.lua b/nellie/interpreter.lua index c74df9b..7dc5788 100644 --- a/nellie/interpreter.lua +++ b/nellie/interpreter.lua @@ -1,59 +1,6 @@ -require("helper") +require("nellie.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) @@ -191,76 +138,6 @@ function Expression:link(msg) ) 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") -- Construct from a neli string to infer a binding -- note that you must wrap this definition in "()"! @@ -362,6 +239,7 @@ function Chain:pcall(func) local success,result = xpcall(func,function(msg) -- If this is a 'nel:' error, strip the line responsible -- 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(.*)") if match then return match @@ -400,7 +278,13 @@ 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 - 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 table.insert(self.bindings,binding) diff --git a/nellie/parser.lua b/nellie/parser.lua index 0b6f54d..2afa862 100644 --- a/nellie/parser.lua +++ b/nellie/parser.lua @@ -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 function Parse(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 - + consumeBlock local consumer = Consumer(content) local function expression(...) @@ -38,93 +142,99 @@ function Parse(content,uri,chain) 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,_) + local patterns = {} + patterns.contentClose = "$" + -- The pattern for words inbetween expressions + patterns.expressionWord = "(.*)" + -- The patterns to open and close expressions, + -- the words scattered inbetween them will be matches + patterns.expressionOpen = "%(" + 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})}) end })) - end - end - local multiLineStringOpenPattern = "%[%[" - local multiLineStringClosePattern = "%]%]" - local function multiLineStringMeal(current,match) - return function(words,_) - current:insert(words,consumer:consume({ + end, + [patterns.named] = function(words,_,current) + current:insert(words,consumer:consumePatterns({ + [patterns.named] = function(words,_) + return expression({"the",Expression({words})}) + end + })) + end, + [patterns.multiLineStringOpen] = function(words,_,current) + current:insert(words,consumer:consumePatterns({ ["[\n\r]"] = function() error("Incomplete string literal") end, - [match] = function(words,_) + [patterns.multiLineStringClose] = 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({ + end, + [patterns.uriOpen] = function(words,_,current) + current:insert(words,consumer:consumePatterns({ ["[\n\r]"] = function() current:error("Incomplete URI literal") end, - [match] = function(path) + [patterns.uriClose] = function(path) return read(path) end })) - end - end - local function expressionMeal(current) - return function(words,_) + end, + [patterns.expressionOpen] = function(words,_,current) current:insert(words,consumeExpression()) - end - end - local singleLineCommentPattern = "//" - local function singleLineCommentMeal(current) - return function(words,_) - current:insert(words) -- Consume what was left - consumer:consume({ - ["[\n\r]"] = function() end + end, + [patterns.singleLineComment] = function(words,_,current) + --current:insert(words) -- Consume what was left + consumer:consumePatterns({ + [patterns.newLine] = function() end }) - end - end - local multiLineCommentPattern = "/%*" - local function multiLineCommentMeal(current) - return function(words,_) -- consume what was left + end, + [patterns.multiLineCommentOpen] = function(words,_,current) current:insert(words) - consumer:consume({ - ["%*/"] = function() end + consumer:consumePatterns({ + [patterns.multiLineCommentClose] = function() end }) - end - end - local colonSyntaxPattern = ":" - local function colonSyntaxMeal(current) - return function(words,_) - current:insert(words,consumeColonExpression()) - end - end - function consumeBlock() + end, + [patterns.colonSyntax] = function(words,_,current) + current:insert(words,consumeExpression()) + end, + [patterns.blockOpen] = function(words,_,current) + current:insert(words,consumeBlock()) + end, + } + -- Consume a {} block of code + function consumeBlock(closing_pattern) + closing_pattern = closing_pattern or patterns.blockClose local expressions = {} local current = expression() local loop = true while loop do - local expr = consumer:consume({ - [multiLineCommentPattern] = multiLineCommentMeal(current), - [singleLineCommentPattern] = singleLineCommentMeal(current), - [uriOpenPattern] = URIMeal(current,uriClosePattern), - [expressionOpenPattern] = expressionMeal(current), - [multiLineStringOpenPattern] = - multiLineStringMeal( - current,multiLineStringClosePattern), - [singleLineStringPattern] = - singleLineStringMeal( - current,singleLineStringPattern), - [blockDelimiterPattern] = function(words,_) + local remaining = consumer:remaining() + local expr = consumer:consumePatterns(union(expressionMeals,{ + [patterns.blockDelimiter] = function(words,_) current:insert(words) if current:empty() then --error("Extravenous semicolon.") @@ -133,17 +243,11 @@ function Parse(content,uri,chain) end current = expression() end, - [blockClosePattern] = function(words,_) + [closing_pattern] = function(words,_) current:insert(words) loop = false end, - [blockOpenPattern] = function(words,_) - current:insert( - words, - consumeBlock() - ) - end, - }) + }),current) end if #current.items ~= 0 then table.insert(expressions,current) @@ -157,38 +261,17 @@ function Parse(content,uri,chain) local loop = true while loop do local remaining = consumer:remaining() - local expr = consumer:consume({ - [multiLineCommentPattern] = multiLineCommentMeal(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,_) + local expr = consumer:consumePatterns(union(expressionMeals,{ + [patterns.expressionClose] = function(words,_,_current) current:insert(words) loop = false - end, - [blockOpenPattern] = function(words,_) - current:insert( - words, - consumeBlock() - ) - end, - ["$"] = function(words,last) - current:insert(remaining) - loop = false end - }) + }),current) -- 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() + return consumeBlock(patterns.contentClose) -- TODO: check for later expressions please? end \ No newline at end of file diff --git a/nellie/proto.lua b/nellie/proto.lua index e373c7d..9ca0502 100644 --- a/nellie/proto.lua +++ b/nellie/proto.lua @@ -1,13 +1,14 @@ -- Prototype for nel -require("interpreter") +require("nellie.parser") function Bind(scope,abstract,callback) - scope:insert( - Binding( - Parse(abstract), + for _,expression in pairs(Parse(abstract)) do + assert_meta(Expression)(expression) + scope:insert(Binding( + expression, callback - ) - ) + )) + end end function Run(scope,expression,chain,name) name = name or "?" @@ -17,6 +18,13 @@ function Run(scope,expression,chain,name) local chain = Chain(expression:link(name)):from(chain) return scope:run(expression,chain) 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() local ns = NelliScope @@ -270,13 +278,37 @@ end) Bind(ns,"let (binding) in (scope)",function(s,e,c) 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) - log(Run(s,e.text,c,"print: text")) +Bind(ns,"(closed)",function(s,e,c) return Run(s,e.closed,c,"(...)") end) +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) +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 success,result = Hchain:call(function(chain) Run(NelliScope,Parse(read(Hpath),Hpath,chain),chain) @@ -284,8 +316,6 @@ end) --[[ Documentation: (log (text)) -> Log text to the output stream (print?) - - (a new scope) -> Becomes a created new scope (this scope) -> Becomes the current scope (macro) expands to (expansion) -> Becomes a binding which makes diff --git a/nellie/nellie b/run.sh similarity index 70% rename from nellie/nellie rename to run.sh index 988bf9f..09d8f2f 100755 --- a/nellie/nellie +++ b/run.sh @@ -4,5 +4,5 @@ then echo "lua could not be found" exit 1 else - lua index.lua "$@" + lua -i -v -W -l nellie -e Run "$@" fi \ No newline at end of file diff --git a/test b/test deleted file mode 100644 index e69de29..0000000 diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..65b416a --- /dev/null +++ b/test.sh @@ -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 \ No newline at end of file diff --git a/tests/test.lua b/tests/test.lua index f5f8a0d..fddafdf 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1 +1,5 @@ -require("") \ No newline at end of file +print("doing some tests") +local function test(file,result) + +end +test("print.nel",[[hello world!]]) \ No newline at end of file