mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-02-25 21:21:14 +00:00
[qfcc] Implement basic conditional compilation
Just #if, #else and #endif for now. However, much of the preprocessor parsing working minus the semantics (expressions do work, though).
This commit is contained in:
parent
df3ffaaf0f
commit
5794c68bda
7 changed files with 652 additions and 44 deletions
|
@ -119,6 +119,7 @@ const char *file_basename (const char *filename, int keepdot) __attribute__((pur
|
||||||
|
|
||||||
int qc_yyparse (FILE *in);
|
int qc_yyparse (FILE *in);
|
||||||
int qp_yyparse (FILE *in);
|
int qp_yyparse (FILE *in);
|
||||||
|
extern int pre_yydebug;
|
||||||
extern int qc_yydebug;
|
extern int qc_yydebug;
|
||||||
extern int qp_yydebug;
|
extern int qp_yydebug;
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,9 @@ typedef struct rua_loc_s {
|
||||||
int file;
|
int file;
|
||||||
} rua_loc_t;
|
} rua_loc_t;
|
||||||
|
|
||||||
|
typedef struct rua_macro_s {
|
||||||
|
} rua_macro_t;
|
||||||
|
|
||||||
typedef struct rua_tok_s {
|
typedef struct rua_tok_s {
|
||||||
rua_loc_t location;
|
rua_loc_t location;
|
||||||
int textlen;
|
int textlen;
|
||||||
|
@ -44,9 +47,25 @@ typedef struct rua_tok_s {
|
||||||
struct {
|
struct {
|
||||||
void *pointer; // mirrors pointer in QC_YYSTYPE
|
void *pointer; // mirrors pointer in QC_YYSTYPE
|
||||||
char str_text[8];// if len < 8 and spec not used
|
char str_text[8];// if len < 8 and spec not used
|
||||||
|
int token; // when recording macros
|
||||||
};
|
};
|
||||||
QC_YYSTYPE value;
|
QC_YYSTYPE value;
|
||||||
|
struct dstring_s *dstr;
|
||||||
|
rua_macro_t *macro;
|
||||||
};
|
};
|
||||||
} rua_tok_t;
|
} rua_tok_t;
|
||||||
|
|
||||||
|
rua_macro_t *rua_start_macro (void *scanner);
|
||||||
|
rua_macro_t *rua_macro_append (rua_macro_t *macro, rua_tok_t *token,
|
||||||
|
void *scanner);
|
||||||
|
void rua_macro_finish (rua_macro_t *macro, void *scanner);
|
||||||
|
void rua_start_text (void *scanner);
|
||||||
|
void rua_start_expr (void *scanner);
|
||||||
|
void rua_end_directive (void *scanner);
|
||||||
|
void rua_if (bool pass, void *scanner);
|
||||||
|
void rua_else (bool pass, const char *tok, void *scanner);
|
||||||
|
void rua_endif (void *scanner);
|
||||||
|
|
||||||
|
#include "tools/qfcc/source/pre-parse.h"
|
||||||
|
|
||||||
#endif//__rua_lang_h
|
#endif//__rua_lang_h
|
||||||
|
|
|
@ -48,6 +48,7 @@ qfcc_SOURCES = \
|
||||||
tools/qfcc/source/opcodes.c \
|
tools/qfcc/source/opcodes.c \
|
||||||
tools/qfcc/source/options.c \
|
tools/qfcc/source/options.c \
|
||||||
tools/qfcc/source/pragma.c \
|
tools/qfcc/source/pragma.c \
|
||||||
|
tools/qfcc/source/pre-parse.y \
|
||||||
tools/qfcc/source/qc-lex.l \
|
tools/qfcc/source/qc-lex.l \
|
||||||
tools/qfcc/source/qc-parse.y \
|
tools/qfcc/source/qc-parse.y \
|
||||||
tools/qfcc/source/qfcc.c \
|
tools/qfcc/source/qfcc.c \
|
||||||
|
@ -88,6 +89,8 @@ BUILT_SOURCES += \
|
||||||
tools/qfcc/source/qp-parse.h \
|
tools/qfcc/source/qp-parse.h \
|
||||||
tools/qfcc/source/qp-lex.c
|
tools/qfcc/source/qp-lex.c
|
||||||
|
|
||||||
|
tools/qfcc/source/pre-parse.c: tools/qfcc/source/pre-parse.y
|
||||||
|
$(AM_V_YACC)$(YACCCOMPILE) $< -o $@
|
||||||
tools/qfcc/source/qc-parse.c: tools/qfcc/source/qc-parse.y
|
tools/qfcc/source/qc-parse.c: tools/qfcc/source/qc-parse.y
|
||||||
$(AM_V_YACC)$(YACCCOMPILE) $< -o $@
|
$(AM_V_YACC)$(YACCCOMPILE) $< -o $@
|
||||||
tools/qfcc/source/qc-lex.c: tools/qfcc/source/qc-lex.l
|
tools/qfcc/source/qc-lex.c: tools/qfcc/source/qc-lex.l
|
||||||
|
|
|
@ -862,6 +862,7 @@ DecodeArgs (int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.verbosity >= 3) {
|
if (options.verbosity >= 3) {
|
||||||
|
pre_yydebug = 1;
|
||||||
qc_yydebug = 1;
|
qc_yydebug = 1;
|
||||||
qp_yydebug = 1;
|
qp_yydebug = 1;
|
||||||
}
|
}
|
||||||
|
|
279
tools/qfcc/source/pre-parse.y
Normal file
279
tools/qfcc/source/pre-parse.y
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
pre-parse.y
|
||||||
|
|
||||||
|
parser for the preprocessor
|
||||||
|
|
||||||
|
Copyright (C) 2023 Bill Currie <bill@taniwha.org>
|
||||||
|
|
||||||
|
Author: Bill Currie <bill@taniwha.org>
|
||||||
|
Date: 2023/10/12
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
Free Software Foundation, Inc.
|
||||||
|
59 Temple Place - Suite 330
|
||||||
|
Boston, MA 02111-1307, USA
|
||||||
|
|
||||||
|
*/
|
||||||
|
%define api.prefix {pre_yy}
|
||||||
|
%define api.pure full
|
||||||
|
%define api.push-pull push
|
||||||
|
%define api.token.prefix {PRE_}
|
||||||
|
%locations
|
||||||
|
%parse-param {void *scanner}
|
||||||
|
|
||||||
|
%{
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "QF/dstring.h"
|
||||||
|
|
||||||
|
#include "tools/qfcc/include/diagnostic.h"
|
||||||
|
#include "tools/qfcc/include/expr.h"
|
||||||
|
#include "tools/qfcc/include/pragma.h"
|
||||||
|
#include "tools/qfcc/include/strpool.h"
|
||||||
|
#include "tools/qfcc/include/type.h"
|
||||||
|
|
||||||
|
#define YYDEBUG 1
|
||||||
|
#define YYERROR_VERBOSE 1
|
||||||
|
#undef YYERROR_VERBOSE
|
||||||
|
|
||||||
|
#include "tools/qfcc/source/pre-parse.h"
|
||||||
|
|
||||||
|
#define pre_yytext qc_yyget_text (scanner)
|
||||||
|
char *qc_yyget_text (void *scanner);
|
||||||
|
|
||||||
|
static void
|
||||||
|
yyerror (YYLTYPE *yylloc, void *scanner, const char *s)
|
||||||
|
{
|
||||||
|
const char *text = quote_string (pre_yytext);
|
||||||
|
#ifdef YYERROR_VERBOSE
|
||||||
|
error (0, "%d:%s before '%s'\n", yylloc->first_column, s, text);
|
||||||
|
#else
|
||||||
|
error (0, "%s before '%s'", s, text);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
static void
|
||||||
|
parse_error (void *scanner)
|
||||||
|
{
|
||||||
|
const char *text = pre_yytext;
|
||||||
|
error (0, "parse error before %s", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PARSE_ERROR do { parse_error (scanner); YYERROR; } while (0)
|
||||||
|
#endif
|
||||||
|
%}
|
||||||
|
|
||||||
|
%code requires {
|
||||||
|
#include "tools/qfcc/include/rua-lang.h"
|
||||||
|
}
|
||||||
|
%define api.value.type {rua_tok_t}
|
||||||
|
%define api.location.type {rua_loc_t}
|
||||||
|
|
||||||
|
%left LOW
|
||||||
|
%nonassoc IFX
|
||||||
|
%nonassoc ELSE
|
||||||
|
%nonassoc BREAK_PRIMARY
|
||||||
|
%nonassoc ';'
|
||||||
|
%nonassoc CLASS_NOT_CATEGORY
|
||||||
|
%nonassoc STORAGEX
|
||||||
|
|
||||||
|
%left COMMA
|
||||||
|
%right <op> '=' ASX
|
||||||
|
%right '?' ':'
|
||||||
|
%left OR
|
||||||
|
%left AND
|
||||||
|
%left '|'
|
||||||
|
%left '^'
|
||||||
|
%left '&'
|
||||||
|
%left EQ NE
|
||||||
|
%left LT GT GE LE
|
||||||
|
%token NAND NOR XNOR
|
||||||
|
// end of tokens common between qc and qp
|
||||||
|
|
||||||
|
%left SHL SHR
|
||||||
|
%left '+' '-'
|
||||||
|
%left '*' '/' '%' MOD SCALE GEOMETRIC
|
||||||
|
%left HADAMARD CROSS DOT WEDGE REGRESSIVE
|
||||||
|
%right <op> SIZEOF UNARY INCOP REVERSE STAR DUAL
|
||||||
|
%left HYPERUNARY
|
||||||
|
%left '.' '(' '['
|
||||||
|
|
||||||
|
%token <value.expr> VALUE STRING
|
||||||
|
%token <location> TOKEN
|
||||||
|
// end of tokens comment between qc and preprocessor
|
||||||
|
|
||||||
|
%token QSTRING HSTRING
|
||||||
|
|
||||||
|
%token INCLUDE EMBED
|
||||||
|
%token DEFINE UNDEF
|
||||||
|
%token <text> TEXT ID IDp
|
||||||
|
%token ERROR WARNING
|
||||||
|
%token PRAGMA LINE
|
||||||
|
%token IF IFDEF IFNDEF ELSE ELIF ELIFDEF ELIFNDEF ENDIF
|
||||||
|
%token DEFINED EOD
|
||||||
|
%token CONCAT
|
||||||
|
|
||||||
|
%type <macro> body body_text
|
||||||
|
%type <dstr> text text_text
|
||||||
|
%type <value.expr> unary_expr expr id
|
||||||
|
|
||||||
|
%{
|
||||||
|
#define BEXPR(a,op,b) new_long_expr (expr_long (a) op expr_long (b), false)
|
||||||
|
#define UEXPR(op,a) new_long_expr (op expr_long (a), false)
|
||||||
|
%}
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
start : directive_list
|
||||||
|
|
||||||
|
directive_list
|
||||||
|
: /*empty*/
|
||||||
|
| directive EOD { rua_end_directive (scanner); } directive_list
|
||||||
|
;
|
||||||
|
|
||||||
|
directive
|
||||||
|
: INCLUDE expand string extra_warn
|
||||||
|
| EMBED expand string extra_ignore
|
||||||
|
| DEFINE ID body
|
||||||
|
| DEFINE IDp params ')' body { rua_macro_finish ($body, scanner); }
|
||||||
|
| UNDEF ID extra_warn
|
||||||
|
| ERROR text { error (0, "%s", $text->str); dstring_delete ($text); }
|
||||||
|
| WARNING text { warning (0, "%s", $text->str); dstring_delete ($text); }
|
||||||
|
| PRAGMA expand pragma_params { pragma_process (); }
|
||||||
|
| LINE expand expr QSTRING extra_warn
|
||||||
|
| IF expand expr { rua_if (expr_long ($3), scanner); }
|
||||||
|
| IFDEF ID extra_warn
|
||||||
|
| IFNDEF ID extra_warn
|
||||||
|
| ELSE extra_warn { rua_else (true, "else", scanner); }
|
||||||
|
| ELIF expand expr { rua_else (expr_long ($3), "elif", scanner); }
|
||||||
|
| ELIFDEF ID extra_warn
|
||||||
|
| ELIFNDEF ID extra_warn
|
||||||
|
| ENDIF extra_warn { rua_endif (scanner); }
|
||||||
|
;
|
||||||
|
|
||||||
|
extra_warn
|
||||||
|
: {}
|
||||||
|
;
|
||||||
|
|
||||||
|
extra_ignore
|
||||||
|
: {}
|
||||||
|
;
|
||||||
|
|
||||||
|
text: { rua_start_text (scanner); $<dstr>$ = dstring_new (); } text_text
|
||||||
|
{ $text = $text_text; }
|
||||||
|
;
|
||||||
|
text_text
|
||||||
|
: TEXT { dstring_appendstr ($<dstr>0, $1); $$ = $<dstr>0; }
|
||||||
|
| text_text TEXT { dstring_appendstr ($1, $2); $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
body: <macro>{ $$ = rua_start_macro (scanner); } body_text
|
||||||
|
{ $body = $body_text; }
|
||||||
|
;
|
||||||
|
body_text
|
||||||
|
: TOKEN { $$ = rua_macro_append ($<macro>0, yyvsp, scanner); }
|
||||||
|
| body_text TOKEN { $$ = rua_macro_append ($1, yyvsp, scanner); }
|
||||||
|
;
|
||||||
|
|
||||||
|
expand
|
||||||
|
: { rua_start_expr (scanner); }
|
||||||
|
;
|
||||||
|
|
||||||
|
pragma_params
|
||||||
|
: ID { pragma_add_arg (pre_yytext); }
|
||||||
|
| pragma_params ID { pragma_add_arg (pre_yytext); }
|
||||||
|
;
|
||||||
|
|
||||||
|
string
|
||||||
|
: HSTRING
|
||||||
|
| QSTRING
|
||||||
|
;
|
||||||
|
|
||||||
|
params
|
||||||
|
: ID
|
||||||
|
| params ',' ID
|
||||||
|
;
|
||||||
|
|
||||||
|
args: arg
|
||||||
|
| args ',' arg
|
||||||
|
;
|
||||||
|
|
||||||
|
arg : /* empty */
|
||||||
|
| expr
|
||||||
|
;
|
||||||
|
|
||||||
|
id : ID {$$ = 0; }
|
||||||
|
| IDp args ')' {$$ = 0; }
|
||||||
|
;
|
||||||
|
|
||||||
|
unary_expr
|
||||||
|
: id
|
||||||
|
| VALUE
|
||||||
|
{
|
||||||
|
auto type = get_type ($1);
|
||||||
|
if (!is_long (type)) {
|
||||||
|
if (is_double (type)) {
|
||||||
|
error (0, "floating constant in preprocessor expression");
|
||||||
|
$1 = new_long_expr (expr_double ($1), false);
|
||||||
|
} else {
|
||||||
|
error (0, "token \"%s\" is not valid in preprocessor"
|
||||||
|
" expressions", $<text>1);
|
||||||
|
$1 = new_long_expr (1, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$$ = $1;
|
||||||
|
}
|
||||||
|
| QSTRING
|
||||||
|
{
|
||||||
|
error (0, "token \"%s\" is not valid in preprocessor"
|
||||||
|
" expressions", $<text>1);
|
||||||
|
$$ = new_long_expr (1, false);
|
||||||
|
}
|
||||||
|
| '(' expr ')' { $$ = $2; }
|
||||||
|
| DEFINED { $$ = new_long_expr (0, false); /*FIXME*/ }
|
||||||
|
| '+' unary_expr { $$ = $2; }
|
||||||
|
| '-' unary_expr { $$ = UEXPR (-, $2); }
|
||||||
|
| '~' unary_expr { $$ = UEXPR (~, $2); }
|
||||||
|
| '!' unary_expr { $$ = UEXPR (!, $2); }
|
||||||
|
;
|
||||||
|
|
||||||
|
expr
|
||||||
|
: unary_expr { $$ = $1; }
|
||||||
|
| expr AND expr { $$ = BEXPR ($1, &&, $3); }
|
||||||
|
| expr OR expr { $$ = BEXPR ($1, ||, $3); }
|
||||||
|
| expr EQ expr { $$ = BEXPR ($1, ==, $3); }
|
||||||
|
| expr NE expr { $$ = BEXPR ($1, !=, $3); }
|
||||||
|
| expr LE expr { $$ = BEXPR ($1, <=, $3); }
|
||||||
|
| expr GE expr { $$ = BEXPR ($1, >=, $3); }
|
||||||
|
| expr LT expr { $$ = BEXPR ($1, < , $3); }
|
||||||
|
| expr GT expr { $$ = BEXPR ($1, > , $3); }
|
||||||
|
| expr SHL expr { $$ = BEXPR ($1, <<, $3); }
|
||||||
|
| expr SHR expr { $$ = BEXPR ($1, >>, $3); }
|
||||||
|
| expr '+' expr { $$ = BEXPR ($1, + , $3); }
|
||||||
|
| expr '-' expr { $$ = BEXPR ($1, - , $3); }
|
||||||
|
| expr '*' expr { $$ = BEXPR ($1, * , $3); }
|
||||||
|
| expr '/' expr { $$ = BEXPR ($1, / , $3); }
|
||||||
|
| expr '&' expr { $$ = BEXPR ($1, & , $3); }
|
||||||
|
| expr '|' expr { $$ = BEXPR ($1, | , $3); }
|
||||||
|
| expr '^' expr { $$ = BEXPR ($1, ^ , $3); }
|
||||||
|
| expr '%' expr { $$ = BEXPR ($1, % , $3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
|
@ -85,8 +85,22 @@
|
||||||
#define YYSTYPE rua_tok_t
|
#define YYSTYPE rua_tok_t
|
||||||
#define YYLTYPE rua_loc_t
|
#define YYLTYPE rua_loc_t
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool saw_true;
|
||||||
|
bool saw_else;
|
||||||
|
bool own_state;
|
||||||
|
bool enabled;
|
||||||
|
} rua_cond_t;
|
||||||
|
|
||||||
|
typedef struct DARRAY_TYPE (rua_cond_t) rua_cond_stack_t;
|
||||||
|
|
||||||
typedef struct rua_extra_s {
|
typedef struct rua_extra_s {
|
||||||
int start_state;
|
int start_state;
|
||||||
|
bool preprocessor;
|
||||||
|
bool recording;
|
||||||
|
qc_yypstate *qc_state;
|
||||||
|
pre_yypstate *pre_state;
|
||||||
|
rua_cond_stack_t cond_stack;
|
||||||
} rua_extra_t;
|
} rua_extra_t;
|
||||||
#define YY_EXTRA_TYPE rua_extra_t *
|
#define YY_EXTRA_TYPE rua_extra_t *
|
||||||
|
|
||||||
|
@ -101,21 +115,25 @@ int yyget_debug (yyscan_t yyscanner) __attribute__((pure));
|
||||||
FILE *yyget_in (yyscan_t yyscanner) __attribute__((pure));
|
FILE *yyget_in (yyscan_t yyscanner) __attribute__((pure));
|
||||||
FILE *yyget_out (yyscan_t yyscanner) __attribute__((pure));
|
FILE *yyget_out (yyscan_t yyscanner) __attribute__((pure));
|
||||||
|
|
||||||
|
static int directive (const char *token, yyscan_t scanner);
|
||||||
static int keyword_or_id (YYSTYPE *lval, const char *token);
|
static int keyword_or_id (YYSTYPE *lval, const char *token);
|
||||||
static void user_action (rua_tok_t *tok, rua_loc_t *loc,
|
static void user_action (rua_tok_t *tok, rua_loc_t *loc,
|
||||||
const char *text, size_t textlen, yyscan_t scanner);
|
const char *text, size_t textlen, int state);
|
||||||
|
static void undo_loc (rua_loc_t *loc);
|
||||||
static void next_line (rua_loc_t *loc);
|
static void next_line (rua_loc_t *loc);
|
||||||
|
|
||||||
enum {
|
typedef enum {
|
||||||
rua_eof = 1,
|
rua_eof = 1,
|
||||||
rua_error,
|
rua_error,
|
||||||
rua_number,
|
rua_number,
|
||||||
rua_vector,
|
rua_vector,
|
||||||
rua_string,
|
rua_string,
|
||||||
rua_char,
|
rua_char,
|
||||||
};
|
|
||||||
|
|
||||||
#define YY_USER_ACTION user_action (yylval, yylloc, yytext, yyleng, yyscanner);
|
rua_num_term,
|
||||||
|
} rua_term;
|
||||||
|
|
||||||
|
#define YY_USER_ACTION user_action (yylval, yylloc, yytext, yyleng, yystart());
|
||||||
|
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
@ -134,21 +152,26 @@ FRAMEID {ID}(\.{ID})*
|
||||||
PRAGMAID {ID}(-{ID})*
|
PRAGMAID {ID}(-{ID})*
|
||||||
s_string \"(\\.|[^"\\])*\"
|
s_string \"(\\.|[^"\\])*\"
|
||||||
c_string '(\\.|[^'\\])*'
|
c_string '(\\.|[^'\\])*'
|
||||||
|
h_string <[^>]*>
|
||||||
|
q_string \"[^\"]*\"
|
||||||
|
|
||||||
pp_number \.?{D}({IDc}|'{IDc}|[eEpP]{m}|\.)*
|
pp_number \.?{D}({IDc}|'{IDc}|[eEpP]{m}|\.)*
|
||||||
pp_vnumber '({s}*{m}?{pp_number}){2,4}{s}*'{ULFD}?
|
pp_vnumber '({s}*{m}?{pp_number}){2,4}{s}*'{ULFD}?
|
||||||
|
|
||||||
%x GRAB_FRAME GRAB_OTHER GRAB_WRITE COMMENT LCOMMENT PRAGMA
|
%x GRAB_FRAME GRAB_OTHER GRAB_WRITE
|
||||||
|
%x COMMENT LCOMMENT
|
||||||
|
%x BOL DIRECTIVE TEXT MACRO SUPPRESS SUPPRESSC PRAGMA
|
||||||
%x VECTOR
|
%x VECTOR
|
||||||
|
%s PREPROC PREEXPR
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
auto extra = qc_yyget_extra (yyscanner);
|
||||||
yyset_debug (0, yyscanner);
|
yyset_debug (0, yyscanner);
|
||||||
grab_frame = GRAB_FRAME;
|
grab_frame = GRAB_FRAME;
|
||||||
grab_other = GRAB_OTHER;
|
grab_other = GRAB_OTHER;
|
||||||
grab_write = GRAB_WRITE;
|
grab_write = GRAB_WRITE;
|
||||||
yylval->pointer = 0; // ensure pointer vals are null
|
yylval->pointer = 0; // ensure pointer vals are null
|
||||||
|
|
||||||
"/*" { yy_push_state (COMMENT, yyscanner); }
|
|
||||||
<COMMENT>"/*" { warning (0, "nested /* in comment"); }
|
<COMMENT>"/*" { warning (0, "nested /* in comment"); }
|
||||||
<COMMENT>\*+"/" { yy_pop_state (yyscanner); }
|
<COMMENT>\*+"/" { yy_pop_state (yyscanner); }
|
||||||
<COMMENT>\n { next_line (yylloc); }
|
<COMMENT>\n { next_line (yylloc); }
|
||||||
|
@ -156,10 +179,50 @@ pp_vnumber '({s}*{m}?{pp_number}){2,4}{s}*'{ULFD}?
|
||||||
<COMMENT>\/+[^*\n]* /* handle /s not followed by * */
|
<COMMENT>\/+[^*\n]* /* handle /s not followed by * */
|
||||||
<COMMENT>\*+[^/\n]* /* handle *s not followed by * */
|
<COMMENT>\*+[^/\n]* /* handle *s not followed by * */
|
||||||
<COMMENT><<EOF>> { error (0, "EOF in comment"); return 0; }
|
<COMMENT><<EOF>> { error (0, "EOF in comment"); return 0; }
|
||||||
"//" { yy_push_state (LCOMMENT, yyscanner); }/* cf <*>\r\n */
|
<*>"/*" { yy_push_state (COMMENT, yyscanner); }
|
||||||
|
<*>"//" { yy_push_state (LCOMMENT, yyscanner); }/* cf <*>\r\n */
|
||||||
<LCOMMENT>[^\\\r\n]* /* consume all but \ and EOL (see line continuation) */
|
<LCOMMENT>[^\\\r\n]* /* consume all but \ and EOL (see line continuation) */
|
||||||
<LCOMMENT>[\\]* /* consume \ */
|
<LCOMMENT>[\\]* /* consume \ */
|
||||||
|
|
||||||
|
<*>^#{s}* {
|
||||||
|
yy_push_state (DIRECTIVE, yyscanner);
|
||||||
|
extra->preprocessor = true;
|
||||||
|
}
|
||||||
|
<*>^{s}* { yy_push_state (BOL, yyscanner); }
|
||||||
|
<BOL>#{s}* { BEGIN (DIRECTIVE); extra->preprocessor = true; }
|
||||||
|
<BOL>. {
|
||||||
|
yy_pop_state (yyscanner);
|
||||||
|
undo_loc (yylloc);
|
||||||
|
yyless (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
<DIRECTIVE>{ID} { {
|
||||||
|
int tok = directive (yytext, yyscanner);
|
||||||
|
if (tok >= 0) {
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
<PREPROC,MACRO>{ID} { return PRE_ID; }
|
||||||
|
<PREPROC>{ID}\( { return PRE_IDp; }
|
||||||
|
|
||||||
|
<DIRECTIVE>\r*\n |
|
||||||
|
<PREEXPR>\r*\n |
|
||||||
|
<MACRO,TEXT>\r*\n |
|
||||||
|
<PREPROC>\r*\n { next_line (yylloc); return PRE_EOD; }
|
||||||
|
|
||||||
|
<SUPPRESS>^[^#\n \t]+ { BEGIN (SUPPRESSC); }
|
||||||
|
<SUPPRESSC>.* /* nom nom */
|
||||||
|
<SUPPRESSC>\n { next_line (yylloc); BEGIN (SUPPRESS); }
|
||||||
|
<SUPPRESS>\n { next_line (yylloc); }
|
||||||
|
<SUPPRESS><<EOF>> { error (0, "unterminated #if"); return 0; }
|
||||||
|
|
||||||
|
<TEXT>[^\\\r\n]* { return PRE_TEXT; }
|
||||||
|
<TEXT>[\\]* { return PRE_TEXT; }
|
||||||
|
<MACRO>## { return PRE_CONCAT; }
|
||||||
|
<MACRO># { return '#'; }
|
||||||
|
<PREEXPR>{h_string} { return PRE_HSTRING; }
|
||||||
|
<PREEXPR>{q_string} { return PRE_QSTRING; }
|
||||||
|
|
||||||
^#{s}+{D}+{s}+\"(\.|[^"\n])*\".*$ { line_info (yytext + 1); }
|
^#{s}+{D}+{s}+\"(\.|[^"\n])*\".*$ { line_info (yytext + 1); }
|
||||||
^#line{s}+{D}+{s}+\"(\.|[^"\n])*\".*$ { line_info (yytext + 5); }
|
^#line{s}+{D}+{s}+\"(\.|[^"\n])*\".*$ { line_info (yytext + 5); }
|
||||||
|
|
||||||
|
@ -248,16 +311,97 @@ pp_vnumber '({s}*{m}?{pp_number}){2,4}{s}*'{ULFD}?
|
||||||
|
|
||||||
<*>{s}+ /* skip */
|
<*>{s}+ /* skip */
|
||||||
|
|
||||||
<*>. error (0, "all your typo are belong to us");
|
<*>. {
|
||||||
|
error (0, "all your typo are belong to us:%d %d-%d",
|
||||||
|
yystart(),
|
||||||
|
yylloc->first_column, yylloc->last_column);
|
||||||
|
}
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
|
#define ARRCOUNT(_k) (sizeof (_k) / sizeof (_k[0]))
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
int value;
|
int value;
|
||||||
specifier_t spec;
|
specifier_t spec;
|
||||||
} keyword_t;
|
} keyword_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
int value;
|
||||||
|
} directive_t;
|
||||||
|
|
||||||
|
// preprocessor directives in ruamoko and quakec
|
||||||
|
static directive_t rua_directives[] = {
|
||||||
|
{"include", PRE_INCLUDE},
|
||||||
|
{"embed", PRE_EMBED},
|
||||||
|
{"define", PRE_DEFINE},
|
||||||
|
{"undef", PRE_UNDEF},
|
||||||
|
{"error", PRE_ERROR},
|
||||||
|
{"warning", PRE_WARNING},
|
||||||
|
{"pragma", PRE_PRAGMA},
|
||||||
|
{"line", PRE_LINE},
|
||||||
|
};
|
||||||
|
|
||||||
|
static directive_t cond_directives[] = {
|
||||||
|
{"if", PRE_IF},
|
||||||
|
{"ifdef", PRE_IFDEF},
|
||||||
|
{"ifndef", PRE_IFNDEF},
|
||||||
|
{"else", PRE_ELSE},
|
||||||
|
{"elif", PRE_ELIF},
|
||||||
|
{"elifdef", PRE_ELIFDEF},
|
||||||
|
{"elifndef", PRE_ELIFNDEF},
|
||||||
|
{"endif", PRE_ENDIF},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
directive_get_key (const void *dir, void *unused)
|
||||||
|
{
|
||||||
|
return ((directive_t*)dir)->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
directive (const char *token, yyscan_t scanner)
|
||||||
|
{
|
||||||
|
static hashtab_t *rua_directive_tab;
|
||||||
|
static hashtab_t *cond_directive_tab;
|
||||||
|
|
||||||
|
yy_pop_state (scanner); // pop DIRECTIVE off the stack
|
||||||
|
yy_push_state (PREPROC, scanner);
|
||||||
|
|
||||||
|
if (!rua_directive_tab) {
|
||||||
|
rua_directive_tab = Hash_NewTable (253, directive_get_key, 0, 0, 0);
|
||||||
|
cond_directive_tab = Hash_NewTable (253, directive_get_key, 0, 0, 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRCOUNT(rua_directives); i++) {
|
||||||
|
Hash_Add (rua_directive_tab, &rua_directives[i]);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < ARRCOUNT(cond_directives); i++) {
|
||||||
|
Hash_Add (cond_directive_tab, &cond_directives[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
directive_t *directive = Hash_Find (cond_directive_tab, token);
|
||||||
|
if (!directive && yy_top_state (scanner) != SUPPRESS) {
|
||||||
|
directive = Hash_Find (rua_directive_tab, token);
|
||||||
|
if (!directive) {
|
||||||
|
error (0, "invalid directive `%s`", token);
|
||||||
|
yy_pop_state (scanner);
|
||||||
|
yy_push_state (LCOMMENT, scanner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!directive) {
|
||||||
|
if (yy_top_state (scanner) == SUPPRESS) {
|
||||||
|
yy_pop_state (scanner);// suppressed non-conditional directive
|
||||||
|
yy_push_state (LCOMMENT, scanner);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
//auto lex = qc_yyget_extra (scanner);
|
||||||
|
//lex->current_lang = &lex->pre_lang;
|
||||||
|
return directive->value;
|
||||||
|
}
|
||||||
|
|
||||||
// These keywords are part of the Ruamoko language and require the QuakeForge
|
// These keywords are part of the Ruamoko language and require the QuakeForge
|
||||||
// Ruamoko VM.
|
// Ruamoko VM.
|
||||||
static keyword_t rua_keywords[] = {
|
static keyword_t rua_keywords[] = {
|
||||||
|
@ -435,17 +579,15 @@ keyword_or_id (YYSTYPE *lval, const char *token)
|
||||||
obj_keyword_tab = Hash_NewTable (253, keyword_get_key, 0, 0, 0);
|
obj_keyword_tab = Hash_NewTable (253, keyword_get_key, 0, 0, 0);
|
||||||
rua_keyword_tab = Hash_NewTable (253, keyword_get_key, 0, 0, 0);
|
rua_keyword_tab = Hash_NewTable (253, keyword_get_key, 0, 0, 0);
|
||||||
|
|
||||||
#define NUMKEYS(_k) (sizeof (_k) / sizeof (_k[0]))
|
for (i = 0; i < ARRCOUNT(keywords); i++)
|
||||||
|
|
||||||
for (i = 0; i < NUMKEYS(keywords); i++)
|
|
||||||
Hash_Add (keyword_tab, &keywords[i]);
|
Hash_Add (keyword_tab, &keywords[i]);
|
||||||
for (i = 0; i < NUMKEYS(qf_keywords); i++)
|
for (i = 0; i < ARRCOUNT(qf_keywords); i++)
|
||||||
Hash_Add (qf_keyword_tab, &qf_keywords[i]);
|
Hash_Add (qf_keyword_tab, &qf_keywords[i]);
|
||||||
for (i = 0; i < NUMKEYS(at_keywords); i++)
|
for (i = 0; i < ARRCOUNT(at_keywords); i++)
|
||||||
Hash_Add (at_keyword_tab, &at_keywords[i]);
|
Hash_Add (at_keyword_tab, &at_keywords[i]);
|
||||||
for (i = 0; i < NUMKEYS(obj_keywords); i++)
|
for (i = 0; i < ARRCOUNT(obj_keywords); i++)
|
||||||
Hash_Add (obj_keyword_tab, &obj_keywords[i]);
|
Hash_Add (obj_keyword_tab, &obj_keywords[i]);
|
||||||
for (i = 0; i < NUMKEYS(rua_keywords); i++)
|
for (i = 0; i < ARRCOUNT(rua_keywords); i++)
|
||||||
Hash_Add (rua_keyword_tab, &rua_keywords[i]);
|
Hash_Add (rua_keyword_tab, &rua_keywords[i]);
|
||||||
}
|
}
|
||||||
if (options.traditional < 1) {
|
if (options.traditional < 1) {
|
||||||
|
@ -867,11 +1009,18 @@ next_line (rua_loc_t *loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
user_action (rua_tok_t *tok, rua_loc_t *loc, const char *text, size_t textlen,
|
undo_loc (rua_loc_t *loc)
|
||||||
yyscan_t scanner)
|
|
||||||
{
|
{
|
||||||
int state = yy_top_state (scanner);
|
// the next call to update_yylloc will sort out first_line and first_column
|
||||||
if (state != COMMENT) {
|
loc->last_line = loc->first_line;
|
||||||
|
loc->last_column = loc->first_column;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
user_action (rua_tok_t *tok, rua_loc_t *loc, const char *text, size_t textlen,
|
||||||
|
int state)
|
||||||
|
{
|
||||||
|
if (state != COMMENT && state != SUPPRESS) {
|
||||||
if (textlen < sizeof (tok->text)) {
|
if (textlen < sizeof (tok->text)) {
|
||||||
strncpy (tok->str_text, text, textlen);
|
strncpy (tok->str_text, text, textlen);
|
||||||
tok->str_text[textlen] = 0;
|
tok->str_text[textlen] = 0;
|
||||||
|
@ -885,18 +1034,31 @@ user_action (rua_tok_t *tok, rua_loc_t *loc, const char *text, size_t textlen,
|
||||||
loc->first_column = loc->last_column;
|
loc->first_column = loc->last_column;
|
||||||
// \n handling rules will take care of the column and line
|
// \n handling rules will take care of the column and line
|
||||||
loc->last_column += textlen;
|
loc->last_column += textlen;
|
||||||
|
#if 0
|
||||||
|
printf ("start: %2d [%3d %3d] [%3d %3d] '%s'\n", state,
|
||||||
|
loc->first_line, loc->first_column,
|
||||||
|
loc->last_line, loc->last_column,
|
||||||
|
quote_string (text));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
qc_process (qc_yypstate *ps, int token, rua_tok_t *tok, yyscan_t scanner)
|
qc_process (rua_extra_t *extra, int token, rua_tok_t *tok, yyscan_t scanner)
|
||||||
{
|
{
|
||||||
auto value = &tok->value;
|
auto value = &tok->value;
|
||||||
auto loc = &tok->location;
|
auto loc = &tok->location;
|
||||||
|
|
||||||
switch (-token) {
|
if (token < 0) {
|
||||||
|
rua_term term = -token;
|
||||||
|
switch (term) {
|
||||||
|
case rua_eof:
|
||||||
|
case rua_error:
|
||||||
|
case rua_num_term:
|
||||||
|
break;
|
||||||
case rua_number:
|
case rua_number:
|
||||||
token = parse_number (tok, scanner);
|
token = parse_number (tok, scanner);
|
||||||
if (token == VALUE && value->expr->implicit) {
|
if (!extra->preprocessor
|
||||||
|
&& token == VALUE && value->expr->implicit) {
|
||||||
if (is_long (get_type (value->expr))) {
|
if (is_long (get_type (value->expr))) {
|
||||||
pr_long_t v = expr_long (value->expr);
|
pr_long_t v = expr_long (value->expr);
|
||||||
if (v < INT32_MIN || v > INT32_MAX) {
|
if (v < INT32_MIN || v > INT32_MAX) {
|
||||||
|
@ -914,8 +1076,20 @@ qc_process (qc_yypstate *ps, int token, rua_tok_t *tok, yyscan_t scanner)
|
||||||
token = parse_string (tok, -token, scanner);
|
token = parse_string (tok, -token, scanner);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (token >= 0) {
|
if (token >= 0) {
|
||||||
return qc_yypush_parse (ps, token, value, loc, scanner);
|
//printf ("%d %d %s\n", token, extra->preprocessor, tok->text);
|
||||||
|
if (extra->preprocessor) {
|
||||||
|
auto state = extra->pre_state;
|
||||||
|
if (extra->recording && token != PRE_EOD) {
|
||||||
|
tok->token = token;
|
||||||
|
token = PRE_TOKEN;
|
||||||
|
}
|
||||||
|
return pre_yypush_parse (state, token, tok, loc, scanner);
|
||||||
|
} else {
|
||||||
|
auto state = extra->qc_state;
|
||||||
|
return qc_yypush_parse (state, token, value, loc, scanner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return YYPUSH_MORE;
|
return YYPUSH_MORE;
|
||||||
}
|
}
|
||||||
|
@ -925,18 +1099,147 @@ qc_yyparse (FILE *in)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
yyscan_t scanner;
|
yyscan_t scanner;
|
||||||
qc_yypstate *ps = qc_yypstate_new ();
|
|
||||||
rua_tok_t tok = { .location = { 1, 1, 1, 1, pr.source_file }, };
|
rua_tok_t tok = { .location = { 1, 1, 1, 1, pr.source_file }, };
|
||||||
rua_extra_t extra = {};
|
rua_extra_t extra = {
|
||||||
|
.qc_state = qc_yypstate_new (),
|
||||||
|
.pre_state = pre_yypstate_new (),
|
||||||
|
.cond_stack = DARRAY_STATIC_INIT (8),
|
||||||
|
};
|
||||||
|
|
||||||
yylex_init_extra (&extra, &scanner);
|
yylex_init_extra (&extra, &scanner);
|
||||||
yyset_in (in, scanner);
|
yyset_in (in, scanner);
|
||||||
do {
|
do {
|
||||||
int token = yylex (&tok, &tok.location, scanner);
|
int token = yylex (&tok, &tok.location, scanner);
|
||||||
status = qc_process (ps, token, &tok, scanner);
|
status = qc_process (&extra, token, &tok, scanner);
|
||||||
} while (status == YYPUSH_MORE);
|
} while (status == YYPUSH_MORE);
|
||||||
|
|
||||||
yylex_destroy (scanner);
|
yylex_destroy (scanner);
|
||||||
qc_yypstate_delete (ps);
|
qc_yypstate_delete (extra.qc_state);
|
||||||
|
pre_yypstate_delete (extra.pre_state);
|
||||||
|
free (extra.cond_stack.a);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rua_macro_t *
|
||||||
|
rua_start_macro (void *scanner)
|
||||||
|
{
|
||||||
|
auto extra = qc_yyget_extra (scanner);
|
||||||
|
extra->recording = true;
|
||||||
|
|
||||||
|
yy_pop_state (scanner);
|
||||||
|
yy_push_state (MACRO, scanner);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rua_macro_t *
|
||||||
|
rua_macro_append (rua_macro_t *macro, rua_tok_t *token, void *scanner)
|
||||||
|
{
|
||||||
|
return macro;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rua_macro_finish (rua_macro_t *macro, void *scanner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rua_start_text (void *scanner)
|
||||||
|
{
|
||||||
|
yy_pop_state (scanner);
|
||||||
|
yy_push_state (TEXT, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rua_start_expr (void *scanner)
|
||||||
|
{
|
||||||
|
yy_pop_state (scanner);
|
||||||
|
yy_push_state (PREEXPR, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rua_end_directive (void *scanner)
|
||||||
|
{
|
||||||
|
auto extra = qc_yyget_extra (scanner);
|
||||||
|
extra->preprocessor = false;
|
||||||
|
extra->recording = false;
|
||||||
|
yy_pop_state (scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dump_state_stack (void *scanner)
|
||||||
|
{
|
||||||
|
auto yyg = (struct yyguts_t *)scanner;
|
||||||
|
for (int i = 0; i < yyg->yy_start_stack_ptr; i++) {
|
||||||
|
printf ("%d ", yyg->yy_start_stack[i]);
|
||||||
|
}
|
||||||
|
printf (": %d\n", yystart ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rua_if (bool pass, void *scanner)
|
||||||
|
{
|
||||||
|
auto extra = qc_yyget_extra (scanner);
|
||||||
|
rua_cond_t cond = {
|
||||||
|
.saw_true = pass,
|
||||||
|
.saw_else = false,
|
||||||
|
.own_state = true,
|
||||||
|
.enabled = pass,
|
||||||
|
};
|
||||||
|
if (extra->cond_stack.size) {
|
||||||
|
auto c = extra->cond_stack.a[extra->cond_stack.size - 1];
|
||||||
|
cond.own_state = c.own_state & c.enabled;
|
||||||
|
}
|
||||||
|
DARRAY_APPEND (&extra->cond_stack, cond);
|
||||||
|
yy_pop_state (scanner); // remove DIRECTIVE/PREEXPR state
|
||||||
|
if (cond.own_state && !cond.enabled) {
|
||||||
|
yy_push_state (SUPPRESS, scanner);
|
||||||
|
}
|
||||||
|
// put PREEXPR on the stack for EOD to pop
|
||||||
|
yy_push_state (PREEXPR, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rua_else (bool pass, const char *tok, void *scanner)
|
||||||
|
{
|
||||||
|
auto extra = qc_yyget_extra (scanner);
|
||||||
|
if (!extra->cond_stack.size) {
|
||||||
|
error (0, "#else without #if");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yy_pop_state (scanner); // remove DIRECTIVE state
|
||||||
|
auto cond = &extra->cond_stack.a[extra->cond_stack.size - 1];
|
||||||
|
if (cond->saw_else) {
|
||||||
|
error (0, "#%s after #else", tok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cond->own_state && !cond->enabled) {
|
||||||
|
yy_pop_state (scanner);
|
||||||
|
}
|
||||||
|
pass &= !cond->saw_true;
|
||||||
|
cond->enabled = pass;
|
||||||
|
cond->saw_true |= pass;
|
||||||
|
cond->saw_else = strcmp (tok, "else") == 0;
|
||||||
|
if (cond->own_state && !cond->enabled) {
|
||||||
|
yy_push_state (SUPPRESS, scanner);
|
||||||
|
}
|
||||||
|
// put PREEXPR on the stack for EOD to pop
|
||||||
|
yy_push_state (PREEXPR, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rua_endif (void *scanner)
|
||||||
|
{
|
||||||
|
auto extra = qc_yyget_extra (scanner);
|
||||||
|
if (!extra->cond_stack.size) {
|
||||||
|
error (0, "#endif without #if");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (0) dump_state_stack (scanner);
|
||||||
|
yy_pop_state (scanner); // remove DIRECTIVE state
|
||||||
|
auto cond = DARRAY_REMOVE (&extra->cond_stack);
|
||||||
|
if (cond.own_state && !cond.enabled) {
|
||||||
|
yy_pop_state (scanner);
|
||||||
|
}
|
||||||
|
// put PREEXPR on the stack for EOD to pop
|
||||||
|
yy_push_state (PREEXPR, scanner);
|
||||||
|
}
|
||||||
|
|
|
@ -163,8 +163,10 @@ int yylex (YYSTYPE *yylval, YYLTYPE *yylloc);
|
||||||
%left HYPERUNARY
|
%left HYPERUNARY
|
||||||
%left '.' '(' '['
|
%left '.' '(' '['
|
||||||
|
|
||||||
|
%token <expr> VALUE STRING TOKEN
|
||||||
|
// end of tokens common between qc and qp
|
||||||
|
|
||||||
%token <symbol> CLASS_NAME NAME
|
%token <symbol> CLASS_NAME NAME
|
||||||
%token <expr> VALUE STRING
|
|
||||||
|
|
||||||
%token LOCAL WHILE DO IF ELSE FOR BREAK CONTINUE
|
%token LOCAL WHILE DO IF ELSE FOR BREAK CONTINUE
|
||||||
%token RETURN AT_RETURN ELLIPSIS
|
%token RETURN AT_RETURN ELLIPSIS
|
||||||
|
|
Loading…
Reference in a new issue