Proto: meaning etc.
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user