# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import importlib.util
import os.path
import sys
import unittest

def _GetDirAbove(dirname):
  """Returns the directory "above" this file containing |dirname| (which must
  also be "above" this file)."""
  path = os.path.abspath(__file__)
  while True:
    path, tail = os.path.split(path)
    assert tail
    if tail == dirname:
      return path

sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
from ply import lex

try:
  importlib.util.find_spec("mojom")
except ImportError:
  sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
import mojom.parse.lexer

# This (monkey-patching LexToken to make comparison value-based) is evil, but
# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
# for object identity.)
def _LexTokenEq(self, other):
  return self.type == other.type and self.value == other.value and \
         self.lineno == other.lineno and self.lexpos == other.lexpos


setattr(lex.LexToken, '__eq__', _LexTokenEq)


def _MakeLexToken(token_type, value, lineno=1, lexpos=0):
  """Makes a LexToken with the given parameters. (Note that lineno is 1-based,
  but lexpos is 0-based.)"""
  rv = lex.LexToken()
  rv.type, rv.value, rv.lineno, rv.lexpos = token_type, value, lineno, lexpos
  return rv


def _MakeLexTokenForKeyword(keyword, **kwargs):
  """Makes a LexToken for the given keyword."""
  return _MakeLexToken(keyword.upper(), keyword.lower(), **kwargs)


class LexerTest(unittest.TestCase):
  """Tests |mojom.parse.lexer.Lexer|."""

  def __init__(self, *args, **kwargs):
    unittest.TestCase.__init__(self, *args, **kwargs)
    # Clone all lexer instances from this one, since making a lexer is slow.
    self._zygote_lexer = lex.lex(mojom.parse.lexer.Lexer("my_file.mojom"))

  def testValidKeywords(self):
    """Tests valid keywords."""
    self.assertEquals(
        self._SingleTokenForInput("handle"), _MakeLexTokenForKeyword("handle"))
    self.assertEquals(
        self._SingleTokenForInput("import"), _MakeLexTokenForKeyword("import"))
    self.assertEquals(
        self._SingleTokenForInput("module"), _MakeLexTokenForKeyword("module"))
    self.assertEquals(
        self._SingleTokenForInput("struct"), _MakeLexTokenForKeyword("struct"))
    self.assertEquals(
        self._SingleTokenForInput("union"), _MakeLexTokenForKeyword("union"))
    self.assertEquals(
        self._SingleTokenForInput("interface"),
        _MakeLexTokenForKeyword("interface"))
    self.assertEquals(
        self._SingleTokenForInput("enum"), _MakeLexTokenForKeyword("enum"))
    self.assertEquals(
        self._SingleTokenForInput("const"), _MakeLexTokenForKeyword("const"))
    self.assertEquals(
        self._SingleTokenForInput("true"), _MakeLexTokenForKeyword("true"))
    self.assertEquals(
        self._SingleTokenForInput("false"), _MakeLexTokenForKeyword("false"))
    self.assertEquals(
        self._SingleTokenForInput("default"),
        _MakeLexTokenForKeyword("default"))
    self.assertEquals(
        self._SingleTokenForInput("array"), _MakeLexTokenForKeyword("array"))
    self.assertEquals(
        self._SingleTokenForInput("map"), _MakeLexTokenForKeyword("map"))
    self.assertEquals(
        self._SingleTokenForInput("associated"),
        _MakeLexTokenForKeyword("associated"))

  def testValidIdentifiers(self):
    """Tests identifiers."""
    self.assertEquals(
        self._SingleTokenForInput("abcd"), _MakeLexToken("NAME", "abcd"))
    self.assertEquals(
        self._SingleTokenForInput("AbC_d012_"),
        _MakeLexToken("NAME", "AbC_d012_"))
    self.assertEquals(
        self._SingleTokenForInput("_0123"), _MakeLexToken("NAME", "_0123"))

  def testInvalidIdentifiers(self):
    with self.assertRaisesRegexp(
        mojom.parse.lexer.LexError,
        r"^my_file\.mojom:1: Error: Illegal character '\$'$"):
      self._TokensForInput("$abc")
    with self.assertRaisesRegexp(
        mojom.parse.lexer.LexError,
        r"^my_file\.mojom:1: Error: Illegal character '\$'$"):
      self._TokensForInput("a$bc")

  def testDecimalIntegerConstants(self):
    self.assertEquals(
        self._SingleTokenForInput("0"), _MakeLexToken("INT_CONST_DEC", "0"))
    self.assertEquals(
        self._SingleTokenForInput("1"), _MakeLexToken("INT_CONST_DEC", "1"))
    self.assertEquals(
        self._SingleTokenForInput("123"), _MakeLexToken("INT_CONST_DEC", "123"))
    self.assertEquals(
        self._SingleTokenForInput("10"), _MakeLexToken("INT_CONST_DEC", "10"))

  def testValidTokens(self):
    """Tests valid tokens (which aren't tested elsewhere)."""
    # Keywords tested in |testValidKeywords|.
    # NAME tested in |testValidIdentifiers|.
    self.assertEquals(
        self._SingleTokenForInput("@123"), _MakeLexToken("ORDINAL", "@123"))
    self.assertEquals(
        self._SingleTokenForInput("456"), _MakeLexToken("INT_CONST_DEC", "456"))
    self.assertEquals(
        self._SingleTokenForInput("0x01aB2eF3"),
        _MakeLexToken("INT_CONST_HEX", "0x01aB2eF3"))
    self.assertEquals(
        self._SingleTokenForInput("123.456"),
        _MakeLexToken("FLOAT_CONST", "123.456"))
    self.assertEquals(
        self._SingleTokenForInput("\"hello\""),
        _MakeLexToken("STRING_LITERAL", "\"hello\""))
    self.assertEquals(
        self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
    self.assertEquals(
        self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
    self.assertEquals(
        self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
    self.assertEquals(
        self._SingleTokenForInput("="), _MakeLexToken("EQUALS", "="))
    self.assertEquals(
        self._SingleTokenForInput("=>"), _MakeLexToken("RESPONSE", "=>"))
    self.assertEquals(
        self._SingleTokenForInput("("), _MakeLexToken("LPAREN", "("))
    self.assertEquals(
        self._SingleTokenForInput(")"), _MakeLexToken("RPAREN", ")"))
    self.assertEquals(
        self._SingleTokenForInput("["), _MakeLexToken("LBRACKET", "["))
    self.assertEquals(
        self._SingleTokenForInput("]"), _MakeLexToken("RBRACKET", "]"))
    self.assertEquals(
        self._SingleTokenForInput("{"), _MakeLexToken("LBRACE", "{"))
    self.assertEquals(
        self._SingleTokenForInput("}"), _MakeLexToken("RBRACE", "}"))
    self.assertEquals(
        self._SingleTokenForInput("<"), _MakeLexToken("LANGLE", "<"))
    self.assertEquals(
        self._SingleTokenForInput(">"), _MakeLexToken("RANGLE", ">"))
    self.assertEquals(
        self._SingleTokenForInput(";"), _MakeLexToken("SEMI", ";"))
    self.assertEquals(
        self._SingleTokenForInput(","), _MakeLexToken("COMMA", ","))
    self.assertEquals(self._SingleTokenForInput("."), _MakeLexToken("DOT", "."))

  def _TokensForInput(self, input_string):
    """Gets a list of tokens for the given input string."""
    lexer = self._zygote_lexer.clone()
    lexer.input(input_string)
    rv = []
    while True:
      tok = lexer.token()
      if not tok:
        return rv
      rv.append(tok)

  def _SingleTokenForInput(self, input_string):
    """Gets the single token for the given input string. (Raises an exception if
    the input string does not result in exactly one token.)"""
    toks = self._TokensForInput(input_string)
    assert len(toks) == 1
    return toks[0]


if __name__ == "__main__":
  unittest.main()
