Major changes to how versioning and parsing work. This should be a

better path going forward to handling upgrades. Old syntax files will
stick around for compatibility purposes. Old syntax can be parsed into
valid syntax trees via the old syntax (.peg) files, and then old syntax
trees should be valid and can be upgraded via the normal code path. This
change has lots of improvements to Nomsu codegen too.
This commit is contained in:
Bruce Hill 2018-07-15 19:41:22 -07:00
parent 8a44869c4a
commit be06fc096a
40 changed files with 565 additions and 445 deletions

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains code that supports manipulating and using collections like lists
and dictionaries.

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains compile-time actions that define basic control flow structures
like "if" statements and loops.

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file defines the code that creates and manipulates coroutines

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains basic error reporting code

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains basic input/output code

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file defines some common math literals and functions

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This File contains actions for making actions and compile-time actions and some helper
functions to make that easier.
@ -186,6 +187,8 @@ compile [remove free vars %vars from %code] to
compile [%lua <-write %code, to %lua write %code] to: Lua "\(%lua as lua expr):append(\(%code as lua expr));"
compile [to %lua write %code joined by %glue] to: Lua "\(%lua as lua expr):concat_append(\(%code as lua expr), \(%glue as lua expr));"
compile [quote %s] to
Lua value ".."
repr(\(%s as lua expr))
@ -212,7 +215,7 @@ compile [compile %block, compiled %block, %block compiled] to
# Return statement is wrapped in a do..end block because Lua is unhappy if you
put code after a return statement, unless you wrap it in a block.
compile [return] to: Lua "do return; end"
compile [return %return_value] to: Lua "do return \(%return_value as lua expr); end"
compile [return %return_value] to: Lua "do return \(%return_value as lua expr) end"
# Literals
compile [yes] to: Lua value "true"

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains definitions of operators like "+" and "and".

View File

@ -1,3 +1,7 @@
#!/usr/bin/env nomsu -V1
#
This file contains definitions pertaining to variable scoping
use "core/metaprogramming.nom"
use "core/operators.nom"
use "core/collections.nom"

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains some definitions of text escape sequences, including ANSI console
color codes.

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
# How do I...
# Write a comment? Put a # and go till the end of the line

View File

@ -1,3 +1,7 @@
#!/usr/bin/env nomsu -V1
#
This file defines actions for ANSI console color escape codes.
use "core"
test: bright: "\(green)Color test passed."
@ -12,6 +16,7 @@ test: bright: "\(green)Color test passed."
for %name = %colornum in %colors
with {%escapecode: "\27[\(%colornum)m"}
run ".."
#!/usr/bin/env nomsu -V1
compile [\%name] to: Lua value (quote \(quote %escapecode))
compile [\%name %text] to
Lua value ".."

View File

@ -1,3 +1,7 @@
#!/usr/bin/env nomsu -V1
#
This file defines some actions for hashing files and looking up files by hash.
use "core"
action [file with hash %hash]

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains the implementation of an Object-Oriented programming system.

View File

@ -1,3 +1,7 @@
#!/usr/bin/env nomsu -V1
#
This file defines some actions that interact with the operating system and filesystem.
use "core"
action [path of Nomsu file %filename]

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
This file contains a set of definitions that bring some familiar language features
from other languages into nomsu (e.g. "==" and "continue")

View File

@ -1 +1,3 @@
#!/usr/bin/env nomsu -V1
# This file sets the current library version.
lua> "NOMSU_LIB_VERSION = 3"

View File

@ -1,5 +1,5 @@
-- Nomsu version 2
file (File):
file (FileChunks):
{:curr_indent: ' '* :}
comment? blank_lines?
(chunk (nl_nodent chunk_delimeter nl_nodent chunk)*)?

148
nomsu.2.peg Normal file
View File

@ -0,0 +1,148 @@
-- Nomsu version 2
file (FileChunks):
{:curr_indent: ' '* :}
comment? blank_lines?
(chunk (nl_nodent section_division nl_nodent chunk)*)?
blank_lines?
%ws* (!! .+ -> "Parse error" !!)?
nodent: =curr_indent !(" ")
indent: =curr_indent " "
blank_lines: %nl ((nodent comment / %ws*) %nl)*
eol: %ws* eol_comment? (!. / &%nl)
nl_nodent: blank_lines nodent
nl_indent: blank_lines {:curr_indent: indent :} (comment nl_nodent)?
comment:
"#" (({} {~ [^%nl]* ((%nl (!indent %ws* %nl)*) (indent -> '') [^%nl]*)* ~} %userdata) => add_comment)
eol_comment:
"#" (({} {[^%nl]*} %userdata) => add_comment)
section_division: ("~")^+3 eol
inline_block (Block):
":" %ws* ((inline_statement (%ws* ";" %ws* inline_statement)*) / !(eol nl_indent))
chunk (Block):
statement (nl_nodent statement)*
indented_block (Block):
":" eol nl_indent statement (nl_nodent statement)*
statement: (action / expression) (eol / (!! [^%nl]+ -> "Unexpected character while parsing line" !!))
inline_statement: (inline_action / inline_expression)
noindex_inline_expression:
number / variable / inline_text / inline_list / inline_dict / inline_nomsu
/ ( "("
%ws* (inline_block / inline_action / inline_expression) %ws*
(%ws* ',' %ws* (inline_block / inline_action / inline_expression) %ws*)*
(")"
/ (!! eol -> 'Line ended without finding a closing )-parenthesis' !!)
/ (!! [^%nl]+ -> 'Unexpected character while parsing subexpression' !!)
)
)
inline_expression: index_chain / noindex_inline_expression
indented_expression:
indented_text / indented_nomsu / indented_list / indented_dict / indented_block / ({|
"(..)" nl_indent
(action / expression) (nl_nodent comment)*
(eol / (!! [^%nl]+ -> "Unexpected character while parsing indented expression" !!))
|} -> unpack)
expression:
inline_expression / indented_expression / inline_block
inline_nomsu (EscapedNomsu): "\" inline_expression
indented_nomsu (EscapedNomsu):
"\" (noindex_inline_expression / indented_expression)
index_chain (IndexChain):
noindex_inline_expression ("." (text_word / noindex_inline_expression))+
-- Actions need either at least 1 word, or at least 2 tokens
inline_action (Action):
!section_division
( (inline_expression (%ws* (inline_expression / word))+)
/ (word (%ws* (inline_expression / word))*))
(%ws* inline_block)?
action (Action):
!section_division
( (expression ((nl_nodent "..")? %ws* (expression / word))+)
/ (word ((nl_nodent "..")? %ws* (expression / word))*))
word: !number { %operator_char+ / %ident_char+ }
text_word (Text): word
inline_text (Text):
!('".."' eol)
'"'
({~ (('\"' -> '"') / ('\\' -> '\') / %escaped_char / [^%nl\"])+ ~}
/ inline_text_interpolation)*
('"'
/ (!! eol -> 'Line ended before finding a closing double quotation mark' !!)
/ (!! [^%nl]+ -> 'Unexpected character while parsing Text' !!))
inline_text_interpolation:
"\" (
variable / inline_list / inline_dict / inline_text
/ ("("
%ws* (inline_block / inline_action / inline_expression) %ws*
(%ws* ',' %ws* (inline_block / inline_action / inline_expression) %ws*)*
(")"
/ (!! eol -> 'Line ended without finding a closing )-parenthesis' !!)
/ (!! [^%nl]+ -> 'Unexpected character while parsing Text interpolation' !!)))
)
indented_text (Text):
'".."' eol %nl {:curr_indent: indent :}
(indented_plain_text / text_interpolation / {~ blank_lines (=curr_indent -> "") ~})*
(!! [^%nl]+ -> "Unexpected character while parsing Text" !!)?
indented_plain_text (Text):
{~ (("\\" -> "\") / (("\" blank_lines =curr_indent "..") -> "") / (!text_interpolation "\") / [^%nl\]+)+
(blank_lines (=curr_indent -> ""))* ~}
text_interpolation:
inline_text_interpolation / ("\" indented_expression blank_lines =curr_indent "..")
number (Number): (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber)
-- Variables can be nameless (i.e. just %) and can't contain operators like apostrophe
-- which is a hack to allow %'s to parse as "%" and "' s" separately
variable (Var): "%" {(%ident_char+ ((!"'" %operator_char+) / %ident_char+)*)?}
inline_list (List):
!('[..]')
"[" %ws*
(inline_list_item (%ws* ',' %ws* inline_list_item)* (%ws* ',')?)? %ws*
("]" / (","? (
(!! eol -> "Line ended before finding a closing ]-bracket" !!)
/(!! [^%nl]+ -> "Unexpected character while parsing List" !!)
)))
indented_list (List):
"[..]" eol nl_indent
list_line (nl_nodent list_line)* (nl_nodent comment)*
(","? (!! [^%nl]+ -> "Unexpected character while parsing List" !!))?
list_line:
(inline_list_item %ws* "," %ws*)+ eol
/ (inline_list_item %ws* "," %ws*)* (action / expression) eol
inline_list_item: inline_block / inline_action / inline_expression
inline_dict (Dict):
!('{..}')
"{" %ws*
(inline_dict_entry (%ws* ',' %ws* inline_dict_entry)*)? %ws*
("}" / (","? (
(!! eol -> "Line ended before finding a closing }-brace" !!)
/ (!! [^%nl]* -> "Unexpected character while parsing Dictionary" !!)
)))
indented_dict (Dict):
"{..}" eol nl_indent
dict_line (nl_nodent dict_line)* (nl_nodent comment)*
(","? (!! [^%nl]+ -> "Unexpected character while parsing Dictionary" !!))?
dict_line:
(inline_dict_entry %ws* "," %ws*)+ eol
/ (inline_dict_entry %ws* "," %ws*)* dict_entry eol
dict_entry(DictEntry):
dict_key (%ws* ":" %ws* (action / expression))?
inline_dict_entry(DictEntry):
dict_key (%ws* ":" %ws* (inline_action / inline_expression)?)?
dict_key:
text_word / inline_expression

View File

@ -197,7 +197,7 @@ run = function()
end
local tree = nomsu:parse(file, source)
if tree then
if tree.type ~= "File" then
if tree.type ~= "FileChunks" then
tree = {
tree
}

View File

@ -130,7 +130,7 @@ run = ->
return unless file
tree = nomsu\parse(file, source)
if tree
if tree.type != "File"
if tree.type != "FileChunks"
tree = {tree}
-- Each chunk's compilation is affected by the code in the previous chunks
-- (typically), so each chunk needs to compile and run before the next one

View File

@ -350,10 +350,13 @@ do
end
end
})
NomsuCompiler.run = function(self, to_run, source)
NomsuCompiler.run = function(self, to_run, source, version)
if source == nil then
source = nil
end
if version == nil then
version = nil
end
source = source or (to_run.source or Source(to_run, 1, #to_run))
if type(source) == 'string' then
source = Source:from_string(source)
@ -365,29 +368,27 @@ do
if AST.is_syntax_tree(to_run) then
tree = to_run
else
tree = self:parse(to_run, source)
tree = self:parse(to_run, source, version)
end
if tree == nil then
return nil
end
if tree.type == "File" then
local ret = nil
local all_lua = { }
for _index_0 = 1, #tree do
local chunk = tree[_index_0]
local lua = self:compile(chunk):as_statements("return ")
lua:declare_locals()
lua:prepend("-- File: " .. tostring(source.filename:gsub("\n.*", "...")) .. "\n")
insert(all_lua, tostring(lua))
ret = self:run_lua(lua)
end
return ret
else
local lua = self:compile(tree):as_statements("return ")
if tree.type ~= "FileChunks" then
tree = {
tree
}
end
local ret = nil
local all_lua = { }
for _index_0 = 1, #tree do
local chunk = tree[_index_0]
local lua = self:compile(chunk):as_statements("return ")
lua:declare_locals()
lua:prepend("-- File: " .. tostring(source.filename:gsub("\n.*", "...")) .. "\n")
return self:run_lua(lua)
insert(all_lua, tostring(lua))
ret = self:run_lua(lua)
end
return ret
end
local _running_files = { }
NomsuCompiler.run_file = function(self, filename)
@ -571,7 +572,8 @@ do
end
bits = _accum_0
end
return t.type .. "(" .. repr(tostring(t.source)) .. ", " .. table.concat(bits, ", ") .. ")"
insert(bits, 1, repr(tostring(t.source)))
return t.type .. "(" .. concat(bits, ", ") .. ")"
end
return LuaCode.Value(tree.source, make_tree(tree[1]))
elseif "Block" == _exp_0 then
@ -711,13 +713,13 @@ do
return LuaCode.Value(tree.source, tostring(tree[1]))
elseif "Var" == _exp_0 then
return LuaCode.Value(tree.source, string.as_lua_id(tree[1]))
elseif "File" == _exp_0 then
return error("Cannot convert File to a single block of lua, since each chunk's " .. "compilation depends on the earlier chunks")
elseif "FileChunks" == _exp_0 then
return error("Cannot convert FileChunks to a single block of lua, since each chunk's " .. "compilation depends on the earlier chunks")
else
return error("Unknown type: " .. tostring(tree.type))
end
end
local MIN_COLON_LEN = 25
local MIN_COLON_LEN = 20
NomsuCompiler.tree_to_nomsu = function(self, tree, options)
options = options or { }
if not (options.pop_comments) then
@ -740,7 +742,10 @@ do
return a.pos < b.pos
end)
local comment_i = 1
options.pop_comments = function(pos)
options.pop_comments = function(pos, prefix)
if prefix == nil then
prefix = ''
end
local nomsu = NomsuCode(tree.source)
while comments[comment_i] and comments[comment_i].pos <= pos do
local comment = comments[comment_i].comment
@ -750,7 +755,11 @@ do
end
comment_i = comment_i + 1
end
return #nomsu.bits == 0 and '' or nomsu
if #nomsu.bits == 0 then
return ''
end
nomsu:prepend(prefix)
return nomsu
end
end
local recurse
@ -759,12 +768,12 @@ do
opts.pop_comments = options.pop_comments
return self:tree_to_nomsu(t, opts)
end
local inline, can_use_colon, pop_comments
inline, can_use_colon, pop_comments = options.inline, options.can_use_colon, options.pop_comments
local inline, pop_comments
inline, pop_comments = options.inline, options.pop_comments
local _exp_0 = tree.type
if "File" == _exp_0 then
if "FileChunks" == _exp_0 then
if inline then
return nil
error("Cannot inline a FileChunks")
end
local nomsu = NomsuCode(tree.source)
for i, chunk in ipairs(tree) do
@ -772,7 +781,9 @@ do
nomsu:append("\n\n" .. tostring(("~"):rep(80)) .. "\n\n")
end
nomsu:append(pop_comments(chunk.source.start))
nomsu:append(recurse(chunk))
nomsu:append(recurse(chunk, {
top = true
}))
end
nomsu:append(pop_comments(tree.source.stop))
return nomsu
@ -792,16 +803,12 @@ do
if not (arg_nomsu) then
return nil
end
if bit.type == "Action" or bit.type == "Block" then
if bit.type == "Action" and i == #tree and #tostring(arg_nomsu) >= MIN_COLON_LEN then
nomsu:append(":")
else
arg_nomsu:parenthesize()
end
end
if not (i == 1) then
nomsu:append(" ")
end
if bit.type == "Action" or (bit.type == "Block" and (#bit > 1 or i < #tree)) then
arg_nomsu:parenthesize()
end
nomsu:append(arg_nomsu)
end
end
@ -809,100 +816,79 @@ do
else
local nomsu = NomsuCode(tree.source)
local next_space = ""
local line_len, last_colon = 0, nil
for i, bit in ipairs(tree) do
if type(bit) == "string" then
line_len = line_len + #next_space + #bit
nomsu:append(next_space, bit)
next_space = " "
else
local arg_nomsu
if last_colon == i - 1 and bit.type == "Action" then
arg_nomsu = nil
elseif bit.type == "Block" then
if bit.type == "Block" and #bit > 1 then
arg_nomsu = nil
else
arg_nomsu = recurse(bit, {
arg_nomsu = assert(recurse(bit, {
inline = true
})
}))
end
if arg_nomsu and line_len + #tostring(arg_nomsu) < MAX_LINE then
if bit.type == "Action" then
if can_use_colon and i > 1 and #tostring(arg_nomsu) >= MIN_COLON_LEN then
nomsu:append(match(next_space, "[^ ]*"), ": ", arg_nomsu)
next_space = "\n.."
line_len = 2
last_colon = i
else
nomsu:append(next_space, "(", arg_nomsu, ")")
line_len = line_len + #next_space + 2 + #tostring(arg_nomsu)
next_space = " "
end
if bit.type == "Block" then
next_space = match(next_space, "[^ ]*")
end
nomsu:append(next_space)
if arg_nomsu and nomsu.trailing_line_len + #tostring(arg_nomsu) < MAX_LINE then
if bit.type == "Block" then
nomsu:append(arg_nomsu)
next_space = "\n.."
else
nomsu:append(next_space, arg_nomsu)
line_len = line_len + #next_space + #tostring(arg_nomsu)
if bit.type == "Action" then
arg_nomsu:parenthesize()
end
nomsu:append(arg_nomsu)
next_space = " "
end
else
arg_nomsu = recurse(bit, {
can_use_colon = true
})
if not (arg_nomsu) then
return nil
arg_nomsu = assert(recurse(bit))
if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" and bit.type ~= "Block" then
nomsu:append(NomsuCode(bit.source, "(..)\n ", pop_comments(bit.source.start), arg_nomsu))
else
nomsu:append(arg_nomsu)
end
if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then
if i == 1 then
arg_nomsu = NomsuCode(bit.source, "(..)\n ", pop_comments(bit.source.start), arg_nomsu)
else
arg_nomsu = NomsuCode(bit.source, "\n ", pop_comments(bit.source.start), arg_nomsu)
end
end
if last_colon == i - 1 and (bit.type == "Action" or bit.type == "Block") then
next_space = ""
end
nomsu:append(next_space, arg_nomsu)
next_space = "\n.."
line_len = 2
end
if next_space == " " and #(match(tostring(nomsu), "[^\n]*$")) > MAX_LINE then
next_space = "\n.."
end
end
if next_space == " " and nomsu.trailing_line_len > MAX_LINE then
next_space = "\n.."
end
end
return nomsu
end
elseif "EscapedNomsu" == _exp_0 then
local nomsu = recurse(tree[1], {
local nomsu = NomsuCode(tree.source, "\\(", assert(recurse(tree[1], {
inline = true
})
if nomsu == nil and not inline then
nomsu = recurse(tree[1])
return nomsu and NomsuCode(tree.source, "\\:\n ", pop_comments(tree.source.start), nomsu)
})), ")")
if inline or #tostring(nomsu) <= MAX_LINE then
return nomsu
end
nomsu = assert(recurse(tree[1]))
local _exp_1 = tree[1].type
if "List" == _exp_1 or "Dict" == _exp_1 or "Text" == _exp_1 or "Block" == _exp_1 then
return NomsuCode(tree.source, "\\", nomsu)
else
return NomsuCode(tree.source, "\\(..)\n ", pop_comments(tree.source.start), nomsu)
end
return nomsu and NomsuCode(tree.source, "\\(", nomsu, ")")
elseif "Block" == _exp_0 then
if inline then
local nomsu = NomsuCode(tree.source)
local nomsu = NomsuCode(tree.source, ":")
for i, line in ipairs(tree) do
if i > 1 then
nomsu:append("; ")
end
local line_nomsu = recurse(line, {
nomsu:append(i == 1 and " " or "; ")
nomsu:append(assert(recurse(line, {
inline = true
})
if not (line_nomsu) then
return nil
end
nomsu:append(line_nomsu)
})))
end
return nomsu
end
local nomsu = NomsuCode(tree.source)
for i, line in ipairs(tree) do
nomsu:append(pop_comments(line.source.start))
line = assert(recurse(line, {
can_use_colon = true
}), "Could not convert line to nomsu")
line = assert(recurse(line), "Could not convert line to nomsu")
nomsu:append(line)
if i < #tree then
nomsu:append("\n")
@ -911,7 +897,7 @@ do
end
end
end
return nomsu
return options.top and nomsu or NomsuCode(tree.source, ":\n ", nomsu)
elseif "Text" == _exp_0 then
if inline then
local make_text
@ -927,7 +913,7 @@ do
local interp_nomsu = assert(recurse(bit, {
inline = true
}))
if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then
if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" then
interp_nomsu:parenthesize()
end
nomsu:append("\\", interp_nomsu)
@ -985,7 +971,7 @@ do
inline = true
})
if interp_nomsu then
if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then
if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" then
interp_nomsu:parenthesize()
end
nomsu:append("\\", interp_nomsu)
@ -1009,16 +995,12 @@ do
if inline then
local nomsu = NomsuCode(tree.source, "[")
for i, item in ipairs(tree) do
local item_nomsu = recurse(item, {
inline = true
})
if not (item_nomsu) then
return nil
end
if i > 1 then
nomsu:append(", ")
end
nomsu:append(item_nomsu)
nomsu:append(assert(recurse(item, {
inline = true
})))
end
nomsu:append("]")
return nomsu
@ -1026,67 +1008,56 @@ do
local inline_version = recurse(tree, {
inline = true
})
if inline_version and #inline_version <= MAX_LINE then
if inline_version and #tostring(inline_version) <= MAX_LINE then
return inline_version
end
local nomsu = NomsuCode(tree.source, "[..]")
local line = NomsuCode(tree.source, "\n ")
local line_comments
if #tree > 0 then
line_comments = pop_comments(tree[1].source.start)
else
line_comments = ''
end
assert(#tree > 0)
local nomsu = NomsuCode(tree.source, pop_comments(tree[1].source.start))
for i, item in ipairs(tree) do
local item_nomsu = recurse(item, {
local item_nomsu = assert(recurse(item, {
inline = true
})
if item_nomsu and #tostring(line) + #", " + #item_nomsu <= MAX_LINE then
if #line.bits > 1 then
line:append(", ")
}))
if item.type == "Block" then
item_nomsu:parenthesize()
end
if nomsu.trailing_line_len + #tostring(item_nomsu) <= MAX_LINE then
if nomsu.trailing_line_len > 0 then
nomsu:append(", ")
end
line:append(item_nomsu)
nomsu:append(item_nomsu)
else
if not (item_nomsu) then
if #tostring(item_nomsu) > MAX_LINE then
item_nomsu = recurse(item)
if not (item_nomsu) then
return nil
end
end
if #line.bits > 1 then
if #tostring(line_comments) > 0 then
nomsu:append('\n ', line_comments)
end
nomsu:append(line)
line = NomsuCode(line.source, "\n ")
if i < #tree then
line_comments = pop_comments(tree[i + 1].source.start)
local _exp_1 = item.type
if "List" == _exp_1 or "Dict" == _exp_1 or "Text" == _exp_1 or "Block" == _exp_1 then
nomsu:append(item_nomsu)
else
line_comments = ''
nomsu:append("(..)\n ", item_nomsu)
end
if i < #tree then
nomsu:append("\n")
end
else
if nomsu.trailing_line_len > 0 then
nomsu:append('\n')
end
nomsu:append(pop_comments(item.source.start), item_nomsu)
end
line:append(item_nomsu)
end
end
if #line.bits > 1 then
nomsu:append(line)
end
return nomsu
nomsu:append(pop_comments(tree.source.stop, '\n'))
return NomsuCode(tree.source, "[..]\n ", nomsu)
end
elseif "Dict" == _exp_0 then
if inline then
local nomsu = NomsuCode(tree.source, "{")
for i, entry in ipairs(tree) do
local entry_nomsu = recurse(entry, {
inline = true
})
if not (entry_nomsu) then
return nil
end
if i > 1 then
nomsu:append(", ")
end
nomsu:append(entry_nomsu)
nomsu:append(assert(recurse(entry, {
inline = true
})))
end
nomsu:append("}")
return nomsu
@ -1094,80 +1065,75 @@ do
local inline_version = recurse(tree, {
inline = true
})
if inline_version then
if inline_version and #tostring(inline_version) <= MAX_LINE then
return inline_version
end
local nomsu = NomsuCode(tree.source, "{..}")
local line = NomsuCode(tree.source, "\n ")
local line_comments
if #tree > 0 then
line_comments = pop_comments(tree[1].source.start)
else
line_comments = ''
end
for i, entry in ipairs(tree) do
local entry_nomsu = recurse(entry)
if not (entry_nomsu) then
return nil
assert(#tree > 0)
local nomsu = NomsuCode(tree.source, pop_comments(tree[1].source.start))
for i, item in ipairs(tree) do
local item_nomsu = assert(recurse(item, {
inline = true
}))
if item.type == "Block" then
item_nomsu:parenthesize()
end
if #line + #tostring(entry_nomsu) <= MAX_LINE then
if #line.bits > 1 then
line:append(", ")
if nomsu.trailing_line_len + #tostring(item_nomsu) <= MAX_LINE then
if nomsu.trailing_line_len > 0 then
nomsu:append(", ")
end
line:append(entry_nomsu)
nomsu:append(item_nomsu)
else
if #line.bits > 1 then
if #tostring(line_comments) > 0 then
nomsu:append("\n ", line_comments)
end
nomsu:append(line)
line = NomsuCode(line.source, "\n ")
if i < #tree then
line_comments = pop_comments(tree[1].source.start)
if #tostring(item_nomsu) > MAX_LINE then
item_nomsu = recurse(item)
local _exp_1 = item.type
if "List" == _exp_1 or "Dict" == _exp_1 or "Text" == _exp_1 or "Block" == _exp_1 then
nomsu:append(item_nomsu)
else
line_comments = ''
nomsu:append("(..)\n ", item_nomsu)
end
if i < #tree then
nomsu:append("\n")
end
else
if nomsu.trailing_line_len > 0 then
nomsu:append('\n')
end
nomsu:append(pop_comments(item.source.start), item_nomsu)
end
line:append(entry_nomsu)
end
end
if #line.bits > 1 then
nomsu:append(line)
end
return nomsu
nomsu:append(pop_comments(tree.source.stop, '\n'))
return NomsuCode(tree.source, "{..}\n ", nomsu)
end
elseif "DictEntry" == _exp_0 then
local key, value = tree[1], tree[2]
local key_nomsu = recurse(key, {
local key_nomsu = assert(recurse(key, {
inline = true
})
if not (key_nomsu) then
return nil
end
}))
if key.type == "Action" or key.type == "Block" then
key_nomsu:parenthesize()
end
local value_nomsu
if value then
value_nomsu = recurse(value, {
value_nomsu = assert(recurse(value, {
inline = true
})
}))
else
value_nomsu = NomsuCode(tree.source, "")
end
if inline and not value_nomsu then
return nil
assert(value.type ~= "Block", "Didn't expect to find a Block as a value in a dict")
if value.type == "Block" then
value_nomsu:parenthesize()
end
if not value_nomsu then
if inline then
return nil
end
value_nomsu = recurse(value)
if not (value_nomsu) then
return nil
end
if inline or #tostring(key_nomsu) + 2 + #tostring(value_nomsu) <= MAX_LINE then
return NomsuCode(tree.source, key_nomsu, ": ", value_nomsu)
end
value_nomsu = recurse(value)
if value.type == "List" or value.type == "Dict" or value.type == "Text" then
return NomsuCode(tree.source, key_nomsu, ": ", value_nomsu)
else
return NomsuCode(tree.source, key_nomsu, ": (..)\n ", value_nomsu)
end
return NomsuCode(tree.source, key_nomsu, ":", value_nomsu)
elseif "IndexChain" == _exp_0 then
local nomsu = NomsuCode(tree.source)
for i, bit in ipairs(tree) do
@ -1175,18 +1141,12 @@ do
nomsu:append(".")
end
local bit_nomsu
if bit.type == "Text" and #bit == 1 and type(bit[1]) == 'string' then
if bit[1]:match("[_a-zA-Z][_a-zA-Z0-9]*") then
bit_nomsu = bit[1]
end
end
if not (bit_nomsu) then
bit_nomsu = recurse(bit, {
if bit.type == "Text" and #bit == 1 and type(bit[1]) == 'string' and bit[1]:match("[_a-zA-Z][_a-zA-Z0-9]*") then
bit_nomsu = bit[1]
else
bit_nomsu = assert(recurse(bit, {
inline = true
})
end
if not (bit_nomsu) then
return nil
}))
end
local _exp_1 = bit.type
if "Action" == _exp_1 or "Block" == _exp_1 or "IndexChain" == _exp_1 then

View File

@ -229,31 +229,27 @@ with NomsuCompiler
return @["# compile math expr #"]
}
.run = (to_run, source=nil)=>
.run = (to_run, source=nil, version=nil)=>
source or= to_run.source or Source(to_run, 1, #to_run)
if type(source) == 'string' then source = Source\from_string(source)
if not files.read(source.filename) then files.spoof(source.filename, to_run)
tree = if AST.is_syntax_tree(to_run) then to_run else @parse(to_run, source)
tree = if AST.is_syntax_tree(to_run) then to_run else @parse(to_run, source, version)
if tree == nil -- Happens if pattern matches, but there are no captures, e.g. an empty string
return nil
if tree.type == "File"
-- Each chunk's compilation is affected by the code in the previous chunks
-- (typically), so each chunk needs to compile and run before the next one
-- compiles.
ret = nil
all_lua = {}
for chunk in *tree
lua = @compile(chunk)\as_statements("return ")
lua\declare_locals!
lua\prepend "-- File: #{source.filename\gsub("\n.*", "...")}\n"
insert all_lua, tostring(lua)
ret = @run_lua(lua)
return ret
else
lua = @compile(tree)\as_statements("return ")
if tree.type != "FileChunks"
tree = {tree}
-- Each chunk's compilation is affected by the code in the previous chunks
-- (typically), so each chunk needs to compile and run before the next one
-- compiles.
ret = nil
all_lua = {}
for chunk in *tree
lua = @compile(chunk)\as_statements("return ")
lua\declare_locals!
lua\prepend "-- File: #{source.filename\gsub("\n.*", "...")}\n"
return @run_lua(lua)
insert all_lua, tostring(lua)
ret = @run_lua(lua)
return ret
_running_files = {} -- For detecting circular imports
.run_file = (filename)=>
@ -362,7 +358,8 @@ with NomsuCompiler
unless AST.is_syntax_tree(t)
return repr(t)
bits = [make_tree(bit) for bit in *t]
return t.type.."("..repr(tostring t.source)..", "..table.concat(bits, ", ")..")"
insert bits, 1, repr(tostring t.source)
return t.type.."("..concat(bits, ", ")..")"
return LuaCode.Value tree.source, make_tree(tree[1])
when "Block"
@ -474,44 +471,45 @@ with NomsuCompiler
when "Var"
return LuaCode.Value(tree.source, string.as_lua_id(tree[1]))
when "File"
error("Cannot convert File to a single block of lua, since each chunk's "..
when "FileChunks"
error("Cannot convert FileChunks to a single block of lua, since each chunk's "..
"compilation depends on the earlier chunks")
else
error("Unknown type: #{tree.type}")
MIN_COLON_LEN = 25 -- For beautification purposes, don't bother using colon syntax for short bits
MIN_COLON_LEN = 20 -- For beautification purposes, don't bother using colon syntax for short bits
.tree_to_nomsu = (tree, options)=>
options or= {}
unless options.pop_comments
comments = [{comment:c, pos:p} for p,c in pairs(tree.comments or {}) when tree.source.start <= p and p <= tree.source.stop]
table.sort comments, (a,b)-> a.pos < b.pos
comment_i = 1
options.pop_comments = (pos)->
options.pop_comments = (pos, prefix='')->
nomsu = NomsuCode(tree.source)
while comments[comment_i] and comments[comment_i].pos <= pos
comment = comments[comment_i].comment
nomsu\append("#"..(gsub(comment, "\n", "\n ")).."\n")
if comment\match("^\n.") then nomsu\append("\n") -- for aesthetics
comment_i += 1
return #nomsu.bits == 0 and '' or nomsu
return '' if #nomsu.bits == 0
nomsu\prepend prefix
return nomsu
recurse = (t, opts)->
opts or= {}
opts.pop_comments = options.pop_comments
return @tree_to_nomsu(t, opts)
{:inline, :can_use_colon, :pop_comments} = options
{:inline, :pop_comments} = options
switch tree.type
when "File"
return nil if inline
when "FileChunks"
error("Cannot inline a FileChunks") if inline
nomsu = NomsuCode(tree.source)
for i, chunk in ipairs tree
if i > 1
nomsu\append "\n\n#{("~")\rep(80)}\n\n"
nomsu\append "\n\n#{("~")\rep(80)}\n\n" if i > 1
nomsu\append pop_comments(chunk.source.start)
nomsu\append recurse(chunk)
nomsu\append recurse(chunk, top:true)
nomsu\append pop_comments(tree.source.stop)
return nomsu
@ -520,97 +518,78 @@ with NomsuCompiler
nomsu = NomsuCode(tree.source)
for i,bit in ipairs tree
if type(bit) == "string"
if i > 1
nomsu\append " "
nomsu\append " " if i > 1
nomsu\append bit
else
arg_nomsu = recurse(bit,inline:true)
return nil unless arg_nomsu
if bit.type == "Action" or bit.type == "Block"
if bit.type == "Action" and i == #tree and #tostring(arg_nomsu) >= MIN_COLON_LEN
nomsu\append ":"
else
arg_nomsu\parenthesize!
unless i == 1
nomsu\append " "
if bit.type == "Action" or (bit.type == "Block" and (#bit > 1 or i < #tree))
arg_nomsu\parenthesize!
nomsu\append arg_nomsu
return nomsu
else
nomsu = NomsuCode(tree.source)
next_space = ""
line_len, last_colon = 0, nil
for i,bit in ipairs tree
if type(bit) == "string"
line_len += #next_space + #bit
nomsu\append next_space, bit
next_space = " "
else
arg_nomsu = if last_colon == i-1 and bit.type == "Action" then nil
elseif bit.type == "Block" then nil
else recurse(bit,inline:true)
if arg_nomsu and line_len + #tostring(arg_nomsu) < MAX_LINE
if bit.type == "Action"
if can_use_colon and i > 1 and #tostring(arg_nomsu) >= MIN_COLON_LEN
nomsu\append match(next_space,"[^ ]*"), ": ", arg_nomsu
next_space = "\n.."
line_len = 2
last_colon = i
else
nomsu\append next_space, "(", arg_nomsu, ")"
line_len += #next_space + 2 + #tostring(arg_nomsu)
next_space = " "
arg_nomsu = if bit.type == "Block" and #bit > 1 then nil
else assert recurse(bit,inline:true)
next_space = match(next_space, "[^ ]*") if bit.type == "Block"
nomsu\append next_space
if arg_nomsu and nomsu.trailing_line_len + #tostring(arg_nomsu) < MAX_LINE
if bit.type == "Block"
nomsu\append arg_nomsu
next_space = "\n.."
else
nomsu\append next_space, arg_nomsu
line_len += #next_space + #tostring(arg_nomsu)
arg_nomsu\parenthesize! if bit.type == "Action"
nomsu\append arg_nomsu
next_space = " "
else
arg_nomsu = recurse(bit, can_use_colon:true)
return nil unless arg_nomsu
arg_nomsu = assert recurse(bit)
-- These types carry their own indentation
if bit.type != "List" and bit.type != "Dict" and bit.type != "Text"
if i == 1
arg_nomsu = NomsuCode(bit.source, "(..)\n ", pop_comments(bit.source.start), arg_nomsu)
else
arg_nomsu = NomsuCode(bit.source, "\n ", pop_comments(bit.source.start), arg_nomsu)
if last_colon == i-1 and (bit.type == "Action" or bit.type == "Block")
next_space = ""
nomsu\append next_space, arg_nomsu
if bit.type != "List" and bit.type != "Dict" and bit.type != "Text" and bit.type != "Block"
nomsu\append NomsuCode(bit.source, "(..)\n ", pop_comments(bit.source.start), arg_nomsu)
else
nomsu\append arg_nomsu
next_space = "\n.."
line_len = 2
if next_space == " " and #(match(tostring(nomsu),"[^\n]*$")) > MAX_LINE
next_space = "\n.."
if next_space == " " and nomsu.trailing_line_len > MAX_LINE
next_space = "\n.."
return nomsu
when "EscapedNomsu"
nomsu = recurse(tree[1], inline:true)
if nomsu == nil and not inline
nomsu = recurse(tree[1])
return nomsu and NomsuCode tree.source, "\\:\n ", pop_comments(tree.source.start), nomsu
return nomsu and NomsuCode tree.source, "\\(", nomsu, ")"
nomsu = NomsuCode(tree.source, "\\(", assert(recurse(tree[1], inline:true)), ")")
if inline or #tostring(nomsu) <= MAX_LINE
return nomsu
nomsu = assert recurse(tree[1])
switch tree[1].type
when "List", "Dict", "Text", "Block"
return NomsuCode tree.source, "\\", nomsu
else
return NomsuCode tree.source, "\\(..)\n ", pop_comments(tree.source.start), nomsu
when "Block"
if inline
nomsu = NomsuCode(tree.source)
nomsu = NomsuCode(tree.source, ":")
for i,line in ipairs tree
if i > 1
nomsu\append "; "
line_nomsu = recurse(line,inline:true)
return nil unless line_nomsu
nomsu\append line_nomsu
nomsu\append(i == 1 and " " or "; ")
nomsu\append assert(recurse(line, inline:true))
return nomsu
nomsu = NomsuCode(tree.source)
for i, line in ipairs tree
nomsu\append pop_comments(line.source.start)
line = assert(recurse(line, can_use_colon:true), "Could not convert line to nomsu")
line = assert(recurse(line), "Could not convert line to nomsu")
nomsu\append line
if i < #tree
nomsu\append "\n"
if match(tostring(line), "\n")
nomsu\append "\n"
return nomsu
return options.top and nomsu or NomsuCode(tree.source, ":\n ", nomsu)
when "Text"
if inline
@ -624,7 +603,7 @@ with NomsuCompiler
nomsu\append(make_text(bit))
else
interp_nomsu = assert recurse(bit, inline:true)
if bit.type != "Var" and bit.type != "List" and bit.type != "Dict" and bit.type != "Text"
if bit.type != "Var" and bit.type != "List" and bit.type != "Dict"
interp_nomsu\parenthesize!
nomsu\append "\\", interp_nomsu
return nomsu
@ -662,7 +641,7 @@ with NomsuCompiler
else
interp_nomsu = recurse(bit, inline:true)
if interp_nomsu
if bit.type != "Var" and bit.type != "List" and bit.type != "Dict" and bit.type != "Text"
if bit.type != "Var" and bit.type != "List" and bit.type != "Dict"
interp_nomsu\parenthesize!
nomsu\append "\\", interp_nomsu
else
@ -678,113 +657,98 @@ with NomsuCompiler
if inline
nomsu = NomsuCode(tree.source, "[")
for i, item in ipairs tree
item_nomsu = recurse(item, inline:true)
return nil unless item_nomsu
if i > 1
nomsu\append ", "
nomsu\append item_nomsu
nomsu\append ", " if i > 1
nomsu\append assert(recurse(item, inline:true))
nomsu\append "]"
return nomsu
else
inline_version = recurse(tree, inline:true)
if inline_version and #inline_version <= MAX_LINE
if inline_version and #tostring(inline_version) <= MAX_LINE
return inline_version
nomsu = NomsuCode(tree.source, "[..]")
line = NomsuCode(tree.source, "\n ")
line_comments = if #tree > 0
pop_comments(tree[1].source.start)
else ''
assert #tree > 0
nomsu = NomsuCode(tree.source, pop_comments(tree[1].source.start))
for i, item in ipairs tree
item_nomsu = recurse(item, inline:true)
if item_nomsu and #tostring(line) + #", " + #item_nomsu <= MAX_LINE
if #line.bits > 1
line\append ", "
line\append item_nomsu
item_nomsu = assert recurse(item, inline:true)
item_nomsu\parenthesize! if item.type == "Block"
if nomsu.trailing_line_len + #tostring(item_nomsu) <= MAX_LINE
nomsu\append ", " if nomsu.trailing_line_len > 0
nomsu\append item_nomsu
else
unless item_nomsu
if #tostring(item_nomsu) > MAX_LINE
item_nomsu = recurse(item)
return nil unless item_nomsu
if #line.bits > 1
if #tostring(line_comments) > 0
nomsu\append '\n ', line_comments
nomsu\append line
line = NomsuCode(line.source, "\n ")
line_comments = if i < #tree
pop_comments(tree[i+1].source.start)
else ''
line\append item_nomsu
if #line.bits > 1
nomsu\append line
return nomsu
switch item.type
when "List", "Dict", "Text", "Block"
nomsu\append item_nomsu
else
nomsu\append "(..)\n ", item_nomsu
nomsu\append "\n" if i < #tree
else
nomsu\append '\n' if nomsu.trailing_line_len > 0
nomsu\append pop_comments(item.source.start), item_nomsu
nomsu\append pop_comments(tree.source.stop, '\n')
return NomsuCode(tree.source, "[..]\n ", nomsu)
when "Dict"
if inline
nomsu = NomsuCode(tree.source, "{")
for i, entry in ipairs tree
entry_nomsu = recurse(entry, inline:true)
return nil unless entry_nomsu
if i > 1
nomsu\append ", "
nomsu\append entry_nomsu
nomsu\append ", " if i > 1
nomsu\append assert(recurse(entry, inline:true))
nomsu\append "}"
return nomsu
else
inline_version = recurse(tree, inline:true)
if inline_version then return inline_version
nomsu = NomsuCode(tree.source, "{..}")
line = NomsuCode(tree.source, "\n ")
line_comments = if #tree > 0
pop_comments(tree[1].source.start)
else ''
for i, entry in ipairs tree
entry_nomsu = recurse(entry)
return nil unless entry_nomsu
if #line + #tostring(entry_nomsu) <= MAX_LINE
if #line.bits > 1
line\append ", "
line\append entry_nomsu
if inline_version and #tostring(inline_version) <= MAX_LINE
return inline_version
assert #tree > 0
nomsu = NomsuCode(tree.source, pop_comments(tree[1].source.start))
for i, item in ipairs tree
item_nomsu = assert recurse(item, inline:true)
item_nomsu\parenthesize! if item.type == "Block"
if nomsu.trailing_line_len + #tostring(item_nomsu) <= MAX_LINE
nomsu\append ", " if nomsu.trailing_line_len > 0
nomsu\append item_nomsu
else
if #line.bits > 1
if #tostring(line_comments) > 0
nomsu\append "\n ", line_comments
nomsu\append line
line = NomsuCode(line.source, "\n ")
line_comments = if i < #tree
pop_comments(tree[1].source.start)
else ''
line\append entry_nomsu
if #line.bits > 1
nomsu\append line
return nomsu
if #tostring(item_nomsu) > MAX_LINE
item_nomsu = recurse(item)
switch item.type
when "List", "Dict", "Text", "Block"
nomsu\append item_nomsu
else
nomsu\append "(..)\n ", item_nomsu
nomsu\append "\n" if i < #tree
else
nomsu\append '\n' if nomsu.trailing_line_len > 0
nomsu\append pop_comments(item.source.start), item_nomsu
nomsu\append pop_comments(tree.source.stop, '\n')
return NomsuCode(tree.source, "{..}\n ", nomsu)
when "DictEntry"
key, value = tree[1], tree[2]
key_nomsu = recurse(key, inline:true)
return nil unless key_nomsu
if key.type == "Action" or key.type == "Block"
key_nomsu\parenthesize!
key_nomsu = assert recurse(key, inline:true)
key_nomsu\parenthesize! if key.type == "Action" or key.type == "Block"
value_nomsu = if value
recurse(value, inline:true)
assert recurse(value, inline:true)
else NomsuCode(tree.source, "")
if inline and not value_nomsu then return nil
if not value_nomsu
return nil if inline
value_nomsu = recurse(value)
return nil unless value_nomsu
return NomsuCode tree.source, key_nomsu, ":", value_nomsu
assert(value.type != "Block", "Didn't expect to find a Block as a value in a dict")
value_nomsu\parenthesize! if value.type == "Block"
if inline or #tostring(key_nomsu) + 2 + #tostring(value_nomsu) <= MAX_LINE
return NomsuCode tree.source, key_nomsu, ": ", value_nomsu
value_nomsu = recurse(value)
if value.type == "List" or value.type == "Dict" or value.type == "Text"
return NomsuCode tree.source, key_nomsu, ": ", value_nomsu
else
return NomsuCode tree.source, key_nomsu, ": (..)\n ", value_nomsu
when "IndexChain"
nomsu = NomsuCode(tree.source)
for i, bit in ipairs tree
if i > 1
nomsu\append "."
nomsu\append "." if i > 1
local bit_nomsu
if bit.type == "Text" and #bit == 1 and type(bit[1]) == 'string'
bit_nomsu = if bit.type == "Text" and #bit == 1 and type(bit[1]) == 'string' and bit[1]\match("[_a-zA-Z][_a-zA-Z0-9]*")
-- TODO: support arbitrary words here, including operators and unicode
if bit[1]\match("[_a-zA-Z][_a-zA-Z0-9]*")
bit_nomsu = bit[1]
unless bit_nomsu then bit_nomsu = recurse(bit, inline:true)
return nil unless bit_nomsu
bit[1]
else assert recurse(bit, inline:true)
switch bit.type
when "Action", "Block", "IndexChain"
bit_nomsu\parenthesize!

View File

@ -26,7 +26,7 @@ local types = {
"DictEntry",
"IndexChain",
"Action",
"File"
"FileChunks"
}
for _index_0 = 1, #types do
local name = types[_index_0]
@ -52,35 +52,25 @@ for _index_0 = 1, #types do
end)(), ', ')) .. ")"
end
cls.map = function(self, fn)
do
local replacement = fn(self)
if replacement then
return replacement
end
local replacement = fn(self)
if replacement ~= nil then
return replacement or nil
end
local replacements
do
local _accum_0 = { }
local _len_0 = 1
for _index_1 = 1, #self do
local v = self[_index_1]
_accum_0[_len_0] = AST.is_syntax_tree(v) and v:map(fn) or nil
_len_0 = _len_0 + 1
local replacements = { }
local changes = false
for i, v in ipairs(self) do
if AST.is_syntax_tree(v) then
replacement = v:map(fn)
else
replacement = v
end
replacements = _accum_0
changes = changes or (replacement ~= v)
replacements[#replacements + 1] = replacement
end
if not (next(replacements)) then
if not (changes) then
return self
end
return (self.__class)(self.source, unpack((function()
local _accum_0 = { }
local _len_0 = 1
for i, v in ipairs(self) do
_accum_0[_len_0] = replacements[i] or v
_len_0 = _len_0 + 1
end
return _accum_0
end)()))
return (self.__class)(self.source, unpack(replacements))
end
end
AST[name] = setmetatable(cls, {

View File

@ -10,7 +10,7 @@ AST.is_syntax_tree = (n, t=nil)->
type(n) == 'table' and getmetatable(n) and AST[n.type] == getmetatable(n) and (t == nil or n.type == t)
types = {"Number", "Var", "Block", "EscapedNomsu", "Text", "List", "Dict", "DictEntry",
"IndexChain", "Action", "File"}
"IndexChain", "Action", "FileChunks"}
for name in *types
cls = {}
with cls
@ -21,10 +21,18 @@ for name in *types
.is_instance = (x)=> getmetatable(x) == @
.__tostring = => "#{@type}(#{repr tostring(@source)}, #{concat([repr(v) for v in *@], ', ')})"
.map = (fn)=>
if replacement = fn(@) then return replacement
replacements = [AST.is_syntax_tree(v) and v\map(fn) or nil for v in *@]
return @ unless next(replacements)
return (@__class)(@source, unpack([replacements[i] or v for i,v in ipairs(@)]))
replacement = fn(@)
if replacement != nil then return replacement or nil
replacements = {}
changes = false
for i,v in ipairs(@)
replacement = if AST.is_syntax_tree(v)
v\map(fn)
else v
changes or= replacement != v
replacements[#replacements+1] = replacement
return @ unless changes
return (@__class)(@source, unpack(replacements))
AST[name] = setmetatable cls,
__tostring: => @name

View File

@ -110,11 +110,12 @@ setmetatable(NOMSU_DEFS, {
return make_node
end
})
local Parser = { }
local NOMSU_PATTERN
local Parser = {
version = 2,
patterns = { }
}
do
local peg_tidier = re.compile([[ file <- %nl* version %nl* {~ (def/comment) (%nl+ (def/comment))* %nl* ~}
version <- "--" (!"version" [^%nl])* "version" (" ")* (([0-9])+ -> set_version) ([^%nl])*
local peg_tidier = re.compile([[ file <- %nl* {~ (def/comment) (%nl+ (def/comment))* %nl* ~}
def <- anon_def / captured_def
anon_def <- ({ident} (" "*) ":"
{~ ((%nl " "+ def_line?)+) / def_line ~}) -> "%1 <- %2"
@ -124,39 +125,40 @@ do
err <- ("(!!" { (!("!!)") .)* } "!!)") -> "(({} (%1) %%userdata) => error)"
ident <- [a-zA-Z_][a-zA-Z0-9_]*
comment <- "--" [^%nl]*
]], {
set_version = function(v)
Parser.version = tonumber(v), {
nl = NOMSU_DEFS.nl
}
end
})
local peg_file = io.open("nomsu.peg")
if not peg_file and package.nomsupath then
for path in package.nomsupath:gmatch("[^;]+") do
peg_file = io.open(path .. "/nomsu.peg")
if peg_file then
break
]])
for version = 1, Parser.version do
local peg_file = io.open("nomsu." .. tostring(version) .. ".peg")
if not peg_file and package.nomsupath then
for path in package.nomsupath:gmatch("[^;]+") do
peg_file = io.open(path .. "/nomsu." .. tostring(version) .. ".peg")
if peg_file then
break
end
end
end
assert(peg_file, "could not find nomsu .peg file")
local nomsu_peg = peg_tidier:match(peg_file:read('*a'))
peg_file:close()
Parser.patterns[version] = re.compile(nomsu_peg, NOMSU_DEFS)
end
assert(peg_file, "could not find nomsu.peg file")
local nomsu_peg = peg_tidier:match(peg_file:read('*a'))
peg_file:close()
NOMSU_PATTERN = re.compile(nomsu_peg, NOMSU_DEFS)
end
Parser.parse = function(nomsu_code, source)
Parser.parse = function(nomsu_code, source, version)
if source == nil then
source = nil
end
if version == nil then
version = nil
end
source = source or nomsu_code.source
nomsu_code = tostring(nomsu_code)
version = version or nomsu_code:match("^#![^\n]*nomsu[ ]+-V[ ]*([0-9.]+)")
version = (version and tonumber(version)) or Parser.version
local userdata = {
errors = { },
source = source,
comments = { }
}
local tree = NOMSU_PATTERN:match(nomsu_code, nil, userdata)
local tree = Parser.patterns[version]:match(nomsu_code, nil, userdata)
if not (tree) then
error("In file " .. tostring(colored.blue(tostring(source or "<unknown>"))) .. " failed to parse:\n" .. tostring(colored.onyellow(colored.black(nomsu_code))))
end
@ -186,7 +188,7 @@ Parser.parse = function(nomsu_code, source)
end
errors = _accum_0
end
error("Errors occurred while parsing:\n\n" .. table.concat(errors, "\n\n"), 0)
error("Errors occurred while parsing (v" .. tostring(version) .. "):\n\n" .. table.concat(errors, "\n\n"), 0)
end
tree.version = userdata.version
return tree

View File

@ -73,13 +73,12 @@ setmetatable(NOMSU_DEFS, {__index:(key)=>
return make_node
})
Parser = {}
NOMSU_PATTERN = do
Parser = {version:2, patterns:{}}
do
-- Just for cleanliness, I put the language spec in its own file using a slightly modified
-- version of the lpeg.re syntax.
peg_tidier = re.compile [[
file <- %nl* version %nl* {~ (def/comment) (%nl+ (def/comment))* %nl* ~}
version <- "--" (!"version" [^%nl])* "version" (" ")* (([0-9])+ -> set_version) ([^%nl])*
file <- %nl* {~ (def/comment) (%nl+ (def/comment))* %nl* ~}
def <- anon_def / captured_def
anon_def <- ({ident} (" "*) ":"
{~ ((%nl " "+ def_line?)+) / def_line ~}) -> "%1 <- %2"
@ -89,24 +88,27 @@ NOMSU_PATTERN = do
err <- ("(!!" { (!("!!)") .)* } "!!)") -> "(({} (%1) %%userdata) => error)"
ident <- [a-zA-Z_][a-zA-Z0-9_]*
comment <- "--" [^%nl]*
]], {set_version: (v) -> Parser.version = tonumber(v), nl:NOMSU_DEFS.nl}
peg_file = io.open("nomsu.peg")
if not peg_file and package.nomsupath
for path in package.nomsupath\gmatch("[^;]+")
peg_file = io.open(path.."/nomsu.peg")
break if peg_file
assert(peg_file, "could not find nomsu.peg file")
nomsu_peg = peg_tidier\match(peg_file\read('*a'))
peg_file\close!
re.compile(nomsu_peg, NOMSU_DEFS)
]]
for version=1,Parser.version
peg_file = io.open("nomsu.#{version}.peg")
if not peg_file and package.nomsupath
for path in package.nomsupath\gmatch("[^;]+")
peg_file = io.open(path.."/nomsu.#{version}.peg")
break if peg_file
assert(peg_file, "could not find nomsu .peg file")
nomsu_peg = peg_tidier\match(peg_file\read('*a'))
peg_file\close!
Parser.patterns[version] = re.compile(nomsu_peg, NOMSU_DEFS)
Parser.parse = (nomsu_code, source=nil)->
Parser.parse = (nomsu_code, source=nil, version=nil)->
source or= nomsu_code.source
nomsu_code = tostring(nomsu_code)
version or= nomsu_code\match("^#![^\n]*nomsu[ ]+-V[ ]*([0-9.]+)")
version = (version and tonumber(version)) or Parser.version
userdata = {
errors: {}, :source, comments: {}
}
tree = NOMSU_PATTERN\match(nomsu_code, nil, userdata)
tree = Parser.patterns[version]\match(nomsu_code, nil, userdata)
unless tree
error "In file #{colored.blue tostring(source or "<unknown>")} failed to parse:\n#{colored.onyellow colored.black nomsu_code}"
if type(tree) == 'number'
@ -116,7 +118,7 @@ Parser.parse = (nomsu_code, source=nil)->
keys = [k for k,v in pairs(userdata.errors)]
table.sort(keys)
errors = [userdata.errors[k] for k in *keys]
error("Errors occurred while parsing:\n\n"..table.concat(errors, "\n\n"), 0)
error("Errors occurred while parsing (v#{version}):\n\n"..table.concat(errors, "\n\n"), 0)
tree.version = userdata.version
return tree

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#..
Tests for the stuff defined in core/control_flow.nom

View File

@ -1,2 +1,3 @@
#!/usr/bin/env nomsu -V1
use "lib/consolecolor.nom"
say: bright: green "Color test passed."

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
Tests for the stuff defined in core/control_flow.nom

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
Tests for the stuff defined in core/control_flow.nom

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
Tests for the stuff defined in core/errors.nom

View File

@ -1,2 +1,3 @@
#!/usr/bin/env nomsu -V1
use "core"
say "Inner directory 'use' test passed."

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#..
Tests for the stuff defined in core/control_flow.nom

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
Tests for the stuff defined in core/metaprogramming.nom

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
Tests for the object model defined in lib/object.nom

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#..
Tests for the stuff defined in core/operators.nom

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#
Tests for the stuff defined in lib/os.nom
@ -5,7 +6,7 @@ use "core"
use "lib/os.nom"
%lines <-: lines in: read file "tests/os.nom"
assume: %lines.2 = " Tests for the stuff defined in lib/os.nom"
assume: %lines.3 = " Tests for the stuff defined in lib/os.nom"
%n <- 0
for file %f in "core"

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
use "core"
%x <- "outer"

View File

@ -1,3 +1,4 @@
#!/usr/bin/env nomsu -V1
#..
Tests for the stuff defined in core/text.nom