diff --git a/core.nom b/core.nom index 3b0cf32..830e312 100644 --- a/core.nom +++ b/core.nom @@ -1,4 +1,3 @@ -(# Global import #) lua block ".." |utils = require('utils') @@ -107,12 +106,11 @@ macro "%a or %b or %c or %d": macro "%a mod %b": concat ["(",%a as lua expr," mod ",%b as lua expr,")"] macro "- %a": concat ["-(",%a as lua expr,")"] macro "not %a": concat ["not (",%a as lua expr,")"] -macro "# %a": concat ["#(",%a as lua expr,")"] -(# This does equivalence checking instead of identity checking. #) rule "%a == %b": - lua expr "utils.equivalent(vars.a, vars.b)" -macro "%a != %b": concat ["not (",%a as lua expr," == ",%b as lua expr,")"] + lua expr "((vars.a == vars.b) or utils.equivalent(vars.a, vars.b))" +rule "%a != %b": + lua expr "((vars.a ~= vars.b) or not utils.equivalent(vars.a, vars.b))" rule "say %str": lua block ["print(utils.repr(", %str, "))"] diff --git a/examples/sample_code.nom b/examples/sample_code.nom old mode 100755 new mode 100644 index c8b48f7..63078d7 --- a/examples/sample_code.nom +++ b/examples/sample_code.nom @@ -1,18 +1,21 @@ -(# This is just a comment #) -(# Nested comments (# like this #) work fine #) +# This is just a comment +#.. Block comments + start with a #.. and + continue until dedent + run file "core.nom" say "foo" say (4) -(# "rule" is just a function that takes a function call spec and a block of code to run, - and stores the function definition #) +#.. "rule" is just a function that takes a function call spec and a block of code to run, + and stores the function definition rule "fart": say "poot" fart -(# multi-line strings: #) +# multi-line strings: say ".." | Once upon a time |there was a very @@ -30,7 +33,7 @@ say ".." |(done) | -rule "doublefart": (# this farts twice #) +rule "doublefart": # this farts twice say "poot" say "poot" diff --git a/examples/sample_game.nom b/examples/sample_game.nom index 010e610..8ac7e7a 100644 --- a/examples/sample_game.nom +++ b/examples/sample_game.nom @@ -31,13 +31,13 @@ rule "you": lua expr "(you or 'Anonymous')" rule ["make %person %action", "make %person do %action"]: - lua block [..] - "do" - "\n local old_you = you" - "\n you = vars.person" - "\n ret = compiler:call('do %', vars.action)" - "\n you = old_you" - "\nend" + lua block ".." + |do + | local old_you = you + | you = vars.person + | ret = compiler:call('do %', vars.action) + | you = old_you + |end say "====================================================" say " NEW GAME" diff --git a/examples/tutorial.nom b/examples/tutorial.nom index 147a292..4a9c213 100644 --- a/examples/tutorial.nom +++ b/examples/tutorial.nom @@ -1,22 +1,24 @@ -(# Comments use (# ... #), and can be nested #) +# One liner comments start with # and go till end of line +#.. Multi-line comments start with #.. and + continue until dedent -(# Import files like so: #) +# Import files like so: run file "core.nom" -(# Numbers: #) +# Numbers: 23 4.5 -(# Since this language cross-compiles to lua, integers and floating point numbers are - both represented using the same primitive. #) +#.. Since this language cross-compiles to lua, integers and floating point numbers are + both represented using the same primitive. -(# Strings: #) +# Strings: "asdf" ".." - |This is a multi-line string + |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 -(# Lists: #) +# Lists: [1,2,3] [..] "like multi-line strings, lists have an indented form", "that can use commas too" @@ -24,73 +26,73 @@ run file "core.nom" 5 6,7,8 -(# Function calls: #) +# Function calls: say "Hello world!" -(# Function definition: #) +# Function definition: rule "say both %first and also %second": - (# Variables use the "%" sign: #) + # Variables use the "%" sign: say %first say %second -(# Function calls can have parts of the function's name spread throughout. - Everything that's not a literal value is treated as part of the function's name #) +#.. Function calls can have parts of the function's name spread throughout. + Everything that's not a literal value is treated as part of the function's name say both "Hello" and also "again!" -(# Functions can even have their name at the end: #) +# Functions can even have their name at the end: rule "%what-she-said is what she said": say %what-she-said say "-- she said" "Howdy pardner" is what she said -(# The language only reserves []{}().,:;% as special characters, so functions and variables - can have really funky names! #) -rule ">> %foo-bar###^ --> %@@& _~-^-~_~-^ %1 !": - say %foo-bar###^ +#.. The language only reserves []{}().,:;% as special characters, so functions and variables + can have really funky names! +rule ">> %foo-bar$$$^ --> %@@& _~-^-~_~-^ %1 !": + say %foo-bar$$$^ say %@@& say %1 >> "wow" --> "so flexible!" _~-^-~_~-^ "even numbers can be variables!" ! -(# Though literals can't be used in function names #) +# Though literals can't be used in function names -(# Math and logic operations are just treated the same as function calls in the syntax #) +# Math and logic operations are just treated the same as function calls in the syntax say (2 + 3) -(# So it's easy to define your own operators #) +# So it's easy to define your own operators rule "%a ++ %b": 2 * (%a + %b) say (2 ++ 3) -(# Code blocks start with ":" and either continue until the end of the line - or are indented blocks #) +#.. Code blocks start with ":" and either continue until the end of the line + or are indented blocks -(# One liner: #) +# One liner: : say "hi" -(# Block version: #) +# Block version: : say "one" say "two" -(# So the function definitions above are actually just passing a regular string, like +#.. So the function definitions above are actually just passing a regular string, like "say both %first and also %second", and a code block to a function called "rule % %" - that takes two arguments. #) + that takes two arguments. -(# Line continuations work by either ending a line with ".." and continuing with an indented block: #) +# Line continuations work by either ending a line with ".." and continuing with an indented block: say.. both "Tom" and also "Sawyer" -(# Or by starting the next line with ".." #) +# Or by starting the next line with ".." say both "Bruce" ..and also "Lee" -(# This can be mixed and matched: #) +# This can be mixed and matched: say both.. "Rick" ..and also.. "Moranis" -(# And combined with the block forms of literals: #) +# And combined with the block forms of literals: say both ".." |Four score and seven years ago our fathers brought forth, upon this continent, |a new nation, conceived in liberty, and dedicated to the proposition that @@ -99,31 +101,31 @@ 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 #) +# 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] -(# There's a multi-line indented block form for subexpressions too: #) +# There's a multi-line indented block form for subexpressions too: printf [..] "My favorite number is still ", (..) my favorite number -(# There's a few macros in the language for things like conditional branches and logic/math +#.. 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. - There are no keywords in the language! #) + There are no keywords in the language! if (1 < 10): say "One is less than ten" ..else: say "One is not less than ten" -(# Breakdown of the above: #) -(# Function call (actually a macro) to "if % % else %" #) -(# First argument is a subexpression that is a function call (also a macro) to "% < %" - that performs a comparison on its arguments, 1 and 10 #) -(# Second argument is a block of code that includes a function call to "say %", the "if" body #) -(# Third argument is a block of code that includes a different function call to "say %", the "else" body #) +#.. Breakdown of the above: + Function call (actually a macro) to "if % % else %" + First argument is a subexpression that is a function call (also a macro) to "% < %" + that performs a comparison on its arguments, 1 and 10 + Second argument is a block of code that includes a function call to "say %", the "if" body + Third argument is a block of code that includes a different function call to "say %", the "else" body -(# Line continuations can be used for "elseif" #) +# Line continuations can be used for "elseif" if (1 > 10): say "First condition" ..else: if (1 > 5): @@ -131,7 +133,7 @@ if (1 > 10): ..else: say "Last condition" -(# ^that's the same as: #) +# ^that's the same as: if (1 > 10): say "First condition" ..else: @@ -140,10 +142,10 @@ if (1 > 10): ..else: say "Last condition" -(# Variables are modified with a macro, "let % = %" #) +# Variables are modified with a macro, "let % = %" let "numbers" = [5,6,7] -(# Looping: #) +# Looping: printf ["Looping over: ",%numbers,"!"] for "number" in %numbers: say (%number + 100) @@ -158,45 +160,45 @@ rule "sing %starting-bottles bottles of beer": sing 9 bottles of beer - -(# Note that because math and logic operations are just macros, they require a lot - of parentheses to disambiguate. There's no PEMDAS. #) +#.. Note that because math and logic operations are just macros, they require a lot + of parentheses to disambiguate. There's no PEMDAS. say (5 + (4 * (- (1 + (6 + 2))))) -(# For convenience, +,*,"and", and "or" have been hand defined to work with up to 4 operands: #) +# For convenience, +,*,"and", and "or" have been hand defined to work with up to 4 operands: 1 + 2 + 3 + 4 1 * 2 * 3 * 4 1 and 2 and 3 and 4 1 or 2 or 3 or 4 -(# Longer lists can use "sum of %", "product of %", "all of %", and "any of %", respectively, or lots of parentheses. #) +# Longer lists can use "sum of %", "product of %", "all of %", and "any of %", respectively, or lots of parentheses. sum of [1,2,3,4,5,6,7] product of [1,2,3,4,5,6,7] all of [1,1,1,1,0,1,1] any of [0,0,0,0,1,0,0] -(# And 3-operand chained inequality comparisons have been defined: #) +# And 3-operand chained inequality comparisons have been defined: 1 < 2 <= 3 -(# Macros: #) -(# The "lua block %" and "lua expr %" macros can be used to write raw lua code: #) +# Macros: +# The "lua block %" and "lua expr %" macros can be used to write raw lua code: rule "say the time": lua block ".." |io.write("The OS time is: ") |io.write(tostring(os.time()).."\n") say the time printf ["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 #) + +#.. 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] -(# 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 #) +# 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. #) + # "% 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: #) + # Extract the inner part of the code block's body and insert it: "\n ", (lua expr "vars.body.value.value") as lua block "\nend" @@ -204,7 +206,7 @@ unless (1 > 10): say "Macros work!" say "It looks like a keyword, but there's no magic here!" -(# and "macro %" is for defining macros that produce an expression #) +# and "macro %" is for defining macros that produce an expression macro "%value as a boolean": concat ["(not not (", %value as lua expr, "))"] macro "yep": "true" diff --git a/nomsu.lua b/nomsu.lua index c3c3f40..dfe4459 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -5,11 +5,6 @@ local INDENT = " " lpeg.setmaxstack(10000) local P, V, S, Cg, C, Cp, B, Cmt P, V, S, Cg, C, Cp, B, Cmt = lpeg.P, lpeg.V, lpeg.S, lpeg.Cg, lpeg.C, lpeg.Cp, lpeg.B, lpeg.Cmt -local wordchar = P(1) - S(' \t\n\r%:;,.{}[]()"') -local comment = re.compile([[comment <- "(#" (comment / ((! "#)") .))* "#)"]]) -local whitespace = (S(" \t") + comment) ^ 1 -local nl = P("\n") -local blank_line = whitespace ^ -1 * nl local get_line_indentation get_line_indentation = function(line) local indent_amounts = { @@ -18,7 +13,7 @@ get_line_indentation = function(line) } do local sum = 0 - local leading_space = line:gsub("([\t ]*).*", "%1") + local leading_space = line:match("[\t ]*") for c in leading_space:gmatch("[\t ]") do sum = sum + indent_amounts[c] end @@ -64,13 +59,31 @@ make_parser = function(lingo, extra_definitions) end return end_pos end + local wordchar = P(1) - S(' \t\n\r%#:;,.{}[]()"') + local nl = P("\n") + local whitespace = S(" \t") ^ 1 + local blank_line = whitespace ^ -1 * nl + local line_comment = re.compile([=[ "#" [^%nl]* ]=], { + nl = nl + }) + local block_comment = re.compile([=[ "#.." (!%nl .)* (%indent (!%dedent %nl [^%nl]*)*) + ]=], { + nl = nl, + whitespace = whitespace, + indent = #(nl * blank_line ^ 0 * Cmt(S(" \t") ^ 0, check_indent)), + dedent = #(nl * blank_line ^ 0 * Cmt(S(" \t") ^ 0, check_dedent)), + new_line = nl * blank_line ^ 0 * Cmt(S(" \t") ^ 0, check_nodent) + }) + blank_line = ((Cmt(whitespace ^ -1, check_nodent) * (block_comment + line_comment)) ^ -1 + whitespace ^ -1) * nl local defs = { wordchar = wordchar, nl = nl, ws = whitespace, - comment = comment, + blank_line = blank_line, + block_comment = block_comment, + line_comment = line_comment, eol = #nl + (P("") - P(1)), - word_boundary = whitespace + B(P("..")) + B(S("\";)]")) + #S("\":([") + #((whitespace + nl) ^ 0 * P("..")), + word_boundary = whitespace ^ -1 + B(P("..")) + B(S("\";)]")) + #S("\":([") + #((whitespace + nl) ^ 0 * P("..")), indent = #(nl * blank_line ^ 0 * Cmt(whitespace ^ -1, check_indent)), dedent = #(nl * blank_line ^ 0 * Cmt(whitespace ^ -1, check_dedent)), new_line = nl * blank_line ^ 0 * Cmt(whitespace ^ -1, check_nodent), @@ -244,7 +257,7 @@ do if self.debug then print("PARSING:\n" .. tostring(str)) end - local lingo = [=[ file <- ({ {| %ws? %new_line? {:body: block :} %new_line? %ws? (errors)? |} }) -> File + local lingo = [=[ file <- ({ {| %blank_line* {:body: block :} %blank_line* (errors)? |} }) -> File errors <- (({.+}) => error_handler) block <- ({ {| statement (%new_line statement)* |} }) -> Block statement <- ({ (functioncall / expression) }) -> Statement @@ -260,37 +273,37 @@ do functioncall <- ({ {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} }) -> FunctionCall fn_bit <- (expression / word) fn_bits <- - ((".." %ws? (%indent %new_line indented_fn_bits %dedent) (%new_line ".." %ws? fn_bits)?) + ((".." %ws? %line_comment? (%indent %new_line indented_fn_bits %dedent) (%new_line ".." %ws? fn_bits)?) / (%new_line ".." fn_bit (%word_boundary fn_bits)?) / (fn_bit (%word_boundary fn_bits)?)) indented_fn_bits <- fn_bit ((%new_line / %word_boundary) indented_fn_bits)? thunk <- - ({ ":" %ws? + ({ ":" %ws? %line_comment? ((%indent %new_line block ((%dedent (%new_line "..")?) / errors)) / (one_liner (%ws? (%new_line? ".."))?)) }) -> Thunk word <- ({ !number {%wordchar+} }) -> Word expression <- ({ (longstring / string / number / variable / list / thunk / subexpression) }) -> Expression - string <- ({ (!longstring) '"' {(("\" .) / [^"])*} '"' }) -> String - longstring <- ({ '".."' %ws? %indent {(%new_line "|" [^%nl]*)+} ((%dedent (%new_line '..')?) / errors) }) -> Longstring + string <- ({ (!longstring) '"' {(("\" [^%nl]) / [^"%nl])*} '"' }) -> String + longstring <- ({ '".."' %ws? %line_comment? %indent {(%new_line "|" [^%nl]*)+} ((%dedent (%new_line '..')?) / errors) }) -> Longstring number <- ({ {'-'? [0-9]+ ("." [0-9]+)?} }) -> Number variable <- ({ ("%" {%wordchar+}) }) -> Var subexpression <- - (!%comment "(" %ws? (functioncall / expression) %ws? ")") - / ("(..)" %ws? %indent %new_line ((({ {| indented_fn_bits |} }) -> FunctionCall) / expression) %dedent (%new_line "..")?) + ("(" %ws? (functioncall / expression) %ws? ")") + / ("(..)" %ws? %line_comment? %indent %new_line ((({ {| indented_fn_bits |} }) -> FunctionCall) / expression) %dedent (%new_line "..")?) list <- ({ {| - ("[..]" %ws? %indent %new_line indented_list ","? ((%dedent (%new_line "..")?) / errors)) + ("[..]" %ws? %line_comment? %indent %new_line indented_list ","? ((%dedent (%new_line "..")?) / errors)) / ("[" %ws? (list_items ","?)? %ws?"]") |} }) -> List list_items <- ((functioncall / expression) (list_sep list_items)?) list_sep <- %ws? "," %ws? indented_list <- - (functioncall / expression) (((list_sep %new_line?) / %new_line) indented_list)? + (functioncall / expression) (((list_sep (%line_comment? %new_line)?) / (%line_comment? %new_line)) indented_list)? ]=] lingo = make_parser(lingo) local tree = lingo:match(str:gsub("\r", "") .. "\n") diff --git a/nomsu.moon b/nomsu.moon index bb98f1e..d0b9723 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -18,16 +18,10 @@ INDENT = " " lpeg.setmaxstack 10000 -- whoa {:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg -wordchar = P(1)-S(' \t\n\r%:;,.{}[]()"') -comment = re.compile [[comment <- "(#" (comment / ((! "#)") .))* "#)"]] -whitespace = (S(" \t") + comment)^1 -nl = P("\n") -blank_line = whitespace^-1 * nl - get_line_indentation = (line)-> indent_amounts = {[" "]:1, ["\t"]:4} with sum = 0 - leading_space = line\gsub("([\t ]*).*", "%1") + leading_space = line\match("[\t ]*") for c in leading_space\gmatch "[\t ]" sum += indent_amounts[c] @@ -50,10 +44,22 @@ 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%#:;,.{}[]()"') + nl = P("\n") + whitespace = S(" \t")^1 + blank_line = whitespace^-1 * nl + line_comment = re.compile([=[ "#" [^%nl]* ]=], {:nl}) + block_comment = re.compile([=[ + "#.." (!%nl .)* (%indent (!%dedent %nl [^%nl]*)*) + ]=], {:nl, :whitespace, + indent:#(nl * blank_line^0 * Cmt(S(" \t")^0, check_indent)), + dedent:#(nl * blank_line^0 * Cmt(S(" \t")^0, check_dedent)), + new_line:nl * blank_line^0 * Cmt(S(" \t")^0, check_nodent)}) + blank_line = ((Cmt(whitespace^-1, check_nodent) * (block_comment + line_comment))^-1 + whitespace^-1) * nl defs = - :wordchar, :nl, ws:whitespace, :comment + :wordchar, :nl, ws:whitespace, :blank_line, :block_comment, :line_comment eol: #nl + (P("")-P(1)) - word_boundary: whitespace + B(P("..")) + B(S("\";)]")) + #S("\":([") + #((whitespace + nl)^0 * P("..")) + word_boundary: whitespace^-1 + B(P("..")) + B(S("\";)]")) + #S("\":([") + #((whitespace + nl)^0 * P("..")) indent: #(nl * blank_line^0 * Cmt(whitespace^-1, check_indent)) dedent: #(nl * blank_line^0 * Cmt(whitespace^-1, check_dedent)) new_line: nl * blank_line^0 * Cmt(whitespace^-1, check_nodent) @@ -162,7 +168,7 @@ class NomsuCompiler if @debug print("PARSING:\n#{str}") lingo = [=[ - file <- ({ {| %ws? %new_line? {:body: block :} %new_line? %ws? (errors)? |} }) -> File + file <- ({ {| %blank_line* {:body: block :} %blank_line* (errors)? |} }) -> File errors <- (({.+}) => error_handler) block <- ({ {| statement (%new_line statement)* |} }) -> Block statement <- ({ (functioncall / expression) }) -> Statement @@ -178,37 +184,37 @@ class NomsuCompiler functioncall <- ({ {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} }) -> FunctionCall fn_bit <- (expression / word) fn_bits <- - ((".." %ws? (%indent %new_line indented_fn_bits %dedent) (%new_line ".." %ws? fn_bits)?) + ((".." %ws? %line_comment? (%indent %new_line indented_fn_bits %dedent) (%new_line ".." %ws? fn_bits)?) / (%new_line ".." fn_bit (%word_boundary fn_bits)?) / (fn_bit (%word_boundary fn_bits)?)) indented_fn_bits <- fn_bit ((%new_line / %word_boundary) indented_fn_bits)? thunk <- - ({ ":" %ws? + ({ ":" %ws? %line_comment? ((%indent %new_line block ((%dedent (%new_line "..")?) / errors)) / (one_liner (%ws? (%new_line? ".."))?)) }) -> Thunk word <- ({ !number {%wordchar+} }) -> Word expression <- ({ (longstring / string / number / variable / list / thunk / subexpression) }) -> Expression - string <- ({ (!longstring) '"' {(("\" .) / [^"])*} '"' }) -> String - longstring <- ({ '".."' %ws? %indent {(%new_line "|" [^%nl]*)+} ((%dedent (%new_line '..')?) / errors) }) -> Longstring + string <- ({ (!longstring) '"' {(("\" [^%nl]) / [^"%nl])*} '"' }) -> String + longstring <- ({ '".."' %ws? %line_comment? %indent {(%new_line "|" [^%nl]*)+} ((%dedent (%new_line '..')?) / errors) }) -> Longstring number <- ({ {'-'? [0-9]+ ("." [0-9]+)?} }) -> Number variable <- ({ ("%" {%wordchar+}) }) -> Var subexpression <- - (!%comment "(" %ws? (functioncall / expression) %ws? ")") - / ("(..)" %ws? %indent %new_line ((({ {| indented_fn_bits |} }) -> FunctionCall) / expression) %dedent (%new_line "..")?) + ("(" %ws? (functioncall / expression) %ws? ")") + / ("(..)" %ws? %line_comment? %indent %new_line ((({ {| indented_fn_bits |} }) -> FunctionCall) / expression) %dedent (%new_line "..")?) list <- ({ {| - ("[..]" %ws? %indent %new_line indented_list ","? ((%dedent (%new_line "..")?) / errors)) + ("[..]" %ws? %line_comment? %indent %new_line indented_list ","? ((%dedent (%new_line "..")?) / errors)) / ("[" %ws? (list_items ","?)? %ws?"]") |} }) -> List list_items <- ((functioncall / expression) (list_sep list_items)?) list_sep <- %ws? "," %ws? indented_list <- - (functioncall / expression) (((list_sep %new_line?) / %new_line) indented_list)? + (functioncall / expression) (((list_sep (%line_comment? %new_line)?) / (%line_comment? %new_line)) indented_list)? ]=] lingo = make_parser lingo