
//*********************************************
//*** readable form with left recursion *******
//*********************************************

//top-level template package, only one per .tpl file for now
template-package:
	'package' path-ident (string-comment)? ';'
	 (
	  'import' 'interface' path-ident (string-comment)? ';'
	  |
	  'import' path-ident ('.' '*')? (string-comment)? ';'
	  |
	  template-def
	 )*
	'end' path-ident ';'

template-def:
	'template' template-signature (string-comment)? '::='
	   expression
	'end' IDENT ';'
	|
	'constant' const-type CONST '='  constant (string-comment)? ';' //an expression with compile-time evaluation ?

template-signature:
	IDENT  '(' (targ (',' targ)*)?  ')'

targ:
	type-signature  IDENT
	|
	'Text' '&' IDENT

type-signature:
	'list' '<' type-signature '>
	|
	'Option' '<' type-signature '>'
	|
	'tuple' '<' type-signature (',' type-signature )*  '>'
	|
	path-ident
	|
	type-signature '[' ':' ']'  // array

const-type:
	'String' | 'Integer' | 'Real' | 'Boolean' //maybe also 'Text' ?? for constant << ... >> and ' ... ' ??

constant :
	string-const
	|
	( esc-char )+    // unquoted  esc-chars e.g. \n
	|
	('-')? ( [0-9] )*
	//  integer ... useful  for option values or intrinsic functions' arguments
	// or in matching expressions
	// otherwise converted to string representation
	|
	(+|-)?d*(.d+)?(('e'|'E')(+|-)?d+)? // real-number - validate the number - must have integer part or dotpart
	|
	'true' | 'false'   //boolean ... the same  usage as integers

esc-char:
	'\\'   ( '\'' | '"' | '?' |  '\\' | 'a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' | ' ' )

string-const:
    '"'   ( esc-char | [^"] )*  '"'     //  " constant expression with escapes "

template:
	'\''  multi-line-template( EndEsc = '\'' )  '\''
	|
	'<<' multi-line-template( EndEsc = none )  '>>'

multi-line-template( EndEsc  ):
	one-line-templ( EndEsc )
	NEW-LINE  ( multi-line-template( EndEsc  )  )?

one-line-templ( EndEsc):
	STRING(EndEsc)
	'<%' expression ?  '%>'  ( one-line-templ( EndEsc) )?  // nothing  is useful for </*  */> comment

STRING(EndEsc ): //always is processed on one line //no escaping
	( ^'<%' | ^EndEsc )+  //take whatever except '<%' and  EndEsc if SOME, or '>>' if NONE


/* expression operators precedence and associativity (from lowest to highest)
 *   ; option  |  no assoc. -  n-ary
 *   |> =>     |  no assoc. - ternary (binary de-facto) //will be left assoc. in the future
 *   then,else |  no assoc. //to allow nesting of if and/or match ... else always belongs to closest form (if or match)
 *   let (in)  |  no assoc. - binary // the precedence for the invisible 'in' binary operator here ... the bounded expression is full expression - ... let b = bounded-expr in result-expr ... is similar to ... let b = (bounded-expr) in result-expr
 *   match     |  no assoc. //precedence for argument
 *   if        |  no assoc. //precedence for argument
 *   ,         |  no assoc. - n-ary //preserve this precedence for future usage
 *   not       |  unary
 *   +         |  left assoc. - binary
 *   f()       |  unary
 *   . //dot   |  left assoc. - binary
 *   &         |  unary
 *   (), {}    |  no assoc.
 */

expression:
	template
	|
	constant
	|
	bound-value
	|
	fn-call   //template  or  intrinsic function/template
	|
	condition
	|
	match
	|
	map-expr
	|
	'{' expression (',' expression )* '}'  // list construction with possible mixed scalars and lists
	                                       // useful in map/concatenation context
	                                       //TODO: decide if mix of scalars / list values are allowed
	|
	expression '+' expression   //  concatenation ... same as '<expression><expression>'
	|
	'(' expression ')'
	|
	'&' IDENT
	|
	'let' let-expr  expression
	|
	expression ( ';' OPTION  ( '=' expression) ?  )+

let-expr:
	'&' TEXT '=' 'buffer' expression
	|
	'&' TEXT '+=' expression
	|
	'()' '=' fn-call  //no return call
	|
	let-binding '=' expression

let-binding:
	'_'
	|
	IDENT
	|
	'(' let-binding (',' let-binding)+ ')'

bound-value:
	path-ident

path-ident:
	IDENT  ( '.' IDENT  )*

fn-call:
	path-ident  '('  (expression (',' expression )* )?  ')'
	// path-ident to be able to call an imported function from a package

map-expr:
	expression '|>'  binding ('hasindex' IDENT ('fromindex' IDENT)? )? '=>' expression   // only 1 for the first impl. left assoc
	// expression (',' expression)* '|>'  binding  (','  binding)* ('hasindex' IDENT)? '=>'  expression

condition:  // we will stick to simple inline only if expression for now ... not <%if cond%> <%endif%> form ... that is ugly looking
	// Boolean, list, Option, Integer, Real, String, Text
	'if'  ('not')?  expression  'then'  expression
	('else'  expression)?

//cond-expr:
	//('not')?  expression   // Boolean, list, Option, Integer, Real, String
	//|
	//expression 'is' ('not')? binding

match:
	'match'  expression
	( ('case'  binding)+  'then'  expression )+
	( 'else' expression )?
	( 'end' 'match' )?

binding:
    BINDVAL ('as' binding) ?
    |
    path-ident '(' '__' ')'
    |
    path-ident '(' ( FIELD '=' binding  ( ','  FIELD '=' binding )*  )?  ')'
	|
	'SOME' '(' '__' ')'
	|
	'SOME' ( '(' binding  ')' )?
	|
	'NONE' ( '(' ')' )?
	|
	'(' binding ')'
	|
	'(' binding ( ',' binding )+  ')'  //maybe without ( ) ? - no, it would not work when we will want multi map-expr
	|
	'{' ( binding  (',' binding )* )? '}'  // list of individual list values
	|
	binding '::' binding    // left assoc
	|
	constant
	|
	'_'

	//|
	 //CONST  // maybe this should be an (compile time constant) expression like "<costantName>"
	               // because of possible (visual) conflicting with a BINDVAL ??
	               //and when the name of the CONST is changed it can generate a nasty bug
	               //
	// |
	// expression  // we can provide "expression" matching, when it is a constant during compilation
	                           // it would give better readability, while supporting the single-point-of-change principle
	                          // for named constant references  (like '<%costantName%>')

TYPE, ITEM, BINDVAL, FIELD, TEXT , CONST:
	IDENT

IDENT:
	[A-Za-z] ([_0-9A-Za-z] )*
	|
	'_' [0-9A-Za-z] ([_0-9A-Za-z] )*
	// identificator cannot be '_' or start with "__" ... to be simple to generate compiled helper names
	// or, it can be solved with a package / namespace ?
	|
	//TODO: to be implemented ... maybe only for keywords (Susan exclusive: 'let', 'template', ...)
	"$'" ( esc-char | [^'] )*  "'"  // ??is it correct with Modelica '' identifiers ??

string-comment:
	string-const

union-type:
	'uniontype' TYPE (string-comment)?
	    ( record-type ';' )+
	'end' TYPE ';'

record-type:
	'record' TYPE (string-comment)?
	  (type-signature  IDENT (string-comment)?  ';'
	'end' TYPE

function-type:
	'function' IDENT (string-comment)?
	  ('replaceable' 'type' IDENT 'subtypeof' 'Any' ';')*
	  (  ('input' |  'output')   (type-signature  IDENT (string-comment)?  ';')*
	  ('replaceable' 'type' IDENT 'subtypeof' 'Any' ';')*
	'end' IDENT

constant-type:
	'constant'  type-signature  IDENT (string-comment)?  ';'

type-alias:
	'type' TYPE '=' type-signature (string-comment)? ';'

//top-level inteface package, only one per .mo file for now
interface-package:
	'inteface' 'package'  path-ident  (string-comment)?
	   interface-def *
	'end' path-ident ';'

interface-def:
	('public' | 'protected')?
	'package'  path-ident  (string-comment)?
	   (
		union-type-decl
		|
		record-type
		|
		function-type
		|
		constant-type
		|
		type-alias
	   )*
	'end' path-ident ';'



//**************************************************
//*** gramar spec suitable for MM implementation ***
//*** no left recursion and LL(k) ******************
//*** usage of => productions is inspired by MGrammar and OMeta languages
//**************************************************

newLine:
	\r \n  //CR + LF ... Windows
	|
	\n     //CR only ... Linux
	|
	\r     //LF only ... Mac OS up to 9


// interleave will be applied before every token
interleave:  //i.e. space / comment
	[' '\n\r\t] interleave
	|
	'//' toEndOfLine interleave
	|
	'/*' comment interleave
	|
	_ //just nothing


toEndOfLine:
    newLine
    |
    eof  //end of stream ~ {}
    |
    any  toEndOfLine //any is any character

comment:
	'*/'
	|
	'/*' comment comment  //nesting is possible
	|
	any  comment
	|
	EOF => error

//afterKeyword must not fail after every keyword to be considered as keyword
afterKeyword:
    [_0-9A-Za-z]  =>  fail  // if it can be an identifier/other keyword
    |
    _  => ()

//TODO: check against Susan + MetaModelica keywords(maybe only for template names)
identifier:
	[_A-Za-z]:c  identifier_rest:rest     =>  string_char_list_string(c::rest)

identifier_rest:
    [_0-9A-Za-z]:c  identifier_rest:rest  =>  c::rest
    |
	_  =>  {}

pathIdent:
	identifier:head  pathIdentPath(head):pid => pid

pathIdentPath(head):
	'.' pathIdent:path  =>  PATH_IDENT(head, path)
	|
	'.' error "expecting identifier after dot."
	  => PATH_IDENT(head, TplAbsyn.IDENT("#error#"))
	|
	_ =>  IDENT(head)


templPackage:
	'package'  pathIdent:pid  stringComment
		definitions(pid,{},{}):(astDefs,templDefs)
	endDefPathIdent(pid)
	=> 	TEMPL_PACKAGE(pid, listReverse(astDefs),listReverse(templDefs))

definitions(astDefs,templDefs):
	'import' 'interface' pathIdent:pid stringComment ';'
	  { ads = typeviewDefsFromInterfaceFile(pid, astDefs) }
	  definitions(ads, templDefs):(ads,tds)
	  => (ads,tds)
	|
	'import' pathIdent:pid unqualImportPostfix:unq stringComment ';'
	  { ads = typeviewDefsFromTemplateFile(pid, unq, astDefs) }
	  definitions(ads, templDefs):(ads,tds)
	  => (ads,tds)
//	|
//	absynDef:ad  definitions(ad::astDefs,templDefs):(ads,tds) => (ads,tds)
	|
	templDef:(name, td)  definitions(astDefs,(name,td)::templDefs):(ads,tds) => (ads,tds)
//	|
//	error "Expecting 'end' | ['public' | 'protected' ] 'package' definition | template definition starting with an identifier."

unqualImportPostfix:
	'.' '*' => true
	|
	_ => false

typeSig:
    typeSig_base:base  typeSig_array(base):ts  => ts

typeSig_base:
	'list' '<' typeSig:tof '>'  =>  LIST_TYPE(tof)
	|
	'Option' '<' typeSig '>'   =>  OPTION_TYPE(tof)
	|
	'tuple' '<' typeSig:ts  typeSig_restList:restLst  '>'  => TUPLE_TYPE(ts::restLst)
	|
	pathIdent:pid  =>  NAMED_TYPE(pid)  // +specializations for String, Integer, .... => STRING_TYPE(), ...

typeSig_array(base):
	'[' ':' ']'  typeSig_array(ARRAY_TYPE(base)):ts   =>  ts
	|
	_  =>  base

typeSig_restList:
    ',' typeSig:ts  typeSig_restList:restLst  =>  ts::restLst
    |
    _  => {}

publicProtected:
	'public' => true
	|
	'protected' => false
	|
	_ => true

stringComment:
	'"' stringCommentRest
	|
	_

stringCommentRest:
	'"'
	|
	'\\"' stringCommentRest
	|
	'\\' stringCommentRest
	|
	~'"' stringCommentRest
	|
//TODO:	EOF => error "End of file reached inside string comment"

interfacePackage(astDefs):
	'interface' 'package'  pathIdent:pid  stringComment
		typeviewDefs(astDefs):ads
	endDefPathIdent(pid)
	=> 	(pid, ads)

typeviewDefs(astDefs):
	absynDef:ad  typeviewDefs(ad::astDefs):ads => ads
	|
	_ => astDefs

//TODO: is optional rule
absynDef:
	publicProtected:isD  'package' pathIdent:pid  stringComment
	  absynTypes:types
	endDefPathIdent(pid)
	=>  AST_DEF(pid, isD, types)

//not optional, must not fail
endDefPathIdent(pid):
	'end' pathIdent:pidEnd ';' // pid == pidEnd | warning

//not optional ... must not fail
endDefIdent(id):
	'end' identifier:idEnd ';' // id == idEnd | warning


absynTypes:
	'end' => {}
	|
	absynType:(id,ti)  absynTypes:types  => (id,ti) :: types

//TODO: this is an optional rule -> how to error report this ?
//-> from above context, perhaps, or return expected token when unsatisfied ?
//-> optional rules whould be then imlemented through other path thean failing ?
absynType:
	'uniontype' identifier:id  stringComment
	    recordTags(id):rtags
	=> (id, TI_UNION_TYPE(rtags))
	|
	recordType:(id,fields)
	=> (id, TI_RECORD_TYPE(fields))
	|
	'function' identifier:id  stringComment
		typeVars({}):tyVars
		inputFunArgs:inArgs
		outputFunArgs:outArgs
		typeVars(tyVars):tyVars
	endDefIdent(id)
	=> (id, TI_FUN_TYPE(inArgs,outArgs,tyVars))
	|
	'constant'  typeSig:ts  identifier:id  stringComment  ';'
	=> (id, TI_CONST_TYPE(ts))
	|
	'type' identifier:id '=' typeSig:ts stringComment  ';'
	=> (id, TI_ALIAS_TYPE(ts))
	|
	error "Expecting 'uniontype' | 'function' | 'constant' | 'type'"

//TODO: is optional; can fail
recordType:
	'record' identifier:id  stringComment
	    typeDecls(id):fields
	=> (id,fields)

typeDecls(rid):
	'end' identifier:idEnd ';' // rid == idEnd
	=> {}
	|
	typeSig:ts  identifier:id  stringComment ';'
	typeDecls(rid):tids
	=> (id,ts) :: tids


recordTags(uid):
	'end' identifier:idEnd ';' // uid == idEnd
	=> {}
	|
	recordType:(id,tids)  recordTags:rtags  => (id,tids) :: rtags

//TODO: is optional
inputFunArgs:
	'input' typeSig:ts  identifier:id  stringComment ';'
	inputFunArgs:iargs
	=> (id,ts) :: iargs
	|
	_ => {}

//TODO: is optional
outputFunArgs:
	'output' typeSig:ts  identifier:id  stringComment ';'
	outputFunArgs:oargs
	=> (id,ts) :: oargs
	|
	_ => {}

typeVars(tyvars):
	'replaceable' 'type'  identifier:id  'subtypeof' 'Any' ';'
	typeVars(id :: tyvars):tyvars
	=> tyvars
	|
	_ => tyvars

//TODO: is optional	... it is not when templDef_ConstOrTempl:td
templDef:
    'template' identifier:name
      '(' templArgs:args ')' stringComment
	    templDef_Templ:(exp,lesc,resc)
    endDefIdent(name)
      =>  (name, TEMPLATE_DEF(args,lesc,resc,exp))
    |
    'constant' constantType:ctype  identifier:name templDef_Const:td //check ctype
      stringComment ';'
      => (name, td)

templDef_Templ:
	'::='  expression(LEsc = '<',REsc = '>'):exp   => (exp,'<','>')
	///|
	//'$$='  expression(LEsc = '$',REsc = '$'):exp   => (exp,'$','$')

constantType:
	'String'  => STRING_TYPE()
	'Integer' => INTEGER_TYPE()
	'Real'    => REAL_TYPE()
	'Boolean' => BOOLEAN_TYPE()

templDef_Const:
	'=' stringConstant:strRevList
	  =>  STR_TOKEN_DEF(makeStrTokFromRevStrList(strRevList))
	|
	'=' literalConstant:(str,litType)
	  =>  LITERAL_DEF(str, litType)


templArgs:
	//TODO: to be TEXT_REF ... for now only syntax
    'Text' '&' identifier:name  templArgs_rest:args  =>  (name,TEXT_TYPE())::args
    |
    typeSig:ts  identifier:name  templArgs_rest:args  =>  (name,ts)::args
    |
    _  => {}

//is optional
//templArg:
//    typeSig:ts  identifier:name  =>  (name,ts)

templArgs_rest
	',' typeSig:ts  identifier:name  templArgs_rest:rest  =>  (name,ts)::rest
	|
	_  => {}

expression(lesc,resc):
	expressionNoOptions(lesc,resc):exp  escapedOptions:opts
	  => makeEscapedExp(exp, opts)

escapedOptions(lesc,resc):
	';' identifier:id  escOptionExp(lesc,resc):expOpt  escapedOptions(lesc,resc):opts
	=> (id, expOpt) :: opts
	|
	_ => {}

escOptionExp(lesc,resc):
	'=' expressionLet(lesc,resc):exp
	  => SOME(exp)
	|
	_ => NONE

expressionNoOptions(lesc,resc):
	expressionLet(lesc,resc):expLet  mapTailOpt(lesc,resc,expLet):exp
	  => exp

mapTailOpt(headExp,lesc,resc):
	'|>' matchBinding:mexp
	indexedByOpt:idxNmOpt //TODO: 'indexedby' in TplAbsyn
	'=>' expressionLet(lesc,resc):exp  =>  MAP(headExp,mexp,exp)
	|
	_ => headExp

indexedByOpt:
	'hasindex' identifier:id fromOpt:indexOffsetOpt
		=> SOME(id), indexOffsetOpt
	|
	_ => NONE, {}

fromOpt:
	'fromindex' expression_base:expFrom
		=> { ("$indexOffset", SOME(expFrom)) }
	|
	_ => {}

expressionLet(lesc,resc):
	'let' letExp(lesc,resc):lexp  concatLetExp_rest(lesc,resc):expLst
	   => TEMPLATE(lexp::expLst}, "let", ""); //TODO: should be a LET_EXPRESSION()
	|
	expressionMatch(lesc,resc):exp

concatLetExp_rest(lesc,resc):
	'let' letExp(lesc,resc):lexp  concatLetExp_rest(lesc,resc):expLst
	  =>  lexp::expLst
	|
	expressionMatch(lesc,resc):exp
	  => {exp}

//not optional, at least one must match
letExp(lesc,resc):
	'&' identifier:id '=' 'buffer' expression(lesc,resc):exp
	     => TEXT_CREATE(id,exp)
	|
	'&' identifier:id '+=' expression(lesc,resc):exp
	     => TEXT_ADD(id,exp)
	|
	'()' '=' pathIdent:name  funCall(name,lesc,resc):FUN_CALL(name, args)
	     =>  NORET_CALL(name, args)
	|
	identifier:id '=' expression(lesc,resc):exp
		=> TEXT_CREATE(id,exp) //TODO: !! a HACK for now

	//TODO:
	|
	letBinding:bd '=' expression(lesc,resc):exp
	  =>  LET_BINDING(bd, exp)

//TODO:
letBinding:
	'(' letBinding:headMExp  ',' letBinding:secMExp  letBindingTupleRest:mrest ')'
	  => TUPLE_MATCH(headMExp :: secMExp :: mrest)
	|
	'_'
	  => REST_MATCH()
	|
	identifier:id
	  => BIND_MATCH(id)

letBindingTupleRest:
	',' letBinding:mexp  letBindingTupleRest:mrest
	  => mexp :: mrest
	|
	_ => {}


expressionMatch(lesc,resc):
	matchExp(lesc,resc):exp
	  => exp
	|
	expressionIf(lesc,resc):exp
	  => exp

expressionIf(lesc,resc):
	conditionExp(lesc,resc):exp
	  => exp
	|
	expressionPlus(lesc,resc):exp
	  => exp

expressionPlus(lesc,resc):
	expression_base(lesc,resc):bexp  plusTailOpt(lesc,resc,bexp):exp
	  => exp

plusTailOpt(lesc,resc,bexp):
	'+' expression_base(lesc,resc):exp  concatExp_rest(lesc,resc):expLst   //  concatenation ... same as '<expression><expression>'
	  => TEMPLATE(bexp::exp::expLst, "+", "");
	|
	_ => bexp

concatPlus_rest(lesc,resc):
	'+' expression_base(lesc,resc):exp  concatExp_rest(lesc,resc):expLst  =>  exp::expLst
	|
	_ => {}


expression_base(lesc,resc):
	stringConstant:strRevList
	  => STR_TOKEN(makeStrTokFromRevStrList(strRevList))
	|
	literalConstant:(str,litType)
	  => LITERAL(str,litType)
	|
	templateExp(lesc,resc)
	|
	'{' '}'  => MAP_ARG_LIST({})
	|
	'{' expressionPlus(lesc,resc):exp  expressionList_rest(lesc,resc):expLst '}'   //  list construction with possible mixed scalars and lists
	                                                           // useful in map/concatenation context
	   => MAP_ARG_LIST(exp::expLst)
	|
	'(' expression(lesc,resc):exp ')'
	   => exp
	|
	'&' identifier:id
	  => BOUND_VALUE(IDENT(name))  //TODO: ref Text buffer
	|// TODO: create an optional/error reporting variant of pathIdent
	pathIdent:name  boundValueOrFunCall(name,lesc,resc):exp  =>  exp





boundValueOrFunCall(name,lesc,resc):
	funCall(name,lesc,resc):exp  => exp
	|
	_ => BOUND_VALUE(name)

//TODO: is optional -> can fail
funCall(name,lesc,resc):
	'(' ')' => FUN_CALL(name,{})
	|
	'(' expressionPlus(lesc,resc):exp  expressionList_rest(lesc,resc):expLst ')'  //template  or  intrinsic function
	  => FUN_CALL(name,exp::expLst)


expressionList_rest(lesc,resc):
	',' expressionPlus(lesc,resc):exp  expressionList_rest(lesc,resc):expLst => exp::expLst
	|
	_ => {}


stringConstant:
	'"' doubleQuoteConst({},{}):stRevLst
	  => stRevLst
	|
	//'%'(lquot) stripFirstNewLine verbatimConst(Rquote(lquot),{},{}):stRevLst
	//  => stRevLst
	//|
	'\\n' escUnquotedChars({}, {"\n"}):stRevLst
	  => stRevLst
	|
	'\\' escChar:c  escUnquotedChars({c}, {}):stRevLst
	  => stRevLst


literalConstant:
	//(+|-)?d*(.d+)?(('e'|'E')(+|-)?d+)?
	plusMinus:pm digits:ds dotNumber:(dn,ts) exponent(ts):(ex,ts)
	=> (pm+& string_char_list_string(ds)+&dn+&ex, ts)  //validate the number - must have integer part or dotpart
	|
	'true' => ("true", BOOLEAN_TYPE())
	|
	'false' => ("false", BOOLEAN_TYPE())

stripFirstNewLine:
	'\n'
	|
	_

doubleQuoteConst(accChars,accStrList):
	'\'' => string_char_list_string(listReverse(accChars)) :: accStrList
	|
	newLine doubleQuoteConst({}, string_char_list_string(listReverse('\n'::accChars))::accStrList):stRevLst
	=> stRevLst
	|
	'\\n' doubleQuoteConst({}, string_char_list_string(listReverse('\n'::accChars))::accStrList):stRevLst
	=> stRevLst
	|
	'\\'escChar:c doubleQuoteConst(c::accChars,accStrList):stRevLst
	=> stRevLst
	|
	c doubleQuoteConst(c::accChars,accStrList):stRevLst
	=> stRevLst
	|
	Error end of file

verbatimConst(rquot, accChars, accStrList):
	//strip a last inline new line
	newLine (rquot)'%' =>  string_char_list_string(listReverse(accChars)) :: accStrList
	|
	(rquot)'%' =>  string_char_list_string(listReverse(accChars)) :: accStrList
	|
	newLine verbatimConst(rquot, {}, string_char_list_string(listReverse('\n'::accChars))::accStrList):stRevLst
	  => stRevLst
	|
	c  verbatimConst(rquot, c::accChars,accStrList):stRevLst
	  => stRevLst
	|
	Error end of file

escUnquotedChars(accChars,accStrList):
	'\\n' escUnquotedChars({}, string_char_list_string(listReverse('\n'::accChars)) :: accStrList):stRevLst
	=> stRevLst
	|
	'\\' escChar:c  escUnquotedChars(c::accChars, accStrList):stRevLst
	=> stRevLst
	|
	_ => string_char_list_string(listReverse(accChars)) :: accStrList


escChar:
	( '\'' | '"' | '?' |  '\\' | 'a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' | ' ' )
	=> the escaped char

plusMinus:
	'+' => "+"
	|
	'-' => "-"
	|
	_ => ""

digits:
	[0-9]:d  digits:ds => d::ds
	|
	_ => {}

dotNumber:
	'.' digits:ds  =>  (string_char_list_string(ds), REAL_TYPE())
	|
	_ => INTEGER_TYPE()

exponent(typ):
	'e' plusMinus:pm  digits:ds => ("e"+&pm+&string_char_list_string(ds), REAL_TYPE())
	|
	'E' plusMinus:pm  digits:ds => ("E"+&pm+&string_char_list_string(ds), REAL_TYPE())
	|
	=> ("",typ)


// & ... no interleave
templateExp(lesc, resc):
	"'" & templateBody(lesc, resc, isSingleQuote = true, {},{},0):exp
	  => exp
	|
	'<<' & templStripFirstNewLine
	   & templateBody(lesc, resc, isSingleQuote = false,{},{},0 ):exp
	  => exp


// & ... no interleave
takeSpaceAndNewLine:
	newLine
    |
    ' ' & takeSpaceAndNewLine
	|
	'\t' & takeSpaceAndNewLine

// & ... no interleave
templateBody(lesc, resc, isSingleQuote, expList, indStack, actInd):
	lineIndent(0):lineInd
	  & restOfTemplLine(lesc, resc, isSingleQuote, expList, indStack, actInd, lineInd, {}):exp
	=> exp

// & ... no interleave
lineIndent(ind):
	' ' & lineIndent(ind+1):n  =>  n
	|
	'\t' & lineIndent(ind+4):n  =>  n
	|
	_  =>  ind

// & ... no interleave
restOfTemplLine(lesc, resc, isSingleQuote, expList, indStack, actInd, lineInd, accStrChars):
	//(lesc)'#' nonTemplateExprWithOpts(lesc,resc):eexp  '#'(resc)
	//   { (expList, indStack, actInd) = onEscapedExp(eexp, expList, indStack, actInd, lineInd, accStrChars) }
	//   & restOfTemplLine(lesc,resc,isSingleQuote, expList, indStack, actInd, actInd, {}):exp
	//   => exp
	//
	//|
	(lesc)  (resc)	// a comment | empty expression ... ignore completely
	   & restOfTemplLineAfterEmptyExp(lesc,resc,isSingleQuote, expList, indStack, actInd, lineInd, accStrChars):exp
	   => exp
	|
	(lesc) '%' expression(lesc,resc):eexp (resc)
	   { (expList, indStack, actInd) = onEscapedExp(eexp, expList, indStack, actInd, lineInd, accStrChars) }
	   & restOfTemplLine(lesc,resc,isSingleQuote, expList, indStack, actInd, actInd, {}):exp
	   => exp

	| // on \n
	newLine
	 { (expList, indStack, actInd) = onNewLine(expList, indStack, actInd, lineInd, accStrChars) }
	 & templateBody(lesc, resc, isSingleQuote, expList, indStack, actInd):exp
	=> exp

	| //end
	(isSingleQuote = true) "'"
	 =>
	  onTemplEnd(expList, indStack, actInd, lineInd, accStrChars)

	| //end
	(isSingleQuote = false) '>>'
	 =>
	 onTemplEnd(expList, indStack, actInd, lineInd, accStrChars)

	|
	'\' & ( '\' | "'" | (lesc) | (resc) ):c
	 & restOfTemplLine(lesc, resc, isSingleQuote, expList, indStack, actInd, lineInd, c :: accStrChars) : exp
	  => exp
	|
	any:c
	  & restOfTemplLine(lesc, resc, isSingleQuote, expList, indStack, actInd, lineInd, c :: accStrChars) : exp
	  => exp


conditionExp(lesc,resc):
	'if' condArgExp(lesc,resc):(isNot, lhsExp, rhsMExpOpt)
	'then' expressionLet(lesc,resc):trueBr
	elseBranch(lesc,resc):elseBrOpt
	 => CONDITION(isNot, lhsExp, rhsMExpOpt, trueBr, elseBrOpt)

elseBranch(lesc,resc):
	'else' expressionLet(lesc,resc):elseBr
	  => SOME(elseBr)
	|
	_ => NONE

condArgExp:
	'not' expressionPlus(lesc,resc):lhsExp
	  => (true, lhsExp, NONE)
	|
	expressionPlus(lesc,resc):lhsExp
	//  condArgRHS:(isNot, rshMExpOpt)
	{ isNot = false }
	 => (isNot,lhsExp, rhsMExpOpt)

//condArgRHS:
//	'is' 'not' matchBinding:rhsMExp  =>  (true, SOME(rhsMexp))
//	|
//	'is' matchBinding:rhsMExp  =>  (false, SOME(rhsMexp))
//	|
//	_ => (false, NONE)


matchExp(lesc,resc):
	'match' expressionIf:exp
	  matchCaseList(lesc,resc):mcaseLst  { (_::_) = mcaseLst }//not optional
	  matchElseCase(lesc,resc):elseLst
	  matchEndMatch
	 => MATCH(exp, listAppend(mcaseLst, elseLst))
	//|
	//matchCaseList(lesc,resc):mcaseLst { (_::_) = mcaseLst }
	//=> MATCH(BOUND_VALUE(IDENT("it")), mcaseLst)

matchCase(lesc,resc):
	'case'  matchBinding:mexp	matchCaseHeads(): mexpHeadLst
	'then'  expressionLet:exp
	   => makeMatchCaseLst(mexp::mexpHeadLst,exp)

matchElseCase(lesc,resc):
    'else' expressionLet:exp
	  => {(REST_MATCH(), exp)}
	|
	_ => {}

matchEndMatch:
	'end' 'match'
	|
	_

matchCaseHeads():
	'case'  matchBinding:mexp	matchCaseHeads(): mexpHeadLst
	   => mexp :: mexpHeadLst
	|
	_ => {}

matchCaseList(lesc,resc):
	matchCase(lesc,resc):mcaseLst  matchCaseList(lesc,resc):mcrest
	  => listAppend(mcaseLst, mcrest)
	|
	_ => {}


matchBinding:
	matchBinding_base:headMExp  matchBinding_tail(headMExp):mexp
	  => mexp


matchBinding_tail(headMExp):
	'::' matchBinding:restMExp
	  => LIST_CONS_MATCH(headMExp, restMExp)
	|
	_ => headMExp


matchBinding_base:
	'SOME' someBinding_rest:mexp
	  => SOME_MATCH(mexp)
	|
	'NONE' takeEmptyBraces
	  => NONE_MATCH()
	|
	'(' matchBinding:headMExp  tupleOrSingleMatch(headMExp):mexp ')'
	  => mexp
	|
	'{' '}'
	  => LIST_MATCH({})
	|
	'{' matchBinding:headMExp  listMatch_rest:mrest '}'
	  => LIST_MATCH(headMExp :: mrest)
	|
	stringConstant:strRevList
	  => STRING_MATCH(stringAppendList(listReverse(strRevList))
	|
	literalConstant:(str,litType)
	  => LITERAL_MATCH(str,litType)
	|
	'_'
	  => REST_MATCH()
	|
	pathIdent:pid  afterIdentBinding(pid):mexp
	  => mexp

someBinding_rest:
	'(' '__' ')'
	  => SOME_MATCH(REST_MATCH())
	|
	'(' matchBinding:mexp ')'
	  => SOME_MATCH(mexp)
	|
	_ => SOME_MATCH(REST_MATCH())

takeEmptyBraces:
	'(' ')'
	|
	_

tupleOrSingleMatch(headMExp):
	',' matchBinding:secMExp  listMatch_rest:mrest
	  => TUPLE_MATCH(headMExp :: secMExp :: mrest)
	|
	_ => headMExp

listMatch_rest:
	',' matchBinding:mexp  listMatch_rest:mrest
	  => mexp :: mrest
	|
	_ => {}

afterIdentBinding(pid):
	'(' ')'
	  => RECORD_MATCH(pid, {})
	|
	'(' '__' ')'
	  => RECORD_MATCH(pid, {}) //TODO: to be RECORD_TYPE_MATCH(pid)
	|
	'(' fieldBinding:fb  fieldBinding_rest:fbs ')'
	  => RECORD_MATCH(pid, fb::fbs)
	|
	{pid is PATH_IDENT}
	=> error "Expected '(' after the dot path."
	//RECORD_MATCH(pid, {})
	|
	{pid is IDENT(id)}
	'as' matchBinding:mexp
	  => BIND_AS_MATCH(id, mexp)
	|
	{pid is IDENT(id)}
	_ => BIND_MATCH(id)

fieldBinding:
	identifier:fldId '=' matchBinding:mexp
	  => (fldId, mexp)

fieldBinding_rest:
	',' fieldBinding:fb  fieldBinding_rest:fbs
	  => fb :: fbs
	|
	_ => {}


// ************************************************
// **** alternative expression - experimental *****
// ************************************************

/* expression operators precedence and associativity (from lowest to highest)
 *   then,else |  no assoc. //to allow nesting of if and/or match ... else always belongs to closest form (if or match)
 *   let (in)  |  no assoc. - binary // the precedence for the invisible 'in' binary operator here ... the bounded expression is full expression - similar to ... let b = (bounded-expr) in result-expr
 *   ; option  |  no assoc. -  n-ary
 *   |> =>     |  no assoc. - ternary (binary de-facto) //will be left assoc. in the future
 *   ,         |  no assoc. - n-ary //preserve this precedence for future usage
 *   not       |  unary (only for if)
 *   +         |  left assoc. - binary
 *   match,if  |  no assoc. //precedence for argument
 *   f()       |  unary
 *   . //dot   |  left assoc. - binary
 *   &         |  unary
 *   (), {}    |  no assoc.
 */


expression(lesc,resc):
	expressionBinary(lesc,resc):exp => exp
	|
	expressionUnary(lesc,resc):exp => exp

expressionUnary(lesc,resc):
	'let' letExp(lesc,resc):lexp  concatLetExp_rest(lesc,resc):expLst
	   => TEMPLATE(lexp::expLst}, "let", ""); //TODO: should be a LET_EXPRESSION()
	|
	matchExp(lesc,resc):exp
	  => exp
	|
	conditionExp(lesc,resc):exp
	  => exp

/*
expressionLet(lesc,resc):
	'let' letExp(lesc,resc):lexp  concatLetExp_rest(lesc,resc):expLst
	   => TEMPLATE(lexp::expLst}, "let", ""); //TODO: should be a LET_EXPRESSION()

expressionMatch(lesc,resc):
	matchExp(lesc,resc):exp
	  => exp
	|
	expressionIf(lesc,resc):exp
	  => exp
*/


expressionBinary(lesc,resc):
	expressionMap(lesc,resc):exp  escapedOptions:opts
	  => makeEscapedExp(exp, opts)

expressionMap(lesc,resc):
	expressionPlus(lesc,resc):headExp  mapTailOpt(lesc,resc,headExp):exp
	  => exp

expressionPlus(lesc,resc):
	expression_base(lesc,resc):bexp  plusTailOpt(lesc,resc,bexp):exp
	  => exp

mapTailOpt(headExp,lesc,resc):
	'|>' matchBinding:mexp
	indexedByOpt:idxNmOpt //TODO: 'indexedby' in TplAbsyn
	'=>' expressionMapTailRight(lesc,resc):exp  =>  MAP(headExp,mexp,exp)
	|
	_ => headExp

expressionMapTailRight(lesc,resc):
	expressionPlus(lesc,resc):exp
	  => exp
	|
	expressionUnary(lesc,resc):exp
	  => exp

plusTailOpt(lesc,resc,bexp):
	'+' expression_base(lesc,resc):exp  concatExp_rest(lesc,resc):expLst   //  concatenation ... same as "<expression><expression>"
	  => TEMPLATE(bexp::exp::expLst, "+", "");
	|
	'+' expressionUnary(lesc,resc):exp
	  => TEMPLATE({bexp,exp}, "+", "")
	|
	_ => bexp

concatPlus_rest(lesc,resc):
	'+' expression_base(lesc,resc):exp  concatExp_rest(lesc,resc):expLst
	  =>  exp::expLst
	|
	'+' expressionUnary(lesc,resc):exp
	  => { exp }
	|
	_ => {}

escapedOptions(lesc,resc):
	';' identifier:id  escOptionExp(lesc,resc):expOpt  escapedOptions(lesc,resc):opts
	=> (id, expOpt) :: opts
	|
	_ => {}

escOptionExp(lesc,resc):
	'=' expressionNoOptions(lesc,resc):exp
	  => SOME(exp)
	|
	_ => NONE


indexedByOpt:
	'hasindex' identifier:id
		=> SOME(id)
	|
	_ => NONE


concatLetExp_rest(lesc,resc):
	'let' letExp(lesc,resc):lexp  concatLetExp_rest(lesc,resc):expLst
	  =>  lexp::expLst
	|
	expression(lesc,resc):exp
	  => {exp}

