=begin
Timba language interpreter.
Copyright (C) 2016  Nicolas Guzzo

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
=end
require "parslet"

class TimbaParser < Parslet::Parser
    rule(:space)         { match('[\s]').repeat(1) }
    rule(:space?)        { space.maybe }    
    rule(:brk)           { match('[\n]').repeat(1) }
    rule(:brk?)          { brk.maybe }    
    rule(:comma)         { space? >> str(',') >> space? }
    rule(:comma?)        { comma.maybe }
    rule(:dash)          { str('-') >> space? }
    
	rule(:pila)          { (stri('la')>>space).maybe>>stri('pila')  }
    rule(:stack)         { pila >> space? }
    rule(:stackname)     { match('[a-zA-Z0-9]').repeat(1,10).as(:name) >> space?}
    rule(:carta)         { (stri('la')>>space).maybe>>stri('carta')}
    rule(:card)		     { carta.as(:card) >> space }
    rule(:number)        { str('12')| str('11')|str('10')| str('7')|str('6')|str('5')| str('4')| str('3')|str('2')| str('1') }
    
    rule(:sel_if)		 { stri('si') >> space? }
    rule(:sel_else)		 { stri('sino') >> space? }
    rule(:sel_endif)	 { stri('nada mas') >> space? }
    rule(:oros)          { stri('oros').as(:suit) >> space? }
    rule(:copas)         { stri('copas').as(:suit) >> space? }
    rule(:bastos)        { stri('bastos').as(:suit) >> space? }
    rule(:espadas)       { stri('espadas').as(:suit) >> space? }
    rule(:card_is)		 { stri('es').as(:is) >> space? }
    rule(:noes)          { stri('no')>>space>>stri(es) }   
    rule(:card_isnot)	 { noes.as(:isnot) >> space? }
    rule(:card_is_suit)	 { (stri('de') | stri('del')) >> space >> stri('palo') >>space? >> ( suits ) >> space? }
    rule(:suits)         { oros | bastos | copas | espadas }
    
    rule(:t1 )           { stri('tome') }
    rule(:t2 )           { stri('tome') >> space >> stri('una') }
    rule(:t3 )           { stri('tome') >> space >> stri('una')>> space >> stri('carta') }
    rule(:take)          { ( (t3|t2|t1) >> space >> stri('de') >> space? >> pila ) >> space? >> stackname.as(:take) >> comma? } 
    
    rule(:depositelacarta) {stri('deposite') >> space >> stri('la') >> space >> stri('carta')}
    rule(:deposit)       { (depositelacarta| stri('depositela'))  >> space >> stri('en') >> space >> pila  >> space? >> stackname.as(:deposit) >> comma?}
    
    rule(:inv)           { stri('invierta')>>space>>stri('la')>>space>>stri('carta') }
    rule(:invert)        { (inv|stri('inviertala')).as(:invert) >> space?  >> comma?}
    
    rule(:relac)         { stri('igual').as(:is) | stri('distinto').as(:isnot) }
    
    rule(:mayoriguala)   {stri('mayor')>>space>>stri('o')>>space>>stri('igual')>>space>>stri('a') }
    rule(:menoriguala)   {stri('menor')>>space>>stri('o')>>space>>stri('igual')>>space>>stri('a') }    
    rule(:mayorque)      {stri('mayor')>>space>>stri('que') }
    rule(:menorque)      {stri('menor')>>space>>stri('que') }    
    rule(:iguala)        {stri('igual')>>space>>stri('a')   }
    rule(:distintode)    {stri('distinto')>>space>>stri('de') }
    
    rule(:relacion)      { mayoriguala.as(:grteqthan) | menoriguala.as(:lesseqthan)|
                           iguala.as(:equal) | distintode.as(:notequal) |                            
                           mayorque.as(:grtthan)| menorque.as(:lessthan) 
                          }
    rule(:mayorigual)     {stri('mayor')>>space>>stri('o')>>space>>stri('igual') }
    rule(:menorigual)     {stri('menor')>>space>>stri('o')>>space>>stri('igual') }
    rule(:relacion2)      {  
                           mayorigual.as(:grteqthan)  | menorigual.as(:lesseqthan)|
                           stri('igual').as(:equal)   | stri('distinto').as(:notequal)    |
                           stri('mayor').as(:grtthan) | stri('menor').as(:lessthan)  
                          }        
	rule(:cond_and)      { cond_simple.as(:cond1) >> space? >> stri('y') >> space? >> cond_simple.as(:cond2) >> space?}
    rule(:cond_or)       { cond_simple.as(:cond1) >> space? >> stri('o') >> space? >> cond_simple.as(:cond2) >> space?}
    
    rule(:cond)          { cond_and.as(:and) | cond_or.as(:or) | cond_simple}
    
    rule(:cond_simple)   { cond_empty_stack.as(:cond_empty) | cond_card}
    rule(:vacia)         {stri('esta')>>space>>stri('vacia') }
    rule(:novacia)       {stri('no')>>space>>vacia}
    rule(:cond_empty_stack) { stack >> stackname >> (novacia.as(:not_empty) | vacia.as(:empty) ) >> space? } 
    
    rule(:cond_card)     { status.as(:cond_status) | caract }
    rule(:bocaabajo)     {stri('esta')>>space>>stri('boca')>>space>>stri('abajo')}
    rule(:nobocaabajo)   {stri('no')>>space>>bocaabajo}
    rule(:status)        { card >> ( bocaabajo.as(:is) |  nobocaabajo.as(:isnot) ) >> space? }
    rule(:devalor)       { stri('de')>>space>>stri('valor')}
    rule(:cond_val)      { card >> (card_is | card_isnot) >> devalor.maybe >>space? >> relacion >> space? >> number.as(:num) >> space? }
    
    rule(:delpalo)       { stri('del') >>space>>stri('palo')}
    rule(:quetopede)     {stri('que')>>space>>stri('tope')>>space>>stri('de')}
    rule(:paloquetopede)  { stri('palo' ) >>space>>quetopede }
    rule(:valorquetopede) { stri('valor') >>space>>quetopede }
    rule(:cond_suit)     { card >> (card_is | card_isnot) >> space? >> (delpalo>> space?).maybe >> suits >> space? }
    rule(:cond_suit_top) { card >> stri('es')>>space>> stri('de') >> space? >> relac >> space? >> paloquetopede >>space? >> stack >> space?  >> stackname }
    rule(:cond_val_top)  { card >> (card_is | card_isnot) >> space? >> stri('de')  >> space? >> relacion2 >> space?>> valorquetopede>>space? >>stack >>  space? >> stackname }
    
    rule(:caract)        { cond_val.as(:cond_val) | cond_suit.as(:cond_suit) | cond_suit_top.as(:cond_suit_top) | cond_val_top.as(:cond_val_top)} 

    rule(:selection)     { sel_if >> cond.as(:cond) >> space? >> expressions.as(:on_true) >> space? >> sel_else >> expressions.as(:on_false).maybe >> space? >> sel_endif >> space? >> comma?}
    
    rule(:repetition)    { stri('mientras') >> space? >> cond.as(:cond) >> space? >> expressions.as(:on_true)  >> space? >> stri('repita') >> comma? }
    

    rule(:operation)     { take | deposit | invert }
    rule(:control)       { selection.as(:if) | repetition.as(:while) }    
    rule(:expr)          { operation | control}
    rule(:expressions)   { expr.repeat   }
    rule(:ucp)           { stri('ucp')>>space>>stri('ejecute')>>space>>stri('con')>>space>>stri('las')>>space>>stri('siguientes')>>space>>stri('cartas')>>space?>>stri(':')}
    rule(:declarations)  { ucp >> space? >> stacks.maybe.as(:declarations) }
    rule(:stacks)        { stack_def.as(:stack) >> (comma >> stack_def.as(:stack)).repeat }
    rule(:card_def)      { number.as(:num)  >> space? >> stri('de') >> space >> suits >> space? >> (str('^')>> space?).as(:inverted).maybe}
    rule(:list)          { stri('tiene') >> space?  >> card_def >> ( dash >> card_def ).repeat >> space?}
	
    rule(:notienecartas) {stri('no')>>space>>stri('tiene')>>space>>stri('cartas') }
    rule(:stack_def)     { stack >> space? >> stackname >> ( list.as(:list) | ( notienecartas.as(:empty) >> space? ) ) }
    
    rule(:prog)       { stri('definicion')>>space>>stri('de')>>space>>stri('programa') }
    rule(:defprog)       { prog>> space? >> expressions.as(:program) >> space? >> str(';') >> space? >> declarations >> space?}

    root(:defprog)
    #root(:cond)
    def stri(str)
        key_chars = str.split(//)
        key_chars.collect! { |char| match["#{char.upcase}#{char.downcase}"] }.reduce(:>>)
    end
end