/*   Parser for a mini imperative language   
     
File parser_Imperative.pl 

The object language of our parser is an imperative mini-langauge
described by the following (concrete) syntax:

Expressions
-----------

Exp ::= NExp | BExp

Numerical Expressions

NExp  ::=  Num | NExp + NExp | NExp - NExp | (Exp) | if BExp then NExp else Nexp 

Boolean expressions
  
BExp  ::=  true | false | NExp < NExp | NExp = NExp | not BExp
        |  BExp and BExp | BExp and BExp 


Commands
--------

Cmd ::=  Ide := Exp                          (assignment) 
      |  read(Ide)                           (input) 
      |  print(Exp)                          (output) 
      |  if  BExp  then  Cmd  else Cmd       (conditional) 
      |  begin  Seqc  end                    (block without declarations) 
      |  begin  Seqd  in   Seqc   end        (block with declarations) 
      |  while  BExp  do  Cmd                (indefinite iteration) 
      |  for Ide :=  NExp  to  NExp  do Cmd  (definite iteration) 

Sequence of commands

Seq  ::=  Cmd  |  Seq  ;  Cmd 


Sequence of declarations of variables
            ------------
Seqd    ::=  Decvar  |  Seqd  ; Decvar 
Decvar  ::=  Ide  : Type 
Type    ::=  bool  |  int 


The parser is composed by three predicates: 

- parserC : parser for commands,
- parserD : parser for declarations
- parserE : parser for expressions
     
Each of these take in input a list of tokens, representing 
a command (respectively, declaration, expression) 
written in the concrete syntax of the object language
(the mini imperative language), and constructs the 
parse tree (abstract syntax).

Note 1: we make use of the system predicate string_to_atom(Y,X) which 
converts a constant X into a string Y.

Note 2: we have to change slightly the concrete syntax of read and 
write in the object language because Prolog will not accept 
parentheses in a list. We will use the symbol "/" instead. 
Hence instead of "read(x)" and "write(e)" we will have to write
"read/x/" and "write/e/".
*/

/* parser for commands */

parserC([X,:=|E], assign(Y,P)) :- ide(X), string_to_atom(Y,X), parserE(E,P).
parserC([read,/,X,/], read(Y)) :- ide(X), string_to_atom(Y,X).
parserC([write,/|L], write(P)) :- append(E,[/],L), parserE(E,P).
parserC(L,conc(P1,P2)) :- append(C1,[;|C2],L), parserC(C1,P1), parserC(C2,P2).
parserC([if|L],iteC(P,P1,P2)) :- append(E,[then|L1],L),
                                 append(C1,[else|C2],L1),
                                 parserE(E,P), parserC(C1,P1), parserC(C2,P2).
parserC([while|L],iterate(PE,PC)) :- append(E,[do|L1],L),
                                     append(C,[end],L1),
                                     parserE(E,PE),parserC(C,PC).
parserC([begin|L],block(PD,PC)) :- append(D,[in|L1],L),
                                   append(C,[end],L1),
                                   parserD(D,PD),parserC(C,PC).

/* checks that the argument is a correct identifier */

ide(X) :- not(integer(X)), not(X=true), not(X=false).

/* checks that the type is a legal type (int or bool) */

type(int).
type(bool).

/* parser for declarations */

parserD([X,:,T],vardec(Y,T)):- type(T), ide(X), string_to_atom(Y,X).
parserD([function,F,/,X,:,T1,/,:,T2,=|E],fundec(G,Y,T1,T2,P)) :- 
                                type(T1), type(T2),
                                ide(X), string_to_atom(Y,X),
                                ide(F), string_to_atom(G,F),
                                parserE(E,P).

/* parser for expressions */

parserE([N], n(N)):- integer(N).
parserE([true], b(true)).
parserE([false], b(false)).
parserE([X], i(Y)) :- ide(X), string_to_atom(Y,X).
parserE(L, plus(P1,P2)) :- append(E1,[+|E2],L), parserE(E1,P1), parserE(E2,P2).
parserE(L, minus(P1,P2)) :- append(E1,[-|E2],L),parserE(E1,P1), parserE(E2,P2).
parserE(L, smaller(P1,P2)):- append(E1,[<|E2],L),parserE(E1,P1),parserE(E2,P2).
parserE(L, equal(P1,P2)) :- append(E1,[=|E2],L), parserE(E1,P1),parserE(E2,P2).
parserE(L, both(P1,P2)) :- append(E1,[and|E2],L),parserE(E1,P1),parserE(E2,P2).
parserE([not,/|L], neg(P)) :- append(E,[/],L),parserE(E,P).
parserE([if|L], iteE(P,P1,P2)) :- append(E,[then|L1],L),
                                  append(E1,[else|E2],L1),
                                  parserE(E,P), parserC(E1,P1), parserC(E2,P2).
parserE([F,/|L],app(G,P)) :-  ide(F), string_to_atom(G,F), 
                              append(E,[/],L),parserE(E,P).

                                                            
/* -------------------------------------------------------------------------

EXAMPLE OF SESSION 

?-  parserC([begin,x,:,int,in,x,:=,0,end],T).
 
T = block(vardec("x", int), assign("x", n(0))) ;
 
No



?-  parserC([while,x,<,10,do,write,/,x,/,;,x,:=,x,+,1,end],T).
 
T = iterate(smaller(i("x"), n(10)), conc(write(i("x")), assign("x", plus(i("x"), n(1))))) ;
 
No



?- parserC([begin,x,:,int,in,x,:=,0,;,while,x,<,10,do,write,/,x,/,;,x,:=,x,+,1,end,end],T).
 
T = block(vardec("x", int), conc(assign("x", n(0)), iterate(smaller(i("x"), n(10)), conc(write(i("x")), assign("x", plus(i("x"), n(1))))))) ;
 
No


?-  parserC([x,:=,0,;,y,:=,1,;,z,:=,2],T).
 
T = conc(assign("x", n(0)), conc(assign("y", n(1)), assign("z", n(2)))) ;
 
T = conc(conc(assign("x", n(0)), assign("y", n(1))), assign("z", n(2))) ;
 
No

Note: We get more than one parse tree here because the syntax of the 
object language is ambiguous in the concatenation of commands. 
We could of course solve this ambiguity by choosing the kind 
of associativity. This would amount to split the case of ";" in 
parserC into two (hierarchical) clauses.

*/
