// 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

    var_lookup ::= "ID"

    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

        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))
            i += 2

        return [|
            class:

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


                func init(self):

                    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):

        // element ::= state
        //         ::= transition
        //         ::= var_def

        return self._preorder(node[0])



    func _t_state(self, node):

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

        return CEI::istring(node[1].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 := [| 1 |]
            action := [| func (&sm) { pass } |]
        else:
            // transition_tail ::= ":" event guard action
            event := self._preorder(tail_node[1])
            guard := self._preorder(tail_node[2])
            action := self._preorder(tail_node[3])

        return [| Transition.new($c{CEI::istring(node[1].value)}, $c{CEI::istring(node[3].value)}, $c{CEI::istring(node[5].value)}, $c{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:
            guard := [| 1 |]
        else:
            guard := self._preorder(node[1])

        return [|
            func (&sm):
                return $c{guard}
        |]


    func _t_action(self, node):

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

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

        return [|
            func (&sm):
                $c{action}
        |]



    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])
            rhs := self._preorder(node[2])
            ndif node[1].type == "AND":
                return [| $c{lhs} & $c{rhs} |]
            elif node[1].type == "OR":
                return [| $c{lhs} | $c{rhs} |]
            elif node[1].type == "==":
                return [| $c{lhs} == $c{rhs} |]
            elif node[1].type == "!=":
                return [| $c{lhs} != $c{rhs} |]
            elif node[1].type == ">":
                return [| $c{lhs} > $c{rhs} |]
            elif node[1].type == ">=":
                return [| $c{lhs} >= $c{rhs} |]
            elif node[1].type == "<":
                return [| $c{lhs} < $c{rhs} |]
            elif node[1].type == "<=":
                return [| $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:
            var_name := CEI::istring(node[0].value)
            val := self._preorder(node[2])
            return [| &sm.set_var(${var_name}, $c{val}) |]
        else:
            return self._preorder(node[0])



    func _t_E(self, node):

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

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



    func _t_var_lookup(self, node):

        // var_lookup ::= "ID"

        var_name := CEI::istring(node[0].value)

        return [<node[0].src_infos>| &sm.get_var(${var_name}) |]



    func _t_const(self, node):

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

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




func sm(dsl_block, src_infos):

    parse_tree := parse(dsl_block, src_infos)

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

    Sys::println(CEI::pp_itree(it))

    return it
