#!/usr/bin/env python
# Copyright (c) 2014 Quanta Research Cambridge, Inc
#
# 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.
#

from __future__ import print_function

import ply.lex as lex
import AST
import json, os, re, sys

import bsvpreprocess
import globalv
import cppgen, bsvgen

scripthome = os.path.dirname(os.path.abspath(__file__))
noisyFlag=True
parseDebugFlag=False
parseTrace=False

tokens = (
    'AMPER',
    'AMPERAMPER',
    'AMPERAMPERAMPER',
    'APOSTROPHE',
    'BANG',
    'BAR',
    'BARBAR',
    'BUILTINVAR',
    'CARET',
    'COLON',
    'COLONCOLON',
    'COMMA',
    'DOT',
    'EQEQ',
    'EQUAL',
    'GEQ',
    'GREATER',
    'GREATERGREATER',
    'HASH',
    'LARROW',
    'LBRACE',
    'LBRACKET',
    'LEQ',
    'LESS',
    'LESSLESS',
    'LPAREN',
    'LPARENSTAR',
    'MINUS',
    'NEQ',
    'NUM',
    'PERCENT',
    'PLUS',
    'QUESTION',
    'RBRACE',
    'RBRACKET',
    'RPAREN',
    'RPARENSTAR',
    'SEMICOLON',
    'SLASH',
    'STAR',
    'STARSTAR',
    'STR',
    'TILDE',
    'TILDEAMPER',
    'TILDEBAR',
    'TILDECARET',
    'VAR'
)

reserved = {
    'action': 'TOKACTION',
    'Action': 'TOKUACTION',
    'actionvalue': 'TOKACTIONVALUE',
    'BDPI': 'TOKBDPI',
    'begin': 'TOKBEGIN',
    'BVI': 'TOKBVI',
    'C': 'TOKC',
    'case': 'TOKCASE',
    'CF': 'TOKCF',
    'clocked_by': 'TOKCLOCKED_BY',
    'default': 'TOKDEFAULT',
    'default_clock': 'TOKDEFAULT_CLOCK',
    'default_reset': 'TOKDEFAULT_RESET',
    '`define': 'TOKTICKDEFINE',
    'dependencies': 'TOKDEPENDENCIES',
    'deriving': 'TOKDERIVING',
    'determines': 'TOKDETERMINES',
    'else': 'TOKELSE',
    'enable': 'TOKENABLE',
    'end': 'TOKEND',
    'endaction': 'TOKENDACTION',
    'endactionvalue': 'TOKENDACTIONVALUE',
    'endcase': 'TOKENDCASE',
    'endfunction': 'TOKENDFUNCTION',
    'endinstance': 'TOKENDINSTANCE',
    'endinterface': 'TOKENDINTERFACE',
    'endmethod': 'TOKENDMETHOD',
    'endmodule': 'TOKENDMODULE',
    'endpackage': 'TOKENDPACKAGE',
    'endpar': 'TOKENDPAR',
    'endrule': 'TOKENDRULE',
    'endrules': 'TOKENDRULES',
    'endseq': 'TOKENDSEQ',
    'endtypeclass': 'TOKENDTYPECLASS',
    'enum': 'TOKENUM',
    'export': 'TOKEXPORT',
    'for': 'TOKFOR',
    'function': 'TOKFUNCTION',
    'if': 'TOKIF',
    'import': 'TOKIMPORT',
#    'in': 'TOKIN',
    'input_clock': 'TOKINPUT_CLOCK',
    'input_reset': 'TOKINPUT_RESET',
    'instance': 'TOKINSTANCE',
    'interface': 'TOKINTERFACE',
    'let': 'TOKLET',
    'match': 'TOKMATCH',
    'matches': 'TOKMATCHES',
    'method': 'TOKMETHOD',
    'module': 'TOKMODULE',
    'no_reset': 'TOKNO_RESET',
    'numeric': 'TOKNUMERIC',
    'output_clock': 'TOKOUTPUT_CLOCK',
    'output_reset': 'TOKOUTPUT_RESET',
    'package': 'TOKPACKAGE',
    'par': 'TOKPAR',
    'port': 'TOKPORT',
    'parameter': 'TOKPARAMETER',
    'port': 'TOKPORT',
    'provisos': 'TOKPROVISOS',
    'ready': 'TOKREADY',
    'reset_by': 'TOKRESET_BY',
    'return': 'TOKRETURN',
    'rule': 'TOKRULE',
    'rules': 'TOKRULES',
    'SB': 'TOKSB',
    'SBR': 'TOKSBR',
    'schedule': 'TOKSCHEDULE',
    'seq': 'TOKSEQ',
    '_when_': 'TOKWHEN',
    'Stmt' : 'TOKSTMT',
    'struct': 'TOKSTRUCT',
    'tagged': 'TOKTAGGED',
    'type': 'TOKTYPE',
    'typeclass': 'TOKTYPECLASS',
    'typedef': 'TOKTYPEDEF',
    'union': 'TOKUNION',
    'while': 'TOKWHILE',
}

for tok in reserved.values():
    tokens = tokens + (tok,)

t_AMPER = r'&'
t_AMPERAMPER = r'&&'
t_AMPERAMPERAMPER = r'&&&'
t_APOSTROPHE = r'\''
t_BANG = r'!'
t_BAR = r'\|'
t_BARBAR = r'\|\|'
t_CARET = r'\^'
t_COLON = r':'
t_COLONCOLON = r'::'
t_COMMA = r','
t_DOT = r'[\.]'
t_EQEQ = r'=='
t_EQUAL = r'='
t_GEQ = r'>='
t_GREATER = r'>'
t_GREATERGREATER = r'>>'
t_HASH = r'\#'
t_LARROW = r'<-'
t_LBRACE = r'{'
t_LBRACKET = r'\['
t_LEQ = r'<='
t_LESS = r'<'
t_LESSLESS = r'<<'
t_LPAREN = r'\('
t_LPARENSTAR = r'\(\*'
t_MINUS = r'[-]'
t_NEQ = r'!='
t_NUM = r'(([0-9]+\'?[bdh\.]?[0-9a-zA-Z?]*)|(\'[bdh\.]?[0-9a-zA-Z?]+))'
t_PERCENT = r'%'
t_PLUS = r'\+'
t_QUESTION = r'\?'
t_RBRACE = r'}'
t_RBRACKET = r'\]'
t_RPAREN = r'\)'
t_RPARENSTAR = r'\*\)'
t_SEMICOLON = r';'
t_SLASH = r'/'
t_STAR = r'\*'
t_STARSTAR = r'\*\*'
t_STR = r'"[^\"]*"'
t_TILDE = r'~'
t_TILDEAMPER = r'~\&'
t_TILDEBAR = r'~\|'
t_TILDECARET = r'~^'

t_ignore = ' \t\f'

def t_error(t):
    print("Illegal character '%s' in file '%s'" % (t.value[0], globalfilename))
    t.lexer.skip(1)

def p_error(errtoken):
    if hasattr(errtoken, 'lineno'):
        sys.stderr.write("%s:%d: Syntax error, token=%s\n" % (globalfilename, errtoken.lineno, errtoken.type))
    else:
        sys.stderr.write("%s: Syntax error, token=%s\n" % (globalfilename, errtoken))
    return None
    
def t_VAR(t):
    r'`?([a-zA-Z_][$a-zA-Z0-9_]*)|(\\[-+*/|^&][*]?)'
    t.type = reserved.get(t.value,'VAR')    
    return t

t_BUILTINVAR = r'\$[a-zA-Z_][a-zA-Z0-9_]*'

def t_newline(t):
    r'\n+'
    t.lexer.lineno += len(t.value)

def t_COMMENT(t):
    r'//.*'
    pass

def t_MCOMMENT(t):
    r'/\*(.|\n)*?\*/'
    #print(t.value, t.value.count('\n'), t.lineno)
    t.lineno += t.value.count('\n')

import ply.yacc as yacc

def p_goal(p):
    'goal : package '
    p[0] = p[1]

def p_typeParams(p):
    '''typeParams :
                  | type
                  | typeParams COMMA type'''
    if len(p) == 2:
        p[0] = [p[1]]
    elif len(p) == 4:
        p[0] = p[1] + [p[3]]
    else:
        p[0] = []

def p_type(p):
    '''type : VAR
            | VAR COLONCOLON VAR
            | NUM
            | TOKUACTION
            | VAR HASH LPAREN typeParams RPAREN
            | VAR COLONCOLON VAR HASH LPAREN typeParams RPAREN'''
    if len(p) == 2:
        p[0] = AST.Type(p[1], [])
    elif len(p) == 4:
        p[0] = p[3]
    elif len(p) == 8:
        p[0] = AST.Type(p[3], p[6])
    else:
        p[0] = AST.Type(p[1], p[4])

def p_expressions(p):
    '''expressions : expression
                   | 
                   | expressions COMMA expression'''

precedence = (
    ('left', 'STAR', 'SLASH', 'PERCENT'),
    ('left', 'PLUS', 'MINUS'),
    ('left', 'GREATERGREATER', 'LESSLESS'),
    ('left', 'LEQ', 'GEQ', 'LESS', 'GREATER'),
    ('left', 'EQEQ', 'NEQ'),
    ('left', 'AMPER'),
    ('left', 'CARET'),
    ('left', 'TILDECARET'),
    ('left', 'BAR'),
    ('left', 'AMPERAMPER'),
    ('left', 'BARBAR'),
    ('left', 'AMPERAMPERAMPER')
)

def p_colonVar(p):
    '''colonVar :
                | COLON VAR'''

def p_expression(p):
    '''expression : caseExpr
                  | binaryExpression'''
    p[0] = p[1]

def p_caseExprItem(p):
    '''caseExprItem : pattern COLON expression SEMICOLON'''

def p_caseExprItems(p):
    '''caseExprItems :
                 | caseExprItems caseExprItem'''

def p_defaultExprItem(p):
    '''defaultExprItem :
                   | TOKDEFAULT expression SEMICOLON
                   | TOKDEFAULT COLON expression SEMICOLON'''

def p_caseExpr(p):
    '''caseExpr : TOKCASE LPAREN expression RPAREN caseExprItems defaultExprItem TOKENDCASE
                | TOKCASE LPAREN expression RPAREN TOKMATCHES caseExprItems defaultExprItem TOKENDCASE'''

def p_binaryExpression(p):
    '''binaryExpression : unaryExpression
                        | binaryExpression AMPERAMPERAMPER binaryExpression
                        | binaryExpression MINUS binaryExpression
                        | binaryExpression PLUS binaryExpression
                        | binaryExpression STAR binaryExpression
                        | binaryExpression STARSTAR binaryExpression
                        | binaryExpression APOSTROPHE binaryExpression
                        | binaryExpression SLASH binaryExpression
                        | binaryExpression CARET binaryExpression
                        | binaryExpression LESS binaryExpression
                        | binaryExpression GREATER binaryExpression
                        | binaryExpression GEQ binaryExpression
                        | binaryExpression LESSLESS binaryExpression
                        | binaryExpression LEQ binaryExpression
                        | binaryExpression GREATERGREATER binaryExpression
                        | binaryExpression EQEQ binaryExpression
                        | binaryExpression NEQ binaryExpression
                        | binaryExpression AMPER binaryExpression
                        | binaryExpression AMPERAMPER binaryExpression
                        | binaryExpression BAR binaryExpression
                        | binaryExpression BARBAR binaryExpression
                        | binaryExpression PERCENT binaryExpression'''
    p[0] = p[1]

def p_unaryExpression(p):
    '''unaryExpression : term
                       | PLUS term
                       | MINUS term
                       | BANG term
                       | TILDE term
                       | AMPER term
                       | TILDEAMPER term
                       | BAR term
                       | TILDEBAR term
                       | CARET term
                       | TILDECARET term
                       | TOKACTION colonVar expressionStmts TOKENDACTION colonVar
                       | TOKACTIONVALUE colonVar expressionStmts TOKENDACTIONVALUE colonVar
                       '''
    p[0] = p[1]

def p_term(p):
    '''term : type
            | type LBRACKET expression RBRACKET
            | type LBRACKET expression COLON expression RBRACKET
            | STR
            | QUESTION
            | term QUESTION expression
            | term QUESTION expression COLON expression
            | LPAREN expression RPAREN
            | TOKINTERFACE VAR interfaceHashParams SEMICOLON expressionStmts TOKENDINTERFACE colonVar
            | TOKINTERFACE VAR COLONCOLON VAR interfaceHashParams SEMICOLON expressionStmts TOKENDINTERFACE colonVar
            | TOKINTERFACE VAR expressionStmts TOKENDINTERFACE colonVar
            | TOKINTERFACE VAR COLONCOLON VAR expressionStmts TOKENDINTERFACE colonVar
            | BUILTINVAR
            | TOKCLOCKED_BY expression
            | TOKRESET_BY expression
            | TOKTAGGED VAR
            | TOKTAGGED VAR expression
            | TOKTAGGED VAR LBRACE structInits RBRACE
            | term LBRACE structInits RBRACE
            | term TOKMATCHES pattern
            | LBRACE expressions RBRACE
            | term DOT VAR
            | term LBRACKET expression RBRACKET DOT term
            | term LBRACKET expression RBRACKET
            | term LBRACKET expression COLON expression RBRACKET
            | term LPAREN params RPAREN DOT term
            | term LPAREN params RPAREN'''
    if len(p) > 2 and type(p[1]) == str:
        p[0] = p[2]
    else:
        p[0] = p[1]

def p_structInits(p):
    '''structInits : 
                   | structInits COMMA VAR COLON expression
                   | structInits COMMA VAR COLON DOT VAR
                   | VAR COLON expression
                   | VAR COLON DOT VAR'''

def p_structPatternElements(p):
    '''structPatternElements : VAR COLON pattern
                             | structPatternElements COMMA VAR COLON pattern '''

def p_pattern(p):
    '''pattern : TOKTAGGED VAR
               | TOKTAGGED VAR DOT VAR
               | TOKTAGGED VAR LBRACE structPatternElements RBRACE
               | LBRACE patterns RBRACE
               | DOT VAR
               | DOT STAR
               | NUM'''

def p_patterns(p):
    '''patterns : pattern
                | patterns COMMA pattern'''

def p_importDecl(p):
    'importDecl : TOKIMPORT VAR COLONCOLON STAR SEMICOLON'
    if not p[2] in globalimports:
        globalimports.append(p[2])
    p[0] = p[2]

def p_importDecls(p):
    '''importDecls : 
                   | importDecls importDecl'''

def p_exportDecl(p):
    '''exportDecl : TOKEXPORT VAR LPAREN DOT DOT RPAREN SEMICOLON
                  | TOKEXPORT VAR SEMICOLON
                  | TOKEXPORT VAR COLONCOLON STAR SEMICOLON'''
    p[0] = p[2]

def p_exportDecls(p):
    '''exportDecls :
                   | exportDecls exportDecl'''

def p_interfaceFormalParam(p):
    '''interfaceFormalParam : TOKTYPE VAR
                            | VAR interfaceHashParams
                            | NUM
                            | TOKNUMERIC TOKTYPE VAR'''
    if len(p) == 2:
        p[0] = p[1]
    elif len(p) == 3:
        p[0] = p[2]
    else:
        p[0] = p[3]

def p_interfaceFormalParams(p):
    '''interfaceFormalParams : interfaceFormalParam
                             | interfaceFormalParams COMMA interfaceFormalParam'''
    if len(p) == 2:
        p[0] = [p[1]]
    else:
        p[0] = p[1] + [p[3]]

def p_interfaceHashParams(p):
    '''interfaceHashParams :
                           | HASH LPAREN interfaceFormalParams RPAREN'''
    if len(p) == 5:
        p[0] = p[3]
    else:
        p[0] = []

def p_instanceAttributes(p):
    '''instanceAttributes :
                          | instanceAttributes LPARENSTAR attrSpecs RPARENSTAR'''

def p_subinterfaceDecl(p):
    '''subinterfaceDecl : instanceAttributes TOKINTERFACE type VAR SEMICOLON
                        | type VAR SEMICOLON'''
    if len(p) == 6:
        name = p[4]
        t = p[3]
    elif len(p) == 5:
        name = p[3]
        t = p[2]
    else:
        name = p[2]
        t = p[1]