---- -*- Mode: rpl; -*-                                                                             
---- vim:syn=rosie
----
---- rpl-1.1.rpl     RPL language version 1.1, written in RPL version 0 (core)
----
---- © Copyright IBM Corporation 2016, 2017.
---- LICENSE: MIT License (https://opensource.org/licenses/mit-license.html)
---- AUTHOR: Jamie A. Jennings

-- Notes regarding when whitespace is needed:
-- (1) x.y.z is a parse error. Else the second "." would parse as dot (confusing).
-- (2) .y.z is a parse error.  Else the first "." would parse as dot (confusing).
-- (3) Sequences of the one-char identifiers need no whitespace separating them:
--     ... is NOT a parse error.  It is a sequence of 3 dots.
--     .~ is NOT a parse error.  It is a dot followed by a tilde (boundary).
--     .$ is NOT a parse error.  It is a dot followed by a dollar sign (end of input).

package rpl_1_1

alias ws = [[ \t\r]]+
newline = "\n"
comment = { "--" {!newline .}* newline / $ }
alias atmos = { ws? (comment / newline) }* ws?		    -- "atmosphere"

alias id_char = [[:alnum:]] / [[_]]
alias id = { [[:alpha:]] id_char* }

dot = "."
alias unoverloaded_id_symbol = [[$~\^]]
alias id_symbol = dot / unoverloaded_id_symbol

-- It is important that an error consume at least one character here.  As a
-- tactic for continuing to parse after an error , we will skip to eol and try
-- to continue.
rest_of_line = { {!"\n" .}+ "\n" / $}
syntax_error = rest_of_line

localname = unoverloaded_id_symbol / {dot !id} / {id !dot}
packagename = id
identifier = { {packagename [[.]]}? localname }

-- During parsing, we enforce two rules about string literals: ESC must be
-- followed by another character, and a double quote (inside the literal) must
-- be escaped.  Further interpretation of the literal occurs during
-- compilation. 
alias dquote = [["]]					    -- "
alias esc =    [[\\]]
literal = { {!{esc/dquote} .} / {esc .} }* 
alias quoted_string = { dquote literal dquote }

alias hash_mark = [[#]]
tag = localname
hash_exp = {hash_mark {tag / quoted_string}}

star =     [[*]]
question = [[?]]
plus =     [[+]]
alias end_token = { "end" !id_char }
alias keyword_list = "local" / "alias" / "grammar" / "end"
keyword = { keyword_list !id_char }

-- Two rules are enforced for all character sets during parsing: ESC must be
-- followed by another character; an open or close bracket (inside the character
-- set) must be escaped.
-- At parse time, the only difference between a range and a list is that a range
-- will have an unescaped dash in it.
-- Further interpretation occurs at compile time for the contents of a character
-- list, the ends of a character range, and the name of a named set.
complement = "^"
character = { {!esc !"[" !"]" .} / {esc .} }
range_first = {!"-" character}+
range_last = {!"]" character}+
range = { complement? range_first "-" range_last } 
charlist = { complement? {!"]" character}* }
name = {!":" .}+
named_charset = { complement? name } 
alias simple_charset = { "[" { ":" named_charset ":]" } /
	                  { range "]" } /
		          { charlist "]" } }

low =  [[0-9]]*				    -- an int or nothing
high = [[0-9]]* 
repetition = { "{" atmos low atmos {"," atmos high atmos}? "}" }
	          
int = atmos [[0-9]]+

alias quantifier = star / question / plus / repetition

assignment_prefix = identifier atmos "="
stmnt_prefix = {keyword / assignment_prefix}

alias slash =         [[/]]
alias and =           [[&]]
alias open =          [[(]]
alias openraw =       [[{]]
alias openbracket =   [[\[]]
alias close =         [[)]]
alias closeraw =      [[}]]
alias closebracket =  [[\]]]

negation = [[!]]
lookahead = [[>]]
lookbehind = [[<]]
alias predicate_symbol = negation / lookahead / lookbehind
	    
grammar
   syntax_error = { stmnt_prefix rest_of_line }
   application = { identifier ":" } atmos (arg / arglist / rawarglist )
   arglist = { atmos open int_or_exp { atmos "," int_or_exp }* close }
   rawarglist = { atmos openraw int_or_exp { atmos "," int_or_exp }* closeraw }
   arg = term / int
   alias int_or_exp = atmos int / exp
   choice = { term atmos slash exp }
   and_exp = { term atmos and exp }
   sequence = { term {atmos !stmnt_prefix exp}+ }
   term = { atmos {application /
	           identifier /
	           quoted_string /
	           raw /
	           cooked /
	           simple_charset /
	           bracket /
	           predicate /
   	           hash_exp} quantifier? }
   bracket = { openbracket complement? exp atmos closebracket }
   cooked = { open exp atmos close }
   raw = { openraw exp atmos closeraw }
   predicate = { predicate_symbol ws? term }
;in
   alias exp = {atmos { syntax_error /
	                 choice /
	                 sequence /
	                 and_exp /
	                 term
	              }
	       }
end

grammar
   alias exported = alias_ / grammar_ / assignment_
   local_ = "local" atmos alias_ / grammar_ / assignment_ 
   alias_ = "alias" atmos identifier ws? "=" exp 
   grammar_ = "grammar" { atmos { alias_ / assignment_ } }+ atmos end_token
   assignment_ = identifier ws? "=" exp 
;in
   alias stmnt = local_ / exported
end

major = [[0-9]]{1,2}
minor = major
version_spec = { major "." minor }

importpath = quoted_string /		  -- double quoted, interpreted, pattern name is "literal"
             { id {"/" id}* }		  -- quotes not needed when each path segment is an id
import_spec = atmos importpath ("as" (packagename / dot))?

----------------------------------------------------------------------------------------
-- Top level patterns
----------------------------------------------------------------------------------------

language_decl = { "rpl" ws version_spec {ws? ";"}? }
package_decl = { "package" ws packagename {ws? ";"}? }
import_decl = { "import" import_spec } ("," atmos import_spec)* ws? ";"?

rpl_statements = { {atmos ";" / package_decl / import_decl / language_decl / stmnt / syntax_error}* atmos $}
rpl_expression = {exp atmos} / syntax_error

preparse = atmos language_decl

-- RPL module structure:
-- rpl_module = language_decl? package_decl import_decl* statement* atmos

---------------------------------------------------------------------------------------------------
-- Tests
---------------------------------------------------------------------------------------------------

-- test preparse rejects "rpl", "rpl ", "rpl a", "rpl 1.a", "rpl 1.", "rpl 1.123", "rpl 1.\n1"
-- test preparse accepts "rpl 0.0", "  rpl 1.0\n", "rpl 52.99", "rpl 99.99", "rpl 1.1 -- Comment\n"

-- test package_decl accepts "package foo", "package fooBar", "package f_oo"
-- test package_decl rejects "package", "package _foo", "package 6foo", "package .foo", "package foo.bar"
-- test package_decl rejects "package \n foo"

-- test import_decl rejects "import"

-- importpath can be QUOTED STRING:
-- test import_decl rejects "import \"foo/bar\","
-- test import_decl accepts "import \"foo/bar\""
-- test import_decl accepts "import \"foo/bar\", \"/usr/local/baz\", \"/usr/bin/time\""
-- test import_decl accepts "import \"foo/bar\" as foo"
-- test import_decl accepts "import \"foo/bar\" as foo, \"/usr/local/baz\", \"/usr/bin/time\""
-- test import_decl accepts "import \"foo/bar\" as foo, \"/usr/local/baz\" as ., \"/usr/bin/time\""

-- importpath can be PLAIN SYMBOL, uninterpreted:
--    (But a path must be relative, and a symbol starts with [:alpha:])
-- test import_decl rejects "import foo,"
-- test import_decl rejects "import foo["
-- test import_decl accepts "import foo/bar/baz"
-- test import_decl accepts "import foo/bar", usr/lo-cal/b_az, \"/usr/bin/time\""
-- test import_decl accepts "import foo/bar as foo"
-- test import_decl accepts "import foo/bar as foo, \"/usr/local/baz\", bin/time"
-- test import_decl accepts "import f_o_o as foo, local/baz as ., bin/time"
-- test import_decl accepts "import f_o_o/bar as foo, \"/usr/local/baz\" as ., \"/usr/bin/time\""

-- test rpl_expression accepts "foo_1:x", "pkg.f:x", "f:\"hello\"", "f:!x", "f:x+", "f:(x y z)", "f:{x y z}"
-- test rpl_expression accepts "f:(x, y)", "foo:{a, b}"
-- test rpl_expression accepts "f:(a, 55)", "foo.bar:(1, 2, 3, a b c, d / e, {f}*)", "f:{a / b, 3}+"
-- test rpl_expression rejects "f:", "pkg.f:"
-- test rpl_expression includes arglist "f:(x, y)", "f:(3)"
-- test rpl_expression excludes arglist "f:(x y)"
-- test rpl_expression includes arg "f:(x)", f:(x y)", "f:x", "f:5", "f:{x y}", "f:{x}",
-- test rpl_expression includes arg "f:{a / b}+", "f:{(a)}", "f:{[:set:]}"
-- test rpl_expression includes rawarglist "f:{5}", "f:{7, x, a / b / c+}", "f:{(a),b}", "f:{[:set:]{1,2},\"hi\"}"



