Working string interpolation!

This commit is contained in:
Bruce Hill 2017-09-14 02:41:10 -07:00
parent 0f228d2d25
commit 83463f11c5
6 changed files with 156 additions and 116 deletions

View File

@ -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)"

View File

@ -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 [..]

View File

@ -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

View File

@ -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"

105
nomsu.lua
View File

@ -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
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
result = _accum_0
end
add(utils.repr(table.concat(result, "\n"), true))
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
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([[

View File

@ -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)