# Interface with the scanner.                           -*- Autotest -*-

# Copyright (C) 2019-2020 Free Software Foundation, Inc.

# 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 3 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, see <http://www.gnu.org/licenses/>.

AT_BANNER([[Interface with the scanner.]])


# -------------- #
# AT_RAW_YYLEX.  #
# -------------- #

m4_pushdef([AT_RAW_YYLEX],   [AT_LANG_DISPATCH([$0], $@)])

m4_define([AT_RAW_YYLEX(c)],
[#include <stdlib.h> /* abort */
AT_YYLEX_PROTOTYPE[
{
  static const char* input = "0-(1+2)*3/9";
  int c = *input++;
  switch (c)
    {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      ]AT_VAL[.val = c - '0';
      return NUM;
    case '+': return PLUS;
    case '-': return MINUS;
    case '*': return STAR;
    case '/': return SLASH;
    case '(': return LPAR;
    case ')': return RPAR;
    case 0:   return 0;
    }
  abort ();
}
]])

m4_define([AT_RAW_YYLEX(c++)],
[#include <stdlib.h> /* abort */
AT_YYLEX_PROTOTYPE[
{
  static const char* input = "0-(1+2)*3/9";
  int c = *input++;
  switch (c)
    {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':]AT_TOKEN_CTOR_IF([[
      return yy::parser::make_NUM (c - '0');]], [[
      ]AT_VAL[.val = c - '0';
      return yy::parser::token::NUM;]])[
    case '+': return yy::parser::]AT_TOKEN_CTOR_IF([make_PLUS ()],  [token::PLUS])[;
    case '-': return yy::parser::]AT_TOKEN_CTOR_IF([make_MINUS ()], [token::MINUS])[;
    case '*': return yy::parser::]AT_TOKEN_CTOR_IF([make_STAR ()],  [token::STAR])[;
    case '/': return yy::parser::]AT_TOKEN_CTOR_IF([make_SLASH ()], [token::SLASH])[;
    case '(': return yy::parser::]AT_TOKEN_CTOR_IF([make_LPAR ()],  [token::LPAR])[;
    case ')': return yy::parser::]AT_TOKEN_CTOR_IF([make_RPAR ()],  [token::RPAR])[;
    case 0:   return yy::parser::]AT_TOKEN_CTOR_IF([make_END ()],   [token::END])[;
    }
  abort ();
}
]])

m4_define([AT_RAW_YYLEX(d)],
[[import std.range.primitives;
import std.stdio;

auto yyLexer(R)(R range)
  if (isInputRange!R && is (ElementType!R : dchar))
{
  return new YYLexer!R(range);
}

auto yyLexer ()
{
  return yyLexer("0-(1+2)*3/9");
}

class YYLexer(R) : Lexer
  if (isInputRange!R && is (ElementType!R : dchar))
{
  R input;

  this(R r) {
    input = r;
  }

  ]AT_YYERROR_DEFINE[

  YYSemanticType semanticVal_;
  public final @property YYSemanticType semanticVal ()
  {
    return semanticVal_;
  }

  int yylex ()
  {
    import std.uni : isNumber;
    // Handle EOF.
    if (input.empty)
      return TokenKind.END;

    auto c = input.front;
    input.popFront;

    // Numbers.
    switch (c)
    {
    case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
      semanticVal_.val = c - '0';
      return TokenKind.NUM;
    case '+': return TokenKind.PLUS;
    case '-': return TokenKind.MINUS;
    case '*': return TokenKind.STAR;
    case '/': return TokenKind.SLASH;
    case '(': return TokenKind.LPAR;
    case ')': return TokenKind.RPAR;
    default: assert(0);
    }
  }
}
]])

m4_pushdef([AT_MAIN_DEFINE(d)],
[[int main ()
{
  auto l = yyLexer ();
  auto p = new YYParser (l);
  return !p.parse ();
}]])


m4_pushdef([AT_MAIN_DEFINE(java)],
[[class input
{
  public static void main (String[] args) throws IOException
  {]AT_LEXPARAM_IF([[
    ]AT_PARSER_CLASS[ p = new ]AT_PARSER_CLASS[ (System.in);]], [[
    ]AT_API_prefix[Lexer l = new ]AT_API_prefix[Lexer (System.in);
    ]AT_PARSER_CLASS[ p = new ]AT_PARSER_CLASS[ (l);]])AT_DEBUG_IF([[
    //p.setDebugLevel (1);]])[
    boolean success = p.parse ();
    if (!success)
      System.exit (1);
  }
}]])

m4_define([AT_RAW_YYLEX(java)],
[[class CalcLexer implements Calc.Lexer {

  StreamTokenizer st;

  public CalcLexer (InputStream is)
  {
    st = new StreamTokenizer (new StringReader ("0-(1+2)*3/9"));
    st.resetSyntax ();
    st.eolIsSignificant (true);
    st.whitespaceChars ('\t', '\t');
    st.whitespaceChars (' ', ' ');
    st.wordChars ('0', '9');
  }

  public void yyerror (String s)
  {
    System.err.println (s);
  }

  Integer yylval;

  public Object getLVal () {
    return yylval;
  }

  public int yylex () throws IOException {
    int ttype = st.nextToken ();
    switch (ttype)
      {
      case StreamTokenizer.TT_EOF:
        return EOF;
      case StreamTokenizer.TT_EOL:
        return (int) '\n';
      case StreamTokenizer.TT_WORD:
        yylval = new Integer (st.sval);
        return NUM;
      case '+':
        return PLUS;
      case '-':
        return MINUS;
      case '*':
        return STAR;
      case '/':
        return SLASH;
      case '(':
        return LPAR;
      case ')':
        return RPAR;
      default:
        throw new AssertionError ("invalid character: " + ttype);
      }
  }
}
]])


## ------------------- ##
## Raw token numbers.  ##
## ------------------- ##

m4_pushdef([AT_TEST],
[
AT_SETUP([Token numbers: $1])

AT_BISON_OPTION_PUSHDEFS([%debug ]m4_bmatch([$1], [java], [[%define api.prefix {Calc} %define api.parser.class {Calc}]])[ $1])
AT_DATA_GRAMMAR([[input.y]],
[[$1
%debug
]AT_LANG_MATCH([[c\|c++]], [[
%code
{
#include <stdio.h>
]AT_YYERROR_DECLARE[
]AT_YYLEX_DECLARE[
}]],
  [java], [[
%define api.prefix {Calc}
%define api.parser.class {Calc}
%code imports {
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.StringReader;
  import java.io.Reader;
  import java.io.StreamTokenizer;
}
]])[

]AT_LANG_MATCH([c\|c++\|d],
[AT_VARIANT_IF([[
%token <int> NUM "number"
%nterm <int> exp
]], [[
%union {
  int val;
}
%token <val> NUM "number"
%nterm <val> exp
]])],
    [java],
[[%token <Integer> NUM "number"
%type  <Integer> exp
]])[

%token
  PLUS  "+"
  MINUS "-"
  STAR  "*"
  SLASH "/"
  LPAR  "("
  RPAR  ")"
  END   0

%left "+" "-"
%left "*" "/"

%%

input
: exp         { ]AT_JAVA_IF([[System.out.println ($][1)]], [[printf ("%d\n", $][1)]])[; }
;

exp
: exp "+" exp { $][$][ = $][1 + $][3; }
| exp "-" exp { $][$][ = $][1 - $][3; }
| exp "*" exp { $][$][ = $][1 * $][3; }
| exp "/" exp { $][$][ = $][1 / $][3; }
| "(" exp ")" { $][$][ = $][2; }
| "number"    { $][$][ = $][1; }
;

%%
]AT_LANG_MATCH([c\|c++\|d],
               [AT_YYERROR_DEFINE])[
]AT_RAW_YYLEX[
]AT_MAIN_DEFINE[
]])

AT_FULL_COMPILE([input])

# When api.token.raw, the yytranslate table should not be included.
#
# yacc.c, glr.c and glr.cc use 'yytranslate' (and YYTRANSLATE).
# lalr1.cc uses 'translate_table' (and yytranslate_).
# lalr1.d uses 'byte[] translate_table =' (and yytranslate_).
# lalr1.java uses 'byte[] translate_table_ =' (and yytranslate_).
AT_CHECK([[$EGREP -c 'yytranslate\[\]|translate_table\[\]|translate_table =|translate_table_ =' input.]AT_LANG_EXT],
         [ignore],
         [AT_TOKEN_RAW_IF([0], [1])[
]])


AT_PARSER_CHECK([input], 0,
[[-1
]])

AT_BISON_OPTION_POPDEFS
AT_CLEANUP
])

m4_foreach([b4_skel], [[yacc.c], [glr.c], [lalr1.cc], [glr.cc], [glr2.cc], [lalr1.java], [lalr1.d]],
[AT_TEST([%skeleton "]b4_skel["])
 AT_TEST([%skeleton "]b4_skel[" %define api.token.raw])])

AT_TEST([%skeleton "lalr1.cc" %define api.token.raw %define api.value.type variant %define api.token.constructor])])

m4_popdef([AT_MAIN_DEFINE(java)])
m4_popdef([AT_MAIN_DEFINE(d)])
m4_popdef([AT_TEST])
