// Copyright (c) 2008 Laurence Tratt <laurie@tratt.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.


import Builtins, CEI, CPK::Earley::DSL, CPK::Traverser, Exceptions, Strings, Sys




parse := $<<DSL::mk_parser("system", ["state", "transition", "and", "or", "from", "to"], [])>>:
    system ::= element ( "NEWLINE" element )*

    element ::= state
              | transition
              | var_def

    state ::= "STATE" "ID"


    transition      ::= "TRANSITION" "ID" "FROM" "ID" "TO" "ID" transition_tail
    transition_tail ::= ":" event guard action
                      |

    var_def ::= "ID" ":=" const

    event ::= "ID"
            |

    guard ::= "[" B "]"
            |

    action ::= "/" C
             |

    B ::= B "AND" B   %precedence 5
        | B "OR" B   %precedence 5
        | B "==" B   %precedence 10
        | B "!=" B   %precedence 10
        | B ">" B   %precedence 10
        | B ">=" B   %precedence 10
        | B "<" B   %precedence 10
        | B "<=" B   %precedence 10
        | "NOT" B   %precedence 5
        | E

    C ::= A ( ";" A )*

    A ::= "ID" ":=" E
        | E

    E ::= E "+" E %precedence 5
        | E "-" E %precedence 5
        | E "*" E %precedence 10
        | E "/" E %precedence 7
        | var_lookup
        | const
        | apply

    var_lookup ::= "ID"

    apply ::= "ID" "(" args ")"
    args  ::= E ( "," E )*
            |

    const ::= "INT"
            | "STRING"




class Transition:

    func init(self, name, from, to, event, guard, action):

        self.name := name
        self.from := from
        self.to := to
        self.event := event
        self.guard := guard
        self.action := action




class SM_Translator(Traverser::Strict_Traverser):

    func generate(self, node):

        return self._preorder(node)



    func _t_system(self, node):
    
        // system ::= element ( "NEWLINE" element )*

        sts := [] // States
        tns := [] // Transitions
        vars := [] // Vars
        self._var_names := Set{} // Variable names as strings
        self._free_names := Dict{} // Maps variable names (as strings) to fresh names (as strings)

        i := 0
        while i < node.len():
            ndif node[i][0].name == "state":
                sts.append(self._preorder(node[i]))
            elif node[i][0].name == "transition":
                tns.append(self._preorder(node[i]))
            elif node[i][0].name == "var_def":
                // var_def ::= "ID" ":=" const
                var_def := node[i][0]
                key := CEI::istring(var_def[0].value)
                val := self._preorder(var_def[2])
                vars.append(CEI::idict_elem(key, val))
                self._var_names.add(var_def[0].value)
            i += 2

        renames := []
        for free_name, rn_free_name := self._free_names.iter():
            ifree_name := CEI::ivar(free_name)
            irn_free_name := CEI::ivar(rn_free_name)
            irename := CEI::irename(ifree_name, irn_free_name)
            renames.append(irename)

        return [|
            func () {
                rename $c{renames}

                return class:

                    states := ${CEI::ilist(sts)}
                    transitions := $c{CEI::ilist(tns)}


                    func init(self):

                        self.vars := ${CEI::idict(vars)}
                        self.state := ${sts[0]}

                        while self.transition("")



                    func get_var(self, v):

                        return self.vars[v]



                    func set_var(self, v, o):

                        self.vars[v] := o



                    func event(self, e):

                        if not self.transition(e):
                            msg := Strings::format("Error: No valid transition for event %s from state %s", e, self.state)
                            raise Exceptions::User_Exception.new(msg)
                        while self.transition("")



                    func transition(self, e):

                        for tn := self.transitions.iter():
                            if tn.from == self.state & tn.event == e & tn.guard(self):
                                Sys::println("  Taking transition ", tn.name, " to state ", tn.to)
                                Sys::println("  ", self.vars)
                                self.state := tn.to
                                tn.action(self)
                                break
                        exhausted:
                            fail
            }()
        |]



    func _t_element(self, node):

        return self._preorder(node[0])



    func _t_state(self, node):

        // state ::= "STATE" "ID"

        return CEI::istring(node[0].value)



    func _t_transition(self, node):

        // transition      ::= "TRANSITION" "ID" "FROM" "ID" "TO" "ID" transition_tail
        // transition_tail ::= ":" event guard action
        //                 ::=

        tail_node := node[-1]
        if tail_node.len() == 0:
            // transition_tail ::=
            event := [| "" |]
            guard := [| func (&sm) { return 1 } |]
            action := [| func (&sm) { pass } |]
        else:
            // transition_tail ::= ":" event guard action
            event := self._preorder(tail_node[1])
            guard := [|
                func (&sm):
                    if $c{self._preorder(tail_node[2])}:
                        return 1
                    else:
                        fail
            |]
            action := [| func (&sm) { $c{self._preorder(tail_node[3])} } |]

        return [| Transition.new(${CEI::istring(node[1].value)}, ${CEI::istring(node[3].value)}, ${CEI::istring(node[5].value)}, ${event}, $c{guard}, $c{action}) |]



    func _t_event(self, node):

        // event ::= "ID"
        //       ::=

        if node.len() == 0:
            return [| "" |]
        else:
            return CEI::istring(node[0].value)



    func _t_guard(self, node):

        // guard ::= "[" B "]"
        //       ::=

        if node.len() == 0:
            return [| 1 |]
        else:
            return self._preorder(node[1])


    func _t_action(self, node):

        // action ::= "/" C
        //        ::=

        if node.len() == 0:
            return [| pass |]
        else:
            return self._preorder(node[1])



    func _t_B(self, node):

        // B ::= B "AND" B
        //   ::= B "OR" B
        //   ::= B "==" B
        //   ::= B "!=" B
        //   ::= B ">" B
        //   ::= B ">=" B
        //   ::= B "<" B
        //   ::= B "<=" B
        //   ::= "NOT" B
        //   ::= "ID"
        //   ::= E

        if node.len() == 3:
            lhs := self._preorder(node[0])
            op := node[1]
            rhs := self._preorder(node[2])
            ndif op.type == "AND":
                return [<node.src_infos>| $c{lhs} & $c{rhs} |]
            elif op.type == "OR":
                return [<node.src_infos>| $c{lhs} | $c{rhs} |]
            elif op.type == "==":
                return [<node.src_infos>| $c{lhs} == $c{rhs} |]
            elif op.type == "!=":
                return [<node.src_infos>| $c{lhs} != $c{rhs} |]
            elif op.type == ">":
                return [<node.src_infos>| $c{lhs} > $c{rhs} |]
            elif op.type == ">=":
                return [<node.src_infos>| $c{lhs} >= $c{rhs} |]
            elif op.type == "<":
                return [<node.src_infos>| $c{lhs} < $c{rhs} |]
            elif op.type == "<=":
                return [<node.src_infos>| $c{lhs} <= $c{rhs} |]
        elif node.len() == 2:
            // B ::= "NOT" B
            return [| not $c{self._preorder(node[1])} |]
        else:
            // B ::= E
            return self._preorder(node[0])



    func _t_C(self, node):

        // C ::= A ( ";" A )*

        i := 0
        actions := []
        while i < node.len():
            actions.append(self._preorder(node[i]))
            i += 2

        return actions



    func _t_A(self, node):

        // A ::= "ID" ":=" E
        //   ::= E

        if node.len() == 3:
            if not self._var_names.find(node[0].value):
                CEI::error(Strings::format("Unknown state-machine variable '%s'.", node[0].value), node[0].src_infos)
            return [| &sm.set_var(${CEI::lift(node[0].value)}, $c{self._preorder(node[2])}) |]
        else:
            return self._preorder(node[0])



    func _t_E(self, node):

        // E ::= E "+" E
        //   ::= E "-" E
        //   ::= E "*" E
        //   ::= E "/" E
        //   ::= var_lookup
        //   ::= const
        //   ::= apply

        if node.len() == 3:
            lhs := self._preorder(node[0])
            rhs := self._preorder(node[2])
            if node[1].type == "+":
                return [<node.src_infos>| $c{lhs} + $c{rhs} |]
            elif node[1].type == "-":
                return [<node.src_infos>| $c{lhs} - $c{rhs} |]
            elif node[1].type == "*":
                return [<node.src_infos>| $c{lhs} * $c{rhs} |]
            elif node[1].type == "/":
                return [<node.src_infos>| $c{lhs} / $c{rhs} |]
        else:
            return self._preorder(node[0])



    func _t_var_lookup(self, node):

        // var_lookup ::= "ID"

        if not self._var_names.find(node[0].value):
            CEI::error(Strings::format("Unknown state-machine variable '%s'.", node[0].value), node[0].src_infos)
        return [<node.src_infos>| &sm.get_var(${CEI::istring(node[0].value)}) |]



    func _t_apply(self, node):

        // apply ::= "ID" "(" args ")"

        args := self._preorder(node[2])

        if node[0].value == "input":
            return [| Sys::stdin.readln() |]
        else:
            func_name := node[0].value
            if not rn_func_name := self._free_names.find(func_name):
                rn_func_name := CEI::fresh_name()
                self._free_names[func_name] := rn_func_name
 
            func_var := CEI::ivar(rn_func_name, node[0].src_infos)
            args := self._preorder(node[2])

            return CEI::iapplication(func_var, args, node.src_infos)



    func _t_args(self, node):

        // args  ::= E ( "," E )*
        //       ::=

        args := []
        i := 0
        while i < node.len():
            args.append(self._preorder(node[i]))
            i += 2

        return args




    func _t_const(self, node):

        // const ::= "INT"
        //       ::= "STRING"

        ndif node[0].type == "INT":
            return CEI::lift(Builtins::Int.new(node[0].value), node.src_infos)
        elif node[0].type == "STRING":
            return CEI::lift(node[0].value, node.src_infos)



func sm(dsl_block, src_infos):

    parse_tree := parse(dsl_block, src_infos)

    x := SM_Translator.new().generate(parse_tree)

    Sys::println(CEI::pp_itree(x))

    return x
