aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core.nom8
-rw-r--r--examples/sample_code.nom11
-rw-r--r--examples/sample_game.nom18
-rw-r--r--examples/tutorial.nom47
-rw-r--r--nomsu.lua105
-rwxr-xr-xnomsu.moon83
6 files changed, 156 insertions, 116 deletions
diff --git a/core.nom b/core.nom
index 830e312..810859c 100644
--- a/core.nom
+++ b/core.nom
@@ -115,13 +115,6 @@ rule "%a != %b":
rule "say %str":
lua block ["print(utils.repr(", %str, "))"]
-rule "printf %str":
- lua block ".."
- |for _,s in ipairs(vars.str) do
- | io.write(utils.repr(s))
- |end
- |io.write("\n")
-
rule "do %action":
lua expr "vars.action(compiler, setmetatable({}, {__index=vars}))"
@@ -161,6 +154,7 @@ macro block "for %varname in %iterable %body":
"\n vars[", %varname, "] = old_loopval"
"\nend"
+
rule "%start up to %stop":
lua expr "utils.range(vars.start,vars.stop-1)"
diff --git a/examples/sample_code.nom b/examples/sample_code.nom
index 63078d7..2917be1 100644
--- a/examples/sample_code.nom
+++ b/examples/sample_code.nom
@@ -104,12 +104,11 @@ if %x:
say "three"
-printf [..]
- ".."
- |this is a longstring
- |
- .., "with", ".."
- | multiple lines
+say ".."
+ |this is a longstring
+ |
+ | with multiple lines
+ | and an interpolated expression: \2 + 5\
rule "%n bottles":
lua block [..]
diff --git a/examples/sample_game.nom b/examples/sample_game.nom
index 8ac7e7a..52137d2 100644
--- a/examples/sample_game.nom
+++ b/examples/sample_game.nom
@@ -89,7 +89,8 @@ with everyone's approval:
..else:
let "approvers" = (number of (* %pending = "approved"))
let "num-players" = (number of (players))
- printf [%approvers, "/", %num-players, " players have approved"]
+ say ".."
+ |\number of (* %pending = "approved")\/\number of (players)\ players have approved.
rule ["reject", "vote no", "vote nay", "veto", "disapprove"]:
let "pending" = ("pending proposal" "is" ?)
@@ -104,7 +105,8 @@ with everyone's approval:
rule "join":
(you) "is a player" = (yes)
- printf ["Welcome to the game, ", you,"!"]
+ say ".."
+ |Welcome to the game, \you\!
allow "unpropose" to use "set all * % = %"
allow ["join", "mark % as approving %", "mark % as rejecting %", "propose %", "unpropose"] to use "% % = %"
@@ -149,9 +151,11 @@ propose:
rule "vote for %candidate":
(you) "votes for" = %candidate
let "vote-percent" = ((number of (* "votes for" = %candidate)) / (number of (players)))
- printf ["Vote cast. ",%candidate," now has ",(100 * %vote-percent),"% of the votes."]
+ say ".."
+ |Vote cast. \%candidate\ now has \100 * %vote-percent\% of the votes.
if (%vote-percent > 0.5):
- printf ["The winner of the election is:", %candidate]
+ say ".."
+ |The winner of the election is: \%candidate\
close the election
allow ["open election %", "close the election", "vote for %"] to use ["% % = %"]
@@ -163,7 +167,8 @@ propose:
if ((you) == "Anonymous"):
make "bill" do %action
..else:
- printf ["Who do you think you are, ", you,"?"]
+ say ".."
+ |Who do you think you are, \you\?
allow ["as bill %"] to use ["make % %"]
approve
as bill: join
@@ -173,7 +178,8 @@ propose:
if ((you) == "Anonymous"):
make "dave" do %action
..else:
- printf ["Who do you think you are, ", you,"?"]
+ say ".."
+ |Who do you think you are, \you\?
allow ["as dave %"] to use ["make % %"]
approve
as bill: approve
diff --git a/examples/tutorial.nom b/examples/tutorial.nom
index 4a9c213..14a487f 100644
--- a/examples/tutorial.nom
+++ b/examples/tutorial.nom
@@ -13,8 +13,7 @@ run file "core.nom"
# Strings:
"asdf"
-".."
- |This is a multi-line string with a #.. fake comment
+".."|This is a multi-line string with a #.. fake comment
|that starts with ".." and includes each indented line that starts with a "|"
|until the indentation ends
@@ -101,14 +100,22 @@ say both ".."
"-- Abraham Lincoln"
rule "my favorite number": return 23
+
# Subexpressions are wrapped in parentheses:
-# printf takes a list of bits that are converted to strings and concatenated together, and printed
-printf ["My favorite number is ", my favorite number]
+say (my favorite number)
# There's a multi-line indented block form for subexpressions too:
-printf [..]
- "My favorite number is still ", (..)
+say (..)
+ my favorite
+ number
+
+# Block strings can interpolate values by enclosing an expression in a pair of \s
+say ".."|My favorite number is \my favorite number\!
+
+say ".."
+ |My favorite number is still \(..)
my favorite
number
+ ..\, but this time it uses an indented subexpression!
#.. There's a few macros in the language for things like conditional branches and logic/math
operations, but they can be thought of as basically the same as functions.
@@ -146,17 +153,15 @@ if (1 > 10):
let "numbers" = [5,6,7]
# Looping:
-printf ["Looping over: ",%numbers,"!"]
+say ".."|Looping over \%numbers\:
for "number" in %numbers:
say (%number + 100)
rule "sing %starting-bottles bottles of beer":
for "n" in (%starting-bottles down through 0):
- printf [..]
- (%n if (%n > 0) else "No more")
- (" bottle" if (%n == 1) else " bottles")
- " of beer on the wall."
- ("" if (%n == 0) else " Take one down, pass it around...")
+ say ".."
+ |\%n if (%n > 0) else "No more"\ \"bottle" if (%n == 1) else "bottles"\ of beer on the wall.
+ |\"" if (%n == 0) else " Take one down, pass it around..."\
sing 9 bottles of beer
@@ -182,25 +187,23 @@ any of [0,0,0,0,1,0,0]
rule "say the time":
lua block ".."
|io.write("The OS time is: ")
- |io.write(tostring(os.time()).."\n")
+ |io.write(tostring(os.time()).."\\n")
say the time
-printf ["Math expression result is: ", lua expr "(1 + 2*3 + 3*4)^2"]
+say ".."|Math expression result is: \lua expr "(1 + 2*3 + 3*4)^2"\
#.. In the lua environment, "vars" can be used to get local variables/function args, and
"compiler" can be used to access the compiler, function defs, and other things
rule "square root of %n":
return (lua expr "math.sqrt(vars.n)")
-printf ["the square root of 2 is ", square root of 2]
+say ".."|The square root of 2 is \square root of 2\
# Macros can be defined as functions that take unprocessed syntax trees and return lua code
# "macro block %" is for defining macros that produce blocks of code, not values
macro block "unless %condition %body":
- concat [..]
- # "% as lua expr" and "% as lua block" are two useful helper functions here.
- "if not (", %condition as lua expr, ") then"
- # Extract the inner part of the code block's body and insert it:
- "\n ", (lua expr "vars.body.value.value") as lua block
- "\nend"
+ ".."
+ |if not (\%condition as lua expr\) then
+ | \(lua expr "vars.body.value.value") as lua block\
+ |end
unless (1 > 10):
say "Macros work!"
@@ -208,6 +211,6 @@ unless (1 > 10):
# and "macro %" is for defining macros that produce an expression
macro "%value as a boolean":
- concat ["(not not (", %value as lua expr, "))"]
+ ".."|(not not (\%value as lua expr\))
macro "yep": "true"
diff --git a/nomsu.lua b/nomsu.lua
index dfe4459..2abf7da 100644
--- a/nomsu.lua
+++ b/nomsu.lua
@@ -59,7 +59,7 @@ make_parser = function(lingo, extra_definitions)
end
return end_pos
end
- local wordchar = P(1) - S(' \t\n\r%#:;,.{}[]()"')
+ local wordchar = P(1) - S(' \t\n\r%#:;,.{}[]()"\\')
local nl = P("\n")
local whitespace = S(" \t") ^ 1
local blank_line = whitespace ^ -1 * nl
@@ -288,7 +288,17 @@ do
expression <- ({ (longstring / string / number / variable / list / thunk / subexpression) }) -> Expression
string <- ({ (!longstring) '"' {(("\" [^%nl]) / [^"%nl])*} '"' }) -> String
- longstring <- ({ '".."' %ws? %line_comment? %indent {(%new_line "|" [^%nl]*)+} ((%dedent (%new_line '..')?) / errors) }) -> Longstring
+ longstring <- ({ '".."' %ws?
+ {|
+ (("|" {| ({("\\" / (!string_interpolation [^%nl]))+} / string_interpolation)* |})
+ / %line_comment)?
+ (%indent
+ (%new_line "|" {|
+ ({("\\" / (!string_interpolation [^%nl]))+} / string_interpolation)*
+ |})+
+ ((%dedent (%new_line '..')?) / errors))?
+ |}}) -> Longstring
+ string_interpolation <- "\" %ws? (functioncall / expression) %ws? "\"
number <- ({ {'-'? [0-9]+ ("." [0-9]+)?} }) -> Number
variable <- ({ ("%" {%wordchar+}) }) -> Var
@@ -314,13 +324,13 @@ do
assert(tree, "Failed to parse: " .. tostring(str))
return tree
end,
- tree_to_value = function(self, tree)
- local code = "return (function(compiler, vars)\nreturn " .. tostring(self:tree_to_lua(tree)) .. "\nend)"
+ tree_to_value = function(self, tree, vars)
+ local code = "\n local utils = require('utils')\n return (function(compiler, vars)\nreturn " .. tostring(self:tree_to_lua(tree)) .. "\nend)"
local lua_thunk, err = load(code)
if not lua_thunk then
error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err))
end
- return (lua_thunk())(self, { })
+ return (lua_thunk())(self, vars or { })
end,
tree_to_lua = function(self, tree, kind)
if kind == nil then
@@ -347,9 +357,9 @@ do
for _index_0 = 1, #_list_0 do
local statement = _list_0[_index_0]
local code = to_lua(statement)
- local lua_thunk, err = load("return (function(compiler, vars)\n" .. tostring(code) .. "\nend)")
+ local lua_thunk, err = load("\n local utils = require('utils')\n return (function(compiler, vars)\n" .. tostring(code) .. "\nend)")
if not lua_thunk then
- error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err))
+ error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err) .. "\n\nProduced by statement:\n" .. tostring(utils.repr(statement)))
end
local ok
ok, err = pcall(lua_thunk)
@@ -429,17 +439,35 @@ do
end))
add(utils.repr(unescaped, true))
elseif "Longstring" == _exp_0 then
- local result
- do
- local _accum_0 = { }
- local _len_0 = 1
- for line in tree.value:gmatch("[ \t]*|([^\n]*)") do
- _accum_0[_len_0] = line
- _len_0 = _len_0 + 1
+ local concat_parts = { }
+ local string_buffer = ""
+ for i, line in ipairs(tree.value) do
+ if i > 1 then
+ string_buffer = string_buffer .. "\n"
end
- result = _accum_0
+ for _index_0 = 1, #line do
+ local bit = line[_index_0]
+ if type(bit) == "string" then
+ string_buffer = string_buffer .. bit:gsub("\\\\", "\\")
+ else
+ if string_buffer ~= "" then
+ table.insert(concat_parts, utils.repr(string_buffer, true))
+ string_buffer = ""
+ end
+ table.insert(concat_parts, "utils.repr(" .. tostring(to_lua(bit)) .. ")")
+ end
+ end
+ end
+ if string_buffer ~= "" then
+ table.insert(concat_parts, utils.repr(string_buffer, true))
+ end
+ if #concat_parts == 0 then
+ add("''")
+ elseif #concat_parts == 1 then
+ add(concat_parts[1])
+ else
+ add("(" .. tostring(table.concat(concat_parts, "..")) .. ")")
end
- add(utils.repr(table.concat(result, "\n"), true))
elseif "Number" == _exp_0 then
add(tree.value)
elseif "List" == _exp_0 then
@@ -670,34 +698,12 @@ do
end,
initialize_core = function(self)
local as_lua_code
- as_lua_code = function(self, str)
+ as_lua_code = function(self, str, vars)
local _exp_0 = str.type
if "String" == _exp_0 then
- local escapes = {
- n = "\n",
- t = "\t",
- b = "\b",
- a = "\a",
- v = "\v",
- f = "\f",
- r = "\r"
- }
- local unescaped = str.value:gsub("\\(.)", (function(c)
- return escapes[c] or c
- end))
- return unescaped
+ return self:tree_to_value(str, vars)
elseif "Longstring" == _exp_0 then
- local result
- do
- local _accum_0 = { }
- local _len_0 = 1
- for line in str.value:gmatch("[ \t]*|([^\n]*)") do
- _accum_0[_len_0] = line
- _len_0 = _len_0 + 1
- end
- result = _accum_0
- end
- return table.concat(result, "\n")
+ return self:tree_to_value(str, vars)
else
return self:tree_to_lua(str)
end
@@ -715,13 +721,13 @@ do
local _list_0 = lua_code.value
for _index_0 = 1, #_list_0 do
local i = _list_0[_index_0]
- _accum_0[_len_0] = as_lua_code(self, i.value)
+ _accum_0[_len_0] = as_lua_code(self, i.value, vars)
_len_0 = _len_0 + 1
end
return _accum_0
end)()), true
else
- return as_lua_code(self, lua_code), true
+ return as_lua_code(self, lua_code, vars), true
end
end)
self:defmacro([[lua expr %lua_code]], function(self, vars, kind)
@@ -734,13 +740,13 @@ do
local _list_0 = lua_code.value
for _index_0 = 1, #_list_0 do
local i = _list_0[_index_0]
- _accum_0[_len_0] = as_lua_code(self, i.value)
+ _accum_0[_len_0] = as_lua_code(self, i.value, vars)
_len_0 = _len_0 + 1
end
return _accum_0
end)())
else
- return as_lua_code(self, lua_code)
+ return as_lua_code(self, lua_code, vars)
end
end)
self:def("rule %spec %body", function(self, vars)
@@ -750,15 +756,15 @@ do
if kind == "Expression" then
error("Macro definitions cannot be used as expressions.")
end
- self:defmacro(self:tree_to_value(vars.spec), self:tree_to_value(vars.body))
+ self:defmacro(self:tree_to_value(vars.spec, vars), self:tree_to_value(vars.body, vars))
return "", true
end)
self:defmacro([[macro block %spec %body]], function(self, vars, kind)
if kind == "Expression" then
error("Macro definitions cannot be used as expressions.")
end
- local invocation = self:tree_to_value(vars.spec)
- local fn = self:tree_to_value(vars.body)
+ local invocation = self:tree_to_value(vars.spec, vars)
+ local fn = self:tree_to_value(vars.body, vars)
self:defmacro(invocation, (function(self, vars, kind)
if kind == "Expression" then
error("Macro: " .. tostring(invocation) .. " was defined to be a block, not an expression.")
@@ -837,7 +843,8 @@ if arg and arg[1] then
else
output = io.open(arg[2], 'w')
end
- output:write([[ local load = function()
+ output:write([[ local utils = require('utils')
+ local load = function()
]])
output:write(code)
output:write([[
diff --git a/nomsu.moon b/nomsu.moon
index d0b9723..a27354a 100755
--- a/nomsu.moon
+++ b/nomsu.moon
@@ -4,7 +4,6 @@ lpeg = require 'lpeg'
utils = require 'utils'
-- TODO:
--- string interpolation
-- improve indentation of generated lua code
-- provide way to run precompiled nomsu -> lua code
-- comprehensions?
@@ -44,7 +43,7 @@ make_parser = (lingo, extra_definitions)->
if num_spaces != indent_stack[#indent_stack] then return nil
return end_pos
- wordchar = P(1)-S(' \t\n\r%#:;,.{}[]()"')
+ wordchar = P(1)-S(' \t\n\r%#:;,.{}[]()"\\')
nl = P("\n")
whitespace = S(" \t")^1
blank_line = whitespace^-1 * nl
@@ -199,7 +198,17 @@ class NomsuCompiler
expression <- ({ (longstring / string / number / variable / list / thunk / subexpression) }) -> Expression
string <- ({ (!longstring) '"' {(("\" [^%nl]) / [^"%nl])*} '"' }) -> String
- longstring <- ({ '".."' %ws? %line_comment? %indent {(%new_line "|" [^%nl]*)+} ((%dedent (%new_line '..')?) / errors) }) -> Longstring
+ longstring <- ({ '".."' %ws?
+ {|
+ (("|" {| ({("\\" / (!string_interpolation [^%nl]))+} / string_interpolation)* |})
+ / %line_comment)?
+ (%indent
+ (%new_line "|" {|
+ ({("\\" / (!string_interpolation [^%nl]))+} / string_interpolation)*
+ |})+
+ ((%dedent (%new_line '..')?) / errors))?
+ |}}) -> Longstring
+ string_interpolation <- "\" %ws? (functioncall / expression) %ws? "\"
number <- ({ {'-'? [0-9]+ ("." [0-9]+)?} }) -> Number
variable <- ({ ("%" {%wordchar+}) }) -> Var
@@ -225,12 +234,15 @@ class NomsuCompiler
assert tree, "Failed to parse: #{str}"
return tree
- tree_to_value: (tree)=>
- code = "return (function(compiler, vars)\nreturn #{@tree_to_lua(tree)}\nend)"
+ tree_to_value: (tree, vars)=>
+ -- TODO: clean up require utils
+ code = "
+ local utils = require('utils')
+ return (function(compiler, vars)\nreturn #{@tree_to_lua(tree)}\nend)"
lua_thunk, err = load(code)
if not lua_thunk
error("Failed to compile generated code:\n#{code}\n\n#{err}")
- return (lua_thunk!)(self, {})
+ return (lua_thunk!)(self, vars or {})
tree_to_lua: (tree, kind="Expression")=>
assert tree, "No tree provided."
@@ -251,9 +263,12 @@ class NomsuCompiler
for statement in *tree.value.body.value
code = to_lua(statement)
-- Run the fuckers as we go
- lua_thunk, err = load("return (function(compiler, vars)\n#{code}\nend)")
+ -- TODO: clean up repeated loading of utils?
+ lua_thunk, err = load("
+ local utils = require('utils')
+ return (function(compiler, vars)\n#{code}\nend)")
if not lua_thunk
- error("Failed to compile generated code:\n#{code}\n\n#{err}")
+ error("Failed to compile generated code:\n#{code}\n\n#{err}\n\nProduced by statement:\n#{utils.repr(statement)}")
ok,err = pcall(lua_thunk)
if not ok then error(err)
ok,err = pcall(err, self, vars)
@@ -308,9 +323,28 @@ class NomsuCompiler
add utils.repr(unescaped, true)
when "Longstring"
- -- TODO: handle comments here?
- result = [line for line in tree.value\gmatch("[ \t]*|([^\n]*)")]
- add utils.repr(table.concat(result, "\n"), true)
+ concat_parts = {}
+ string_buffer = ""
+ for i,line in ipairs(tree.value)
+ if i > 1 then string_buffer ..= "\n"
+ for bit in *line
+ if type(bit) == "string"
+ string_buffer ..= bit\gsub("\\\\","\\")
+ else
+ if string_buffer ~= ""
+ table.insert concat_parts, utils.repr(string_buffer, true)
+ string_buffer = ""
+ table.insert concat_parts, "utils.repr(#{to_lua(bit)})"
+
+ if string_buffer ~= ""
+ table.insert concat_parts, utils.repr(string_buffer, true)
+
+ if #concat_parts == 0
+ add "''"
+ elseif #concat_parts == 1
+ add concat_parts[1]
+ else
+ add "(#{table.concat(concat_parts, "..")})"
when "Number"
add tree.value
@@ -482,17 +516,12 @@ class NomsuCompiler
initialize_core: =>
-- Sets up some core functionality
- as_lua_code = (str)=>
+ as_lua_code = (str, vars)=>
switch str.type
when "String"
- escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r"
- unescaped = str.value\gsub("\\(.)", ((c)-> escapes[c] or c))
- return unescaped
-
+ return @tree_to_value(str, vars)
when "Longstring"
- -- TODO: handle comments?
- result = [line for line in str.value\gmatch("[ \t]*|([^\n]*)")]
- return table.concat(result, "\n")
+ return @tree_to_value(str, vars)
else
return @tree_to_lua(str)
@@ -502,18 +531,18 @@ class NomsuCompiler
switch lua_code.type
when "List"
-- TODO: handle subexpressions
- return table.concat([as_lua_code(@, i.value) for i in *lua_code.value]), true
+ return table.concat([as_lua_code(@, i.value, vars) for i in *lua_code.value]), true
else
- return as_lua_code(@, lua_code), true
+ return as_lua_code(@, lua_code, vars), true
@defmacro [[lua expr %lua_code]], (vars, kind)=>
lua_code = vars.lua_code.value
switch lua_code.type
when "List"
-- TODO: handle subexpressions
- return table.concat([as_lua_code(@, i.value) for i in *lua_code.value])
+ return table.concat([as_lua_code(@, i.value, vars) for i in *lua_code.value])
else
- return as_lua_code(@, lua_code)
+ return as_lua_code(@, lua_code, vars)
@def "rule %spec %body", (vars)=>
@def vars.spec, vars.body
@@ -521,14 +550,14 @@ class NomsuCompiler
@defmacro [[macro %spec %body]], (vars, kind)=>
if kind == "Expression"
error("Macro definitions cannot be used as expressions.")
- @defmacro @tree_to_value(vars.spec), @tree_to_value(vars.body)
+ @defmacro @tree_to_value(vars.spec, vars), @tree_to_value(vars.body, vars)
return "", true
@defmacro [[macro block %spec %body]], (vars, kind)=>
if kind == "Expression"
error("Macro definitions cannot be used as expressions.")
- invocation = @tree_to_value(vars.spec)
- fn = @tree_to_value(vars.body)
+ invocation = @tree_to_value(vars.spec, vars)
+ fn = @tree_to_value(vars.body, vars)
@defmacro invocation, ((vars,kind)=>
if kind == "Expression"
error("Macro: #{invocation} was defined to be a block, not an expression.")
@@ -544,6 +573,7 @@ class NomsuCompiler
-- and "./nomsu.moon input_file.nom output_file.lua" to compile (use "-" to compile to stdout)
if arg and arg[1]
c = NomsuCompiler()
+ --c.debug = true
input = io.open(arg[1])\read("*a")
-- Kinda hacky, if run via "./nomsu.moon file.nom -", then silence print and io.write
-- during execution and re-enable them to print out the generated source code
@@ -562,6 +592,7 @@ if arg and arg[1]
else io.open(arg[2], 'w')
output\write [[
+ local utils = require('utils')
local load = function()
]]
output\write(code)