#!/usr/bin/env python3
#
# Copyright 2016 WebAssembly Community Group participants
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import argparse
import os
import struct
import sys

from utils import Error, Hexdump

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
OUT_DIR = os.path.join(ROOT_DIR, 'out')
PLY_DIR = os.path.join(ROOT_DIR, 'third_party', 'ply')

sys.path.append(PLY_DIR)

try:
    import ply.lex as lex
    import ply.yacc as yacc
except ImportError:
    raise Error('Unable to import ply. Did you run "git submodule update"?')

# ply stuff ###################################################################
NAMED_VALUES = {
    'i32': 0x7f,    # -1
    'i64': 0x7e,    # -2
    'f32': 0x7d,    # -3
    'f64': 0x7c,    # -4
    'v128': 0x7b,    # -5
    'funcref': 0x70,    # -0x10
    'function': 0x60,    # -0x20
    'struct': 0x5f,    # -0x21
    'array': 0x5e,    # -0x22
    'void': 0x40,    # -0x40
    'magic': (0, 0x61, 0x73, 0x6d),
    'version': (1, 0, 0, 0),

    # section codes
    'USER': 0,
    'TYPE': 1,
    'IMPORT': 2,
    'FUNCTION': 3,
    'TABLE': 4,
    'MEMORY': 5,
    'GLOBAL': 6,
    'EXPORT': 7,
    'START': 8,
    'ELEM': 9,
    'CODE': 10,
    'DATA': 11,
    'DATACOUNT': 12,
    'TAG': 13,

    # name subsection codes
    'NAME_MODULE': 0,
    'NAME_FUNCTION': 1,
    'NAME_LOCALS': 2,

    # linking subsection codes
    'LINKING_SEGMENT_INFO': 5,
    'LINKING_INIT_FUNCTIONS': 6,
    'LINKING_COMDAT_INFO': 7,
    'LINKING_SYMBOL_TABLE': 8,

    # dylink.0 subsection codes
    'DYLINK_MEM_INFO': 1,
    'DYLINK_NEEDED': 2,
    'DYLINK_EXPORT_INFO': 3,
    'DYLINK_IMPORT_INFO': 4,

    # external kinds
    'func_kind': 0,
    'table_kind': 1,
    'memory_kind': 2,
    'global_kind': 3,
    "unreachable": 0x00,
    "nop": 0x01,
    "block": 0x02,
    "loop": 0x03,
    "if": 0x04,
    "else": 0x05,
    "end": 0x0b,
    "br": 0x0c,
    "br_if": 0x0d,
    "br_table": 0x0e,
    "return": 0x0f,
    "call": 0x10,
    "call_indirect": 0x11,
    "return_call": 0x12,
    "return_call_indirect": 0x13,
    "drop": 0x1a,
    "select": 0x1b,
    "local.get": 0x20,
    "local.set": 0x21,
    "local.tee": 0x22,
    "global.get": 0x23,
    "global.set": 0x24,
    "i32.load": 0x28,
    "i64.load": 0x29,
    "f32.load": 0x2a,
    "f64.load": 0x2b,
    "i32.load8_s": 0x2c,
    "i32.load8_u": 0x2d,
    "i32.load16_s": 0x2e,
    "i32.load16_u": 0x2f,
    "i64.load8_s": 0x30,
    "i64.load8_u": 0x31,
    "i64.load16_s": 0x32,
    "i64.load16_u": 0x33,
    "i64.load32_s": 0x34,
    "i64.load32_u": 0x35,
    "i32.store": 0x36,
    "i64.store": 0x37,
    "f32.store": 0x38,
    "f64.store": 0x39,
    "i32.store8": 0x3a,
    "i32.store16": 0x3b,
    "i64.store8": 0x3c,
    "i64.store16": 0x3d,
    "i64.store32": 0x3e,
    "memory.size": 0x3f,
    "memory.grow": 0x40,
    "i32.const": 0x41,
    "i64.const": 0x42,
    "f32.const": 0x43,
    "f64.const": 0x44,
    "i32.eqz": 0x45,
    "i32.eq": 0x46,
    "i32.ne": 0x47,
    "i32.lt_s": 0x48,
    "i32.lt_u": 0x49,
    "i32.gt_s": 0x4a,
    "i32.gt_u": 0x4b,
    "i32.le_s": 0x4c,
    "i32.le_u": 0x4d,
    "i32.ge_s": 0x4e,
    "i32.ge_u": 0x4f,
    'i64.eqz': 0x50,
    "i64.eq": 0x51,
    "i64.ne": 0x52,
    "i64.lt_s": 0x53,
    "i64.lt_u": 0x54,
    "i64.gt_s": 0x55,
    "i64.gt_u": 0x56,
    "i64.le_s": 0x57,
    "i64.le_u": 0x58,
    "i64.ge_s": 0x59,
    "i64.ge_u": 0x5a,
    "f32.eq": 0x5b,
    "f32.ne": 0x5c,
    "f32.lt": 0x5d,
    "f32.gt": 0x5e,
    "f32.le": 0x5f,
    "f32.ge": 0x60,
    "f64.eq": 0x61,
    "f64.ne": 0x62,
    "f64.lt": 0x63,
    "f64.gt": 0x64,
    "f64.le": 0x65,
    "f64.ge": 0x66,
    "i32.clz": 0x67,
    "i32.ctz": 0x68,
    "i32.popcnt": 0x69,
    "i32.add": 0x6a,
    "i32.sub": 0x6b,
    "i32.mul": 0x6c,
    "i32.div_s": 0x6d,
    "i32.div_u": 0x6e,
    "i32.rem_s": 0x6f,
    "i32.rem_u": 0x70,
    "i32.and": 0x71,
    "i32.or": 0x72,
    "i32.xor": 0x73,
    "i32.shl": 0x74,
    "i32.shr_s": 0x75,
    "i32.shr_u": 0x76,
    "i32.rotl": 0x77,
    "i32.rotr": 0x78,
    "i64.clz": 0x79,
    "i64.ctz": 0x7a,
    "i64.popcnt": 0x7b,
    "i64.add": 0x7c,
    "i64.sub": 0x7d,
    "i64.mul": 0x7e,
    "i64.div_s": 0x7f,
    "i64.div_u": 0x80,
    "i64.rem_s": 0x81,
    "i64.rem_u": 0x82,
    "i64.and": 0x83,
    "i64.or": 0x84,
    "i64.xor": 0x85,
    "i64.shl": 0x86,
    "i64.shr_s": 0x87,
    "i64.shr_u": 0x88,
    "i64.rotl": 0x89,
    "i64.rotr": 0x8a,
    "f32.abs": 0x8b,
    "f32.neg": 0x8c,
    "f32.copysign": 0x8d,
    "f32.ceil": 0x8e,
    "f32.floor": 0x8f,
    "f32.trunc": 0x90,
    "f32.nearest": 0x91,
    "f32.sqrt": 0x92,
    "f32.add": 0x93,
    "f32.sub": 0x94,
    "f32.mul": 0x95,
    "f32.div": 0x96,
    "f32.min": 0x97,
    "f32.max": 0x98,
    "f64.abs": 0x99,
    "f64.neg": 0x9a,
    "f64.copysign": 0x9b,
    "f64.ceil": 0x9c,
    "f64.floor": 0x9d,
    "f64.trunc": 0x9e,
    "f64.nearest": 0x9f,
    "f64.sqrt": 0xa0,
    "f64.add": 0xa1,
    "f64.sub": 0xa2,
    "f64.mul": 0xa3,
    "f64.div": 0xa4,
    "f64.min": 0xa5,
    "f64.max": 0xa6,
    "i32.wrap_i64": 0xa7,
    "i32.trunc_f32_s": 0xa8,
    "i32.trunc_f32_u": 0xa9,
    "i32.trunc_f64_s": 0xaa,
    "i32.trunc_f64_u": 0xab,
    "i64.extend_i32_s": 0xac,
    "i64.extend_i32_u": 0xad,
    "i64.trunc_f32_s": 0xae,
    "i64.trunc_f32_u": 0xaf,
    "i64.trunc_f64_s": 0xb0,
    "i64.trunc_f64_u": 0xb1,
    "f32.convert_i32_s": 0xb2,
    "f32.convert_i32_u": 0xb3,
    "f32.convert_i64_s": 0xb4,
    "f32.convert_i64_u": 0xb5,
    "f32.demote_f64": 0xb6,
    "f64.convert_i32_s": 0xb7,
    "f64.convert_i32_u": 0xb8,
    "f64.convert_i64_s": 0xb9,
    "f64.convert_i64_u": 0xba,
    "f64.promote_f32": 0xbb,
    "i32.reinterpret_f32": 0xbc,
    "i64.reinterpret_f64": 0xbd,
    "f32.reinterpret_i32": 0xbe,
    "f64.reinterpret_i64": 0xbf,

    # bulk memory
    "memory.init": (0xfc, 0x08),
    "data.drop": (0xfc, 0x09),
    "memory.copy": (0xfc, 0x0a),
    "memory.fill": (0xfc, 0x0b),
    "table.init": (0xfc, 0x0c),
    "elem.drop": (0xfc, 0x0d),
    "table.copy": (0xfc, 0x0e),

    # exceptions
    "try": 0x06,
    "catch_all": 0x19,
}

keywords = {
    'func': 'FUNC',
    'section': 'SECTION',
    'leb_i32': 'LEB_I32',
    'leb_i64': 'LEB_I64',
    'leb_u32': 'LEB_U32',
    'f32': 'F32',
    'f64': 'F64',
    'str': 'STR',
}

# lexer ###

tokens = tuple(keywords.values()) + (
    'BYTE',
    'INT',
    'FLOAT',
    'STRING',
    'NAME',
    'NAMED_VALUE',
    'LPAREN',
    'RPAREN',
    'LBRACE',
    'RBRACE',
    'LBRACKET',
    'RBRACKET',)

t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACE = r'{'
t_RBRACE = r'}'
t_LBRACKET = r'\['
t_RBRACKET = r'\]'
t_ignore = ' \t'


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


def t_INT(t):
    r'\-?(0[xX][0-9a-fA-F]+|[0-9]+)'
    if t.value.lower().startswith('0x'):
        t.value = int(t.value, 16)
    else:
        t.value = int(t.value)

    if 0 <= t.value < 256:
        t.type = 'BYTE'
    return t


def t_FLOAT(t):
    r'\-?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][0-9]+)?'
    t.value = float(t.value)
    return t


def t_STRING(t):
    r'\'[^\']*\'|\"[^\"]*\"'
    t.value = t.value[1:-1]
    return t


def t_NAME(t):
    r'[a-zA-Z][a-zA-Z0-9_\.\/]*'
    if t.value in NAMED_VALUES:
        t.type = 'NAMED_VALUE'
        t.value = NAMED_VALUES[t.value]
    elif t.value in keywords:
        t.type = keywords[t.value]
    return t


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


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


lexer = lex.lex()

# parser ###


def LebLoop(data, v, cond):
    while True:
        byte = v & 0x7f
        v >>= 7
        if cond(v, byte):
            data.append(byte)
            break
        else:
            data.append(byte | 0x80)


def WriteUnsignedLeb(data, v, max_size):
    result = []
    LebLoop(result, v, lambda v, byte: v == 0)
    assert len(result) <= max_size
    data.extend(result)


def WriteLebU32(data, v):
    WriteUnsignedLeb(data, v, 5)


def WriteSignedLeb(data, v, max_size):
    result = []
    if v < 0:
        LebLoop(result, v, lambda v, byte: v == -1 and byte & 0x40)
    else:
        LebLoop(result, v, lambda v, byte: v == 0 and not byte & 0x40)
    assert len(result) <= max_size
    data.extend(result)


def WriteLebI32(data, v):
    WriteSignedLeb(data, v, 5)


def WriteLebI64(data, v):
    WriteSignedLeb(data, v, 10)


def WriteF32(data, v):
    data.extend(ord(b) for b in struct.pack('<f', v))


def WriteF64(data, v):
    data.extend(ord(b) for b in struct.pack('<d', v))


def WriteString(data, s):
    data.extend(ord(c) for c in s)


def p_data_byte(p):
    'data : data BYTE'
    p[0] = p[1]
    p[0].append(p[2])


def p_data_name(p):
    '''data : data NAME LBRACKET data RBRACKET
          | data FUNC LBRACKET data RBRACKET'''
    p[0] = p[1]
    # name is only used for documentation
    p[0].extend(p[4])


def p_data_named_value(p):
    'data : data NAMED_VALUE'
    p[0] = p[1]
    if type(p[2]) is tuple:
        p[0].extend(p[2])
    else:
        p[0].append(p[2])


def p_data_section(p):
    'data : data SECTION LPAREN NAMED_VALUE RPAREN LBRACE data RBRACE'
    p[0] = p[1]
    section_data = p[7]
    p[0].append(p[4])
    WriteLebU32(p[0], len(section_data))
    p[0].extend(section_data)


def p_data_user_section(p):
    'data : data SECTION LPAREN STRING RPAREN LBRACE data RBRACE'
    p[0] = p[1]
    name = p[4]
    section_data = p[7]
    p[0].append(0)  # 0 is the section code for "user"
    section_name_data = []
    WriteLebU32(section_name_data, len(name))
    WriteString(section_name_data, name)
    WriteLebU32(p[0], len(section_name_data) + len(section_data))
    p[0].extend(section_name_data)
    p[0].extend(section_data)


def p_data_func(p):
    'data : data FUNC LBRACE data RBRACE'
    p[0] = p[1]
    func_data = p[4]
    func_data.append(0xb)  # end opcode
    WriteLebU32(p[0], len(func_data))
    p[0].extend(func_data)


def p_data_str(p):
    'data : data STR LPAREN STRING RPAREN'
    p[0] = p[1]
    s = p[4]
    WriteLebU32(p[0], len(s))
    WriteString(p[0], s)


def p_data_leb_i32(p):
    '''data : data LEB_I32 LPAREN INT RPAREN
            | data LEB_I32 LPAREN BYTE RPAREN'''
    p[0] = p[1]
    WriteLebI32(p[0], p[4])


def p_data_leb_i64(p):
    '''data : data LEB_I64 LPAREN INT RPAREN
            | data LEB_I64 LPAREN BYTE RPAREN'''
    p[0] = p[1]
    WriteLebI64(p[0], p[4])


def p_data_leb_u32(p):
    '''data : data LEB_U32 LPAREN INT RPAREN
            | data LEB_U32 LPAREN BYTE RPAREN'''
    p[0] = p[1]
    WriteLebU32(p[0], p[4])


def p_data_f32(p):
    'data : data F32 LPAREN FLOAT RPAREN'
    p[0] = p[1]
    WriteF32(p[0], p[4])


def p_data_f64(p):
    'data : data F64 LPAREN FLOAT RPAREN'
    p[0] = p[1]
    WriteF64(p[0], p[4])


def p_data_string(p):
    'data : data STRING'
    p[0] = p[1]
    WriteString(p[0], p[2])


def p_data_empty(p):
    'data :'
    p[0] = []


def p_error(p):
    raise Error('%d: syntax error, %s' % (p.lineno, p))


parser = yacc.yacc(debug=False, tabmodule='gen_wasm',
                   debugfile='gen_wasm_debug.txt', outputdir=OUT_DIR)

################################################################################


def Run(input_file_name):
    with open(input_file_name) as input_file:
        input_data = input_file.read()
    data = parser.parse(input_data)
    # convert to bytes
    data = bytearray(data)
    return data


def main(args):
    arg_parser = argparse.ArgumentParser()
    arg_parser.add_argument('-o', '--output', metavar='PATH',
                            help='output file.')
    arg_parser.add_argument('-v', '--verbose',
                            help='print more diagnotic messages.',
                            action='store_true')
    arg_parser.add_argument('file', help='input file.')
    options = arg_parser.parse_args(args)
    data = Run(options.file)
    if options.output:
        with open(options.output, 'wb') as output_file:
            output_file.write(data)
    else:
        sys.stdout.writelines(Hexdump(data))
    return 0


if __name__ == '__main__':
    try:
        sys.exit(main(sys.argv[1:]))
    except Error as e:
        sys.stderr.write(str(e) + '\n')
        sys.exit(1)
