mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-04 09:56:30 +00:00
884 lines
31 KiB
Plaintext
884 lines
31 KiB
Plaintext
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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/>.
|
|
*
|
|
*/
|
|
|
|
// Heavily inspired by hoc
|
|
// Copyright (C) AT&T 1995
|
|
// All Rights Reserved
|
|
//
|
|
// Permission to use, copy, modify, and distribute this software and
|
|
// its documentation for any purpose and without fee is hereby
|
|
// granted, provided that the above copyright notice appear in all
|
|
// copies and that both that the copyright notice and this
|
|
// permission notice and warranty disclaimer appear in supporting
|
|
// documentation, and that the name of AT&T or any of its entities
|
|
// not be used in advertising or publicity pertaining to
|
|
// distribution of the software without specific, written prior
|
|
// permission.
|
|
//
|
|
// AT&T DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
// INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
|
|
// IN NO EVENT SHALL AT&T OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
|
|
// SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
|
// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
// THIS SOFTWARE.
|
|
|
|
%require "3.6"
|
|
%defines "engines/director/lingo/lingo-gr.h"
|
|
%output "engines/director/lingo/lingo-gr.cpp"
|
|
%define parse.error custom
|
|
%define parse.trace
|
|
|
|
// %glr-parser
|
|
|
|
%{
|
|
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
|
|
#include "common/endian.h"
|
|
#include "common/hash-str.h"
|
|
#include "common/rect.h"
|
|
|
|
#include "director/director.h"
|
|
#include "director/lingo/lingo.h"
|
|
#include "director/lingo/lingo-ast.h"
|
|
#include "director/lingo/lingo-code.h"
|
|
#include "director/lingo/lingo-codegen.h"
|
|
#include "director/lingo/lingo-gr.h"
|
|
#include "director/lingo/lingo-object.h"
|
|
#include "director/lingo/lingo-the.h"
|
|
|
|
extern int yylex();
|
|
extern int yyparse();
|
|
|
|
using namespace Director;
|
|
|
|
static void yyerror(const char *s) {
|
|
LingoCompiler *compiler = g_lingo->_compiler;
|
|
compiler->_hadError = true;
|
|
warning("###################### LINGO: %s at line %d col %d in %s id: %d",
|
|
s, compiler->_linenumber, compiler->_colnumber, scriptType2str(compiler->_assemblyContext->_scriptType),
|
|
compiler->_assemblyContext->_id);
|
|
if (compiler->_lines[2] != compiler->_lines[1])
|
|
warning("# %3d: %s", compiler->_linenumber - 2, Common::String(compiler->_lines[2], compiler->_lines[1] - 1).c_str());
|
|
|
|
if (compiler->_lines[1] != compiler->_lines[0])
|
|
warning("# %3d: %s", compiler->_linenumber - 1, Common::String(compiler->_lines[1], compiler->_lines[0] - 1).c_str());
|
|
|
|
const char *ptr = compiler->_lines[0];
|
|
|
|
while (*ptr && *ptr != '\n')
|
|
ptr++;
|
|
|
|
warning("# %3d: %s", compiler->_linenumber, Common::String(compiler->_lines[0], ptr).c_str());
|
|
|
|
Common::String arrow;
|
|
for (uint i = 0; i < compiler->_colnumber; i++)
|
|
arrow += ' ';
|
|
|
|
warning("# %s^ about here", arrow.c_str());
|
|
}
|
|
|
|
static void checkEnd(Common::String *token, Common::String *expect, bool required) {
|
|
if (required) {
|
|
if (token->compareToIgnoreCase(*expect)) {
|
|
Common::String err = Common::String::format("end mismatch. Expected %s but got %s", expect->c_str(), token->c_str());
|
|
yyerror(err.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
%}
|
|
|
|
%union {
|
|
Common::String *s;
|
|
int i;
|
|
double f;
|
|
Director::ChunkType chunktype;
|
|
struct {
|
|
Common::String *eventName;
|
|
Common::String *stmt;
|
|
} w;
|
|
|
|
Director::IDList *idlist;
|
|
Director::Node *node;
|
|
Director::NodeList *nodelist;
|
|
}
|
|
|
|
%token tUNARY
|
|
|
|
%token<i> tINT
|
|
%token<f> tFLOAT
|
|
%token<s> tVARID tSTRING tSYMBOL
|
|
%token<s> tENDCLAUSE
|
|
%token tCAST tFIELD tSCRIPT tWINDOW
|
|
%token tDELETE tDOWN tELSE tEXIT tFRAME tGLOBAL tGO tHILITE tIF tIN tINTO tMACRO
|
|
%token tMOVIE tNEXT tOF tPREVIOUS tPUT tREPEAT tSET tTHEN tTO tWHEN
|
|
%token tWITH tWHILE tFACTORY tOPEN tPLAY tINSTANCE
|
|
%token tGE tLE tEQ tNEQ tAND tOR tNOT tMOD
|
|
%token tAFTER tBEFORE tCONCAT tCONTAINS tSTARTS
|
|
%token tCHAR tCHARS tITEM tITEMS tLINE tLINES tWORD tWORDS
|
|
%token tABBREVIATED tABBREV tABBR tLONG tSHORT
|
|
%token tDATE tLAST tMENU tMENUITEM tMENUITEMS tNUMBER tTHE tTIME
|
|
%token tSOUND tSPRITE tINTERSECTS tWITHIN tTELL tPROPERTY
|
|
%token tON tMETHOD tENDIF tENDREPEAT tENDTELL
|
|
%token tASSERTERROR
|
|
|
|
%type<w> tWHEN
|
|
|
|
// TOP-LEVEL STUFF
|
|
%type<node> script scriptpart
|
|
%type<nodelist> scriptpartlist
|
|
|
|
// MACRO
|
|
%type<node> macro
|
|
|
|
// FACTORY
|
|
%type<node> factory method
|
|
%type<nodelist> methodlist nonemptymethodlist
|
|
%type<node> methodlistline
|
|
|
|
// HANDLER
|
|
%type<node> handler
|
|
|
|
// GENERIC VAR STUFF
|
|
%type<s> CMDID ID
|
|
%type<idlist> idlist nonemptyidlist
|
|
|
|
// STATEMENT
|
|
%type<node> stmt stmt_insideif stmtoneliner
|
|
%type<node> proc asgn definevars
|
|
%type<node> ifstmt ifelsestmt loop tell when
|
|
%type<nodelist> cmdargs frameargs stmtlist nonemptystmtlist stmtlist_insideif nonemptystmtlist_insideif
|
|
%type<node> stmtlistline stmtlistline_insideif
|
|
|
|
// EXPRESSION
|
|
%type<node> simpleexpr_nounarymath simpleexpr
|
|
%type<node> unarymath
|
|
%type<node> expr expr_nounarymath expr_noeq sprite
|
|
%type<node> var varorchunk varorthe
|
|
%type<chunktype> chunktype
|
|
%type<node> the theobj menu thedatetime thenumberof
|
|
%type<node> writablethe writabletheobj
|
|
%type<node> list proppair
|
|
%type<node> chunk object
|
|
%type<nodelist> refargs proplist exprlist nonemptyexprlist
|
|
|
|
%left tAND tOR
|
|
%left '<' tLE '>' tGE tEQ tNEQ tCONTAINS tSTARTS
|
|
%left '&' tCONCAT
|
|
%left '+' '-'
|
|
%left '*' '/' tMOD
|
|
%right tUNARY
|
|
// %right tCAST tFIELD tSCRIPT tWINDOW
|
|
// %nonassoc tVARID
|
|
|
|
%destructor { delete $$; } <s>
|
|
|
|
%%
|
|
|
|
// TOP-LEVEL STUFF
|
|
|
|
script: scriptpartlist { g_lingo->_compiler->_assemblyAST = new ScriptNode($scriptpartlist); } ;
|
|
|
|
scriptpartlist: scriptpart[item] {
|
|
NodeList *list = new NodeList;
|
|
if ($item) {
|
|
list->push_back($item);
|
|
}
|
|
$$ = list; }
|
|
| scriptpartlist[prev] scriptpart[item] {
|
|
if ($item) {
|
|
$prev->push_back($item);
|
|
}
|
|
$$ = $prev; }
|
|
;
|
|
|
|
scriptpart: '\n' { $$ = nullptr; }
|
|
| macro
|
|
| factory
|
|
| handler
|
|
| stmt
|
|
| tENDCLAUSE endargdef '\n' { $$ = nullptr; delete $tENDCLAUSE; } // stray `end`s are allowed for some reason
|
|
;
|
|
|
|
// MACRO
|
|
|
|
// Special Note The macro keyword is retained in Director 3.0 to maintain compatibility
|
|
// with scripts developed under Version 2.0. When writing new scripts, or editing old
|
|
// scripts, you should use handlers instead of macros. (Handlers are defined with the on keyword.)
|
|
//
|
|
// Syntax:
|
|
//
|
|
// -- [comment]
|
|
// macro macroName [argument1] [, argument2]
|
|
// [, argument3]
|
|
// [statement1]
|
|
// [statement2]
|
|
//
|
|
// Keyword. Defines a macro. A macro is a multiple-line script defined
|
|
// in the Text window. Macros can accept arguments (inputs) and
|
|
// optionally return a result. Macros can call other macros and can be
|
|
// called from any other script or factory.
|
|
//
|
|
// The first line of a castmember in the Text window that contains a macro must be
|
|
// a comment (--). You can define more than one macro in a given text castmember.
|
|
// The macro definition ends where the next macro (or factory) begins.
|
|
//
|
|
// See also:
|
|
// on keyword
|
|
|
|
macro: tMACRO ID idlist '\n' stmtlist { $$ = new HandlerNode($ID, $idlist, $stmtlist); } ;
|
|
|
|
// FACTORY
|
|
|
|
factory: tFACTORY ID '\n' methodlist { $$ = new FactoryNode($ID, $methodlist); } ;
|
|
|
|
method: tMETHOD ID idlist '\n' stmtlist { $$ = new HandlerNode($ID, $idlist, $stmtlist); } ;
|
|
|
|
methodlist: /* empty */ { $$ = new NodeList; }
|
|
| nonemptymethodlist
|
|
;
|
|
|
|
nonemptymethodlist: methodlistline[item] {
|
|
NodeList *list = new NodeList;
|
|
if ($item) {
|
|
list->push_back($item);
|
|
}
|
|
$$ = list; }
|
|
| nonemptymethodlist[prev] methodlistline[item] {
|
|
if ($item) {
|
|
$prev->push_back($item);
|
|
}
|
|
$$ = $prev; }
|
|
;
|
|
|
|
methodlistline: '\n' { $$ = nullptr; }
|
|
| method
|
|
| tENDCLAUSE endargdef '\n' { $$ = nullptr; delete $tENDCLAUSE; } // stray `end`s are allowed for some reason
|
|
;
|
|
|
|
// HANDLER
|
|
|
|
handler: tON ID idlist '\n' stmtlist tENDCLAUSE endargdef '\n' { // D3
|
|
$$ = new HandlerNode($ID, $idlist, $stmtlist);
|
|
checkEnd($tENDCLAUSE, $ID, false);
|
|
delete $tENDCLAUSE; }
|
|
| tON ID idlist '\n' stmtlist { // D4. No 'end' clause
|
|
$$ = new HandlerNode($ID, $idlist, $stmtlist); }
|
|
;
|
|
|
|
endargdef: /* nothing */
|
|
| ID { delete $ID; }
|
|
| endargdef ',' ID { delete $ID; }
|
|
;
|
|
|
|
// GENERIC VAR STUFF
|
|
|
|
// This is only the identifiers that can appaear at the start of a line
|
|
// and will not conflict with other statement types.
|
|
CMDID: tVARID
|
|
| tABBREVIATED { $$ = new Common::String("abbreviated"); }
|
|
| tABBREV { $$ = new Common::String("abbrev"); }
|
|
| tABBR { $$ = new Common::String("abbr"); }
|
|
| tAFTER { $$ = new Common::String("after"); }
|
|
| tAND { $$ = new Common::String("and"); }
|
|
| tBEFORE { $$ = new Common::String("before"); }
|
|
| tCAST { $$ = new Common::String("cast"); }
|
|
| tCHAR { $$ = new Common::String("char"); }
|
|
| tCHARS { $$ = new Common::String("chars"); }
|
|
| tCONTAINS { $$ = new Common::String("contains"); }
|
|
| tDATE { $$ = new Common::String("date"); }
|
|
| tDELETE { $$ = new Common::String("delete"); }
|
|
| tDOWN { $$ = new Common::String("down"); }
|
|
| tFIELD { $$ = new Common::String("field"); }
|
|
| tFRAME { $$ = new Common::String("frame"); }
|
|
| tHILITE { $$ = new Common::String("hilite"); }
|
|
| tIN { $$ = new Common::String("in"); }
|
|
| tINTERSECTS { $$ = new Common::String("intersects"); }
|
|
| tINTO { $$ = new Common::String("into"); }
|
|
| tITEM { $$ = new Common::String("item"); }
|
|
| tITEMS { $$ = new Common::String("items"); }
|
|
| tLAST { $$ = new Common::String("last"); }
|
|
| tLINE { $$ = new Common::String("line"); }
|
|
| tLINES { $$ = new Common::String("lines"); }
|
|
| tLONG { $$ = new Common::String("long"); }
|
|
| tMENU { $$ = new Common::String("menu"); }
|
|
| tMENUITEM { $$ = new Common::String("menuItem"); }
|
|
| tMENUITEMS { $$ = new Common::String("menuItems"); }
|
|
| tMOD { $$ = new Common::String("mod"); }
|
|
| tMOVIE { $$ = new Common::String("movie"); }
|
|
| tNEXT { $$ = new Common::String("next"); }
|
|
| tNOT { $$ = new Common::String("not"); }
|
|
| tNUMBER { $$ = new Common::String("number"); }
|
|
| tOF { $$ = new Common::String("of"); }
|
|
| tOR { $$ = new Common::String("or"); }
|
|
| tPREVIOUS { $$ = new Common::String("previous"); }
|
|
| tREPEAT { $$ = new Common::String("repeat"); }
|
|
| tSCRIPT { $$ = new Common::String("script"); }
|
|
| tASSERTERROR { $$ = new Common::String("scummvmAssertError"); }
|
|
| tSHORT { $$ = new Common::String("short"); }
|
|
| tSOUND { $$ = new Common::String("sound"); }
|
|
| tSPRITE { $$ = new Common::String("sprite"); }
|
|
| tSTARTS { $$ = new Common::String("starts"); }
|
|
| tTHE { $$ = new Common::String("the"); }
|
|
| tTIME { $$ = new Common::String("time"); }
|
|
| tTO { $$ = new Common::String("to"); }
|
|
| tWHILE { $$ = new Common::String("while"); }
|
|
| tWINDOW { $$ = new Common::String("window"); }
|
|
| tWITH { $$ = new Common::String("with"); }
|
|
| tWITHIN { $$ = new Common::String("within"); }
|
|
| tWORD { $$ = new Common::String("word"); }
|
|
| tWORDS { $$ = new Common::String("words"); }
|
|
;
|
|
|
|
ID: CMDID
|
|
| tELSE { $$ = new Common::String("else"); }
|
|
| tENDCLAUSE { $$ = new Common::String("end"); delete $tENDCLAUSE; }
|
|
| tEXIT { $$ = new Common::String("exit"); }
|
|
| tFACTORY { $$ = new Common::String("factory"); }
|
|
| tGLOBAL { $$ = new Common::String("global"); }
|
|
| tGO { $$ = new Common::String("go"); }
|
|
| tIF { $$ = new Common::String("if"); }
|
|
| tINSTANCE { $$ = new Common::String("instance"); }
|
|
| tMACRO { $$ = new Common::String("macro"); }
|
|
| tMETHOD { $$ = new Common::String("method"); }
|
|
| tON { $$ = new Common::String("on"); }
|
|
| tOPEN { $$ = new Common::String("open"); }
|
|
| tPLAY { $$ = new Common::String("play"); }
|
|
| tPROPERTY { $$ = new Common::String("property"); }
|
|
| tPUT { $$ = new Common::String("put"); }
|
|
| tSET { $$ = new Common::String("set"); }
|
|
| tTELL { $$ = new Common::String("tell"); }
|
|
| tTHEN { $$ = new Common::String("then"); }
|
|
;
|
|
|
|
idlist: /* empty */ { $$ = new IDList; }
|
|
| nonemptyidlist
|
|
| nonemptyidlist ',' // allow trailing comma
|
|
;
|
|
|
|
nonemptyidlist: ID[item] {
|
|
Common::Array<Common::String *> *list = new IDList;
|
|
list->push_back($item);
|
|
$$ = list; }
|
|
| nonemptyidlist[prev] ',' ID[item] {
|
|
$prev->push_back($item);
|
|
$$ = $prev; }
|
|
;
|
|
|
|
// STATEMENT
|
|
// N.B. A statement must always be terminated by a '\n' symbol.
|
|
// Sometimes this '\n' is in a nested statement (e.g. tIF expr tTHEN stmt).
|
|
// It may not look like there's a '\n', but it's there.
|
|
|
|
stmt: stmt_insideif
|
|
| tENDIF '\n' { $$ = nullptr; } // stray `end if`s are allowed for some reason
|
|
;
|
|
|
|
stmt_insideif: stmtoneliner
|
|
| ifstmt
|
|
| ifelsestmt
|
|
| loop
|
|
| tell
|
|
| when
|
|
;
|
|
|
|
stmtoneliner: proc
|
|
| asgn
|
|
| definevars
|
|
;
|
|
|
|
proc: CMDID cmdargs '\n' { $$ = new CmdNode($CMDID, $cmdargs, g_lingo->_compiler->_linenumber - 1); }
|
|
| tPUT cmdargs '\n' { $$ = new CmdNode(new Common::String("put"), $cmdargs, g_lingo->_compiler->_linenumber - 1); }
|
|
| tGO cmdargs '\n' { $$ = new CmdNode(new Common::String("go"), $cmdargs, g_lingo->_compiler->_linenumber - 1); }
|
|
| tGO frameargs '\n' { $$ = new CmdNode(new Common::String("go"), $frameargs, g_lingo->_compiler->_linenumber - 1); }
|
|
| tPLAY cmdargs '\n' { $$ = new CmdNode(new Common::String("play"), $cmdargs, g_lingo->_compiler->_linenumber - 1); }
|
|
| tPLAY frameargs '\n' { $$ = new CmdNode(new Common::String("play"), $frameargs, g_lingo->_compiler->_linenumber - 1); }
|
|
| tOPEN cmdargs '\n' { $$ = new CmdNode(new Common::String("open"), $cmdargs, g_lingo->_compiler->_linenumber - 1); }
|
|
| tOPEN expr[arg1] tWITH expr[arg2] '\n' {
|
|
NodeList *args = new NodeList;
|
|
args->push_back($arg1);
|
|
args->push_back($arg2);
|
|
$$ = new CmdNode(new Common::String("open"), args, g_lingo->_compiler->_linenumber - 1); }
|
|
| tNEXT tREPEAT '\n' { $$ = new NextRepeatNode(); }
|
|
| tEXIT tREPEAT '\n' { $$ = new ExitRepeatNode(); }
|
|
| tEXIT '\n' { $$ = new ExitNode(); }
|
|
| tDELETE chunk '\n' { $$ = new DeleteNode($chunk); }
|
|
| tHILITE chunk '\n' { $$ = new HiliteNode($chunk); }
|
|
| tASSERTERROR stmtoneliner { $$ = new AssertErrorNode($stmtoneliner); }
|
|
;
|
|
|
|
cmdargs: /* empty */ {
|
|
// This matches `cmd`
|
|
$$ = new NodeList; }
|
|
| expr trailingcomma {
|
|
// This matches `cmd arg` and `cmd(arg)`
|
|
NodeList *args = new NodeList;
|
|
args->push_back($expr);
|
|
$$ = args; }
|
|
| expr ',' nonemptyexprlist[args] trailingcomma {
|
|
// This matches `cmd args, ...)
|
|
$args->insert_at(0, $expr);
|
|
$$ = $args; }
|
|
| expr expr_nounarymath trailingcomma {
|
|
// This matches `cmd arg arg`
|
|
NodeList *args = new NodeList;
|
|
args->push_back($expr);
|
|
args->push_back($expr_nounarymath);
|
|
$$ = args; }
|
|
| expr expr_nounarymath ',' nonemptyexprlist[args] trailingcomma {
|
|
// This matches `cmd arg arg, ...`
|
|
$args->insert_at(0, $expr_nounarymath);
|
|
$args->insert_at(0, $expr);
|
|
$$ = $args; }
|
|
| '(' ')' {
|
|
// This matches `cmd()`
|
|
$$ = new NodeList; }
|
|
| '(' expr ',' ')' {
|
|
// This matches `cmd(args,)`
|
|
NodeList *args = new NodeList;
|
|
args->push_back($expr);
|
|
$$ = args; }
|
|
| '(' expr ',' nonemptyexprlist[args] trailingcomma ')' {
|
|
// This matches `cmd(args, ...)`
|
|
$args->insert_at(0, $expr);
|
|
$$ = $args; }
|
|
;
|
|
|
|
trailingcomma: /* empty */ | ',' ;
|
|
|
|
frameargs:
|
|
// On the off chance that we encounter something like `play frame done`
|
|
// we will wrap the frame arg in a FrameNode. This has no purpose other than
|
|
// to avoid detecting this case as `play done`.
|
|
tFRAME expr[frame] {
|
|
// This matches `play frame arg`
|
|
NodeList *args = new NodeList;
|
|
args->push_back(new FrameNode($frame));
|
|
$$ = args; }
|
|
| tMOVIE expr[movie] {
|
|
// This matches `play movie arg`
|
|
NodeList *args = new NodeList;
|
|
args->push_back(new IntNode(1));
|
|
args->push_back(new MovieNode($movie));
|
|
$$ = args; }
|
|
| tFRAME expr[frame] tOF tMOVIE expr[movie] {
|
|
// This matches `play frame arg of movie arg`
|
|
NodeList *args = new NodeList;
|
|
args->push_back(new FrameNode($frame));
|
|
args->push_back(new MovieNode($movie));
|
|
$$ = args; }
|
|
| expr[frame] tOF tMOVIE expr[movie] {
|
|
// This matches `play arg of movie arg` (weird but valid)
|
|
NodeList *args = new NodeList;
|
|
args->push_back($frame);
|
|
args->push_back(new MovieNode($movie));
|
|
$$ = args; }
|
|
| tFRAME expr[frame] expr_nounarymath[movie] {
|
|
// This matches `play frame arg arg` (also weird but valid)
|
|
NodeList *args = new NodeList;
|
|
args->push_back(new FrameNode($frame));
|
|
args->push_back($movie);
|
|
$$ = args; }
|
|
;
|
|
|
|
asgn: tPUT expr tINTO varorchunk '\n' { $$ = new PutIntoNode($expr, $varorchunk); }
|
|
| tPUT expr tAFTER varorchunk '\n' { $$ = new PutAfterNode($expr, $varorchunk); }
|
|
| tPUT expr tBEFORE varorchunk '\n' { $$ = new PutBeforeNode($expr, $varorchunk); }
|
|
| tSET varorthe to expr '\n' { $$ = new SetNode($varorthe, $expr); }
|
|
;
|
|
|
|
to: tTO | tEQ ;
|
|
|
|
definevars: tGLOBAL idlist '\n' { $$ = new GlobalNode($idlist); }
|
|
| tPROPERTY idlist '\n' { $$ = new PropertyNode($idlist); }
|
|
| tINSTANCE idlist '\n' { $$ = new InstanceNode($idlist); }
|
|
;
|
|
|
|
ifstmt: tIF expr tTHEN stmt {
|
|
NodeList *stmtlist = new NodeList;
|
|
stmtlist->push_back($stmt);
|
|
$$ = new IfStmtNode($expr, stmtlist); }
|
|
| tIF expr tTHEN '\n' stmtlist_insideif endif {
|
|
$$ = new IfStmtNode($expr, $stmtlist_insideif); }
|
|
;
|
|
|
|
ifelsestmt: tIF expr tTHEN stmt[stmt1] tELSE stmt[stmt2] {
|
|
NodeList *stmtlist1 = new NodeList;
|
|
stmtlist1->push_back($stmt1);
|
|
NodeList *stmtlist2 = new NodeList;
|
|
stmtlist2->push_back($stmt2);
|
|
$$ = new IfElseStmtNode($expr, stmtlist1, stmtlist2); }
|
|
| tIF expr tTHEN stmt[stmt1] tELSE '\n' stmtlist_insideif[stmtlist2] endif {
|
|
NodeList *stmtlist1 = new NodeList;
|
|
stmtlist1->push_back($stmt1);
|
|
$$ = new IfElseStmtNode($expr, stmtlist1, $stmtlist2); }
|
|
| tIF expr tTHEN '\n' stmtlist_insideif[stmtlist1] tELSE stmt[stmt2] {
|
|
NodeList *stmtlist2 = new NodeList;
|
|
stmtlist2->push_back($stmt2);
|
|
$$ = new IfElseStmtNode($expr, $stmtlist1, stmtlist2); }
|
|
| tIF expr tTHEN '\n' stmtlist_insideif[stmtlist1] tELSE '\n' stmtlist_insideif[stmtlist2] endif {
|
|
$$ = new IfElseStmtNode($expr, $stmtlist1, $stmtlist2); }
|
|
;
|
|
|
|
endif: /* empty */ { warning("LingoCompiler::parse: no end if"); }
|
|
| tENDIF '\n' ;
|
|
|
|
loop: tREPEAT tWHILE expr '\n' stmtlist tENDREPEAT '\n' {
|
|
$$ = new RepeatWhileNode($expr, $stmtlist); }
|
|
| tREPEAT tWITH ID tEQ expr[start] tTO expr[end] '\n' stmtlist tENDREPEAT '\n' {
|
|
$$ = new RepeatWithToNode($ID, $start, false, $end, $stmtlist); }
|
|
| tREPEAT tWITH ID tEQ expr[start] tDOWN tTO expr[end] '\n' stmtlist tENDREPEAT '\n' {
|
|
$$ = new RepeatWithToNode($ID, $start, true, $end, $stmtlist); }
|
|
| tREPEAT tWITH ID tIN expr '\n' stmtlist tENDREPEAT '\n' {
|
|
$$ = new RepeatWithInNode($ID, $expr, $stmtlist); }
|
|
;
|
|
|
|
tell: tTELL expr tTO stmtoneliner {
|
|
NodeList *stmtlist = new NodeList;
|
|
stmtlist->push_back($stmtoneliner);
|
|
$$ = new TellNode($expr, stmtlist); }
|
|
| tTELL expr '\n' stmtlist tENDTELL '\n' {
|
|
$$ = new TellNode($expr, $stmtlist); }
|
|
;
|
|
|
|
when: tWHEN '\n' { $$ = new WhenNode($tWHEN.eventName, $tWHEN.stmt); } ;
|
|
|
|
stmtlist: /* empty */ { $$ = new NodeList; }
|
|
| nonemptystmtlist
|
|
;
|
|
|
|
nonemptystmtlist:
|
|
stmtlistline[item] {
|
|
NodeList *list = new NodeList;
|
|
if ($item) {
|
|
list->push_back($item);
|
|
}
|
|
$$ = list; }
|
|
| nonemptystmtlist[prev] stmtlistline[item] {
|
|
if ($item) {
|
|
$prev->push_back($item);
|
|
}
|
|
$$ = $prev; }
|
|
;
|
|
|
|
stmtlistline: '\n' { $$ = nullptr; }
|
|
| stmt
|
|
;
|
|
|
|
stmtlist_insideif: /* empty */ { $$ = new NodeList; }
|
|
| nonemptystmtlist_insideif
|
|
;
|
|
|
|
nonemptystmtlist_insideif:
|
|
stmtlistline_insideif[item] {
|
|
NodeList *list = new NodeList;
|
|
if ($item) {
|
|
list->push_back($item);
|
|
}
|
|
$$ = list; }
|
|
| nonemptystmtlist_insideif[prev] stmtlistline_insideif[item] {
|
|
if ($item) {
|
|
$prev->push_back($item);
|
|
}
|
|
$$ = $prev; }
|
|
;
|
|
|
|
stmtlistline_insideif: '\n' { $$ = nullptr; }
|
|
| stmt_insideif
|
|
;
|
|
|
|
// EXPRESSION
|
|
|
|
simpleexpr_nounarymath:
|
|
tINT { $$ = new IntNode($tINT); }
|
|
| tFLOAT { $$ = new FloatNode($tFLOAT); }
|
|
| tSYMBOL { $$ = new SymbolNode($tSYMBOL); } // D3
|
|
| tSTRING { $$ = new StringNode($tSTRING); }
|
|
| tNOT simpleexpr[arg] %prec tUNARY { $$ = new UnaryOpNode(LC::c_not, $arg); }
|
|
| ID '(' ')' { $$ = new FuncNode($ID, new NodeList); }
|
|
| ID '(' nonemptyexprlist[args] trailingcomma ')' { $$ = new FuncNode($ID, $args); }
|
|
| '(' expr ')' { $$ = $expr; } ;
|
|
| var
|
|
| chunk
|
|
| object
|
|
| the
|
|
| list
|
|
;
|
|
|
|
var: ID { $$ = new VarNode($ID); } ;
|
|
|
|
varorchunk: var
|
|
| chunk
|
|
;
|
|
|
|
varorthe: var
|
|
| writablethe
|
|
;
|
|
|
|
chunk: tFIELD refargs { $$ = new FuncNode(new Common::String("field"), $refargs); }
|
|
| tCAST refargs { $$ = new FuncNode(new Common::String("cast"), $refargs); }
|
|
| tCHAR expr[idx] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkChar, $idx, nullptr, $src); }
|
|
| tCHAR expr[start] tTO expr[end] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkChar, $start, $end, $src); }
|
|
| tWORD expr[idx] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkWord, $idx, nullptr, $src); }
|
|
| tWORD expr[start] tTO expr[end] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkWord, $start, $end, $src); }
|
|
| tITEM expr[idx] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkItem, $idx, nullptr, $src); }
|
|
| tITEM expr[start] tTO expr[end] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkItem, $start, $end, $src); }
|
|
| tLINE expr[idx] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkLine, $idx, nullptr, $src); }
|
|
| tLINE expr[start] tTO expr[end] tOF simpleexpr[src] {
|
|
$$ = new ChunkExprNode(kChunkLine, $start, $end, $src); }
|
|
| tTHE tLAST chunktype inof simpleexpr { $$ = new TheLastNode($chunktype, $simpleexpr); }
|
|
;
|
|
|
|
chunktype: tCHAR { $$ = kChunkChar; }
|
|
| tWORD { $$ = kChunkWord; }
|
|
| tITEM { $$ = kChunkItem; }
|
|
| tLINE { $$ = kChunkLine; }
|
|
;
|
|
|
|
object: tSCRIPT refargs { $$ = new FuncNode(new Common::String("script"), $refargs); }
|
|
| tWINDOW refargs { $$ = new FuncNode(new Common::String("window"), $refargs); }
|
|
;
|
|
|
|
refargs: simpleexpr {
|
|
// This matches `ref arg` and `ref(arg)`
|
|
NodeList *args = new NodeList;
|
|
args->push_back($simpleexpr);
|
|
$$ = args; }
|
|
| '(' ')' {
|
|
// This matches `ref()`
|
|
$$ = new NodeList; }
|
|
| '(' expr ',' ')' {
|
|
// This matches `ref(args,)`
|
|
NodeList *args = new NodeList;
|
|
args->push_back($expr);
|
|
$$ = args; }
|
|
| '(' expr ',' nonemptyexprlist[args] trailingcomma ')' {
|
|
// This matches `ref(args, ...)`
|
|
$args->insert_at(0, $expr);
|
|
$$ = $args; }
|
|
;
|
|
|
|
the: tTHE ID { $$ = new TheNode($ID); }
|
|
| tTHE ID tOF theobj { $$ = new TheOfNode($ID, $theobj); }
|
|
| tTHE tNUMBER tOF theobj { $$ = new TheOfNode(new Common::String("number"), $theobj); }
|
|
| thedatetime
|
|
| thenumberof
|
|
;
|
|
|
|
theobj: simpleexpr
|
|
| menu
|
|
| tMENUITEM simpleexpr[item] tOF tMENU simpleexpr[menu] { $$ = new MenuItemNode($item, $menu); }
|
|
| tSOUND simpleexpr[arg] { $$ = new SoundNode($arg); }
|
|
| tSPRITE simpleexpr[arg] { $$ = new SpriteNode($arg); }
|
|
;
|
|
|
|
menu: tMENU simpleexpr[arg] { $$ = new MenuNode($arg); } ;
|
|
|
|
thedatetime: tTHE tABBREVIATED tDATE { $$ = new TheDateTimeNode(kTheAbbr, kTheDate); }
|
|
| tTHE tABBREVIATED tTIME { $$ = new TheDateTimeNode(kTheAbbr, kTheTime); }
|
|
| tTHE tABBREV tDATE { $$ = new TheDateTimeNode(kTheAbbr, kTheDate); }
|
|
| tTHE tABBREV tTIME { $$ = new TheDateTimeNode(kTheAbbr, kTheTime); }
|
|
| tTHE tABBR tDATE { $$ = new TheDateTimeNode(kTheAbbr, kTheDate); }
|
|
| tTHE tABBR tTIME { $$ = new TheDateTimeNode(kTheAbbr, kTheTime); }
|
|
| tTHE tLONG tDATE { $$ = new TheDateTimeNode(kTheLong, kTheDate); }
|
|
| tTHE tLONG tTIME { $$ = new TheDateTimeNode(kTheLong, kTheTime); }
|
|
| tTHE tSHORT tDATE { $$ = new TheDateTimeNode(kTheShort, kTheDate); }
|
|
| tTHE tSHORT tTIME { $$ = new TheDateTimeNode(kTheShort, kTheTime); }
|
|
;
|
|
|
|
thenumberof:
|
|
tTHE tNUMBER tOF tCHARS inof simpleexpr { $$ = new TheNumberOfNode(kNumberOfChars, $simpleexpr); }
|
|
| tTHE tNUMBER tOF tWORDS inof simpleexpr { $$ = new TheNumberOfNode(kNumberOfWords, $simpleexpr); }
|
|
| tTHE tNUMBER tOF tITEMS inof simpleexpr { $$ = new TheNumberOfNode(kNumberOfItems, $simpleexpr); }
|
|
| tTHE tNUMBER tOF tLINES inof simpleexpr { $$ = new TheNumberOfNode(kNumberOfLines, $simpleexpr); }
|
|
| tTHE tNUMBER tOF tMENUITEMS inof menu { $$ = new TheNumberOfNode(kNumberOfMenuItems, $menu); }
|
|
;
|
|
|
|
inof: tIN | tOF ;
|
|
|
|
writablethe: tTHE ID { $$ = new TheNode($ID); }
|
|
| tTHE ID tOF writabletheobj { $$ = new TheOfNode($ID, $writabletheobj); }
|
|
;
|
|
|
|
writabletheobj: simpleexpr
|
|
| tMENU expr_noeq[arg] { $$ = new MenuNode($arg); } ;
|
|
| tMENUITEM expr_noeq[item] tOF tMENU expr_noeq[menu] { $$ = new MenuItemNode($item, $menu); }
|
|
| tSOUND expr_noeq[arg] { $$ = new SoundNode($arg); }
|
|
| tSPRITE expr_noeq[arg] { $$ = new SpriteNode($arg); }
|
|
;
|
|
|
|
list: '[' exprlist ']' { $$ = new ListNode($exprlist); }
|
|
| '[' ':' ']' { $$ = new PropListNode(new NodeList); }
|
|
| '[' proplist ']' { $$ = new PropListNode($proplist); }
|
|
;
|
|
|
|
// A property list must start with a proppair, but it may be followed by
|
|
// keyless expressions, which will be compiled as equivalent to the
|
|
// proppair <index>: <expr>.
|
|
proplist: proppair[item] {
|
|
NodeList *list = new NodeList;
|
|
list->push_back($item);
|
|
$$ = list; }
|
|
| proplist[prev] ',' proppair[item] {
|
|
$prev->push_back($item);
|
|
$$ = $prev; }
|
|
| proplist[prev] ',' expr[item] {
|
|
$prev->push_back($item);
|
|
$$ = $prev; }
|
|
;
|
|
|
|
proppair: tSYMBOL ':' expr { $$ = new PropPairNode(new SymbolNode($tSYMBOL), $expr); }
|
|
| ID ':' expr { $$ = new PropPairNode(new SymbolNode($ID), $expr); }
|
|
| tSTRING ':' expr { $$ = new PropPairNode(new StringNode($tSTRING), $expr); }
|
|
| tINT ':' expr { $$ = new PropPairNode(new IntNode($tINT), $expr); }
|
|
| tFLOAT ':' expr { $$ = new PropPairNode(new FloatNode($tFLOAT), $expr); }
|
|
;
|
|
|
|
unarymath: '+' simpleexpr[arg] %prec tUNARY { $$ = $arg; }
|
|
| '-' simpleexpr[arg] %prec tUNARY { $$ = new UnaryOpNode(LC::c_negate, $arg); }
|
|
;
|
|
|
|
simpleexpr: simpleexpr_nounarymath
|
|
| unarymath
|
|
;
|
|
|
|
// REMEMBER TO SYNC THIS WITH expr_nounarymath and expr_noeq!
|
|
expr: simpleexpr
|
|
| sprite
|
|
| expr[a] '+' expr[b] { $$ = new BinaryOpNode(LC::c_add, $a, $b); }
|
|
| expr[a] '-' expr[b] { $$ = new BinaryOpNode(LC::c_sub, $a, $b); }
|
|
| expr[a] '*' expr[b] { $$ = new BinaryOpNode(LC::c_mul, $a, $b); }
|
|
| expr[a] '/' expr[b] { $$ = new BinaryOpNode(LC::c_div, $a, $b); }
|
|
| expr[a] tMOD expr[b] { $$ = new BinaryOpNode(LC::c_mod, $a, $b); }
|
|
| expr[a] '>' expr[b] { $$ = new BinaryOpNode(LC::c_gt, $a, $b); }
|
|
| expr[a] '<' expr[b] { $$ = new BinaryOpNode(LC::c_lt, $a, $b); }
|
|
| expr[a] tEQ expr[b] { $$ = new BinaryOpNode(LC::c_eq, $a, $b); }
|
|
| expr[a] tNEQ expr[b] { $$ = new BinaryOpNode(LC::c_neq, $a, $b); }
|
|
| expr[a] tGE expr[b] { $$ = new BinaryOpNode(LC::c_ge, $a, $b); }
|
|
| expr[a] tLE expr[b] { $$ = new BinaryOpNode(LC::c_le, $a, $b); }
|
|
| expr[a] tAND expr[b] { $$ = new BinaryOpNode(LC::c_and, $a, $b); }
|
|
| expr[a] tOR expr[b] { $$ = new BinaryOpNode(LC::c_or, $a, $b); }
|
|
| expr[a] '&' expr[b] { $$ = new BinaryOpNode(LC::c_ampersand, $a, $b); }
|
|
| expr[a] tCONCAT expr[b] { $$ = new BinaryOpNode(LC::c_concat, $a, $b); }
|
|
| expr[a] tCONTAINS expr[b] { $$ = new BinaryOpNode(LC::c_contains, $a, $b); }
|
|
| expr[a] tSTARTS expr[b] { $$ = new BinaryOpNode(LC::c_starts, $a, $b); }
|
|
;
|
|
|
|
// This is the same as expr except it can't start with a unary math operator.
|
|
// It's ugly but unfortunately necessary to allow two expressions in a row with no delimeter.
|
|
// Without this, `cmd 1 + 1` could be interpreted as either `cmd(1 + 1)` or `cmd(1, +1)`.
|
|
// We only want to allow the first interpretation, so we must exclude unary math from the second expression.
|
|
expr_nounarymath: simpleexpr_nounarymath
|
|
| sprite
|
|
| expr_nounarymath[a] '+' expr[b] { $$ = new BinaryOpNode(LC::c_add, $a, $b); }
|
|
| expr_nounarymath[a] '-' expr[b] { $$ = new BinaryOpNode(LC::c_sub, $a, $b); }
|
|
| expr_nounarymath[a] '*' expr[b] { $$ = new BinaryOpNode(LC::c_mul, $a, $b); }
|
|
| expr_nounarymath[a] '/' expr[b] { $$ = new BinaryOpNode(LC::c_div, $a, $b); }
|
|
| expr_nounarymath[a] tMOD expr[b] { $$ = new BinaryOpNode(LC::c_mod, $a, $b); }
|
|
| expr_nounarymath[a] '>' expr[b] { $$ = new BinaryOpNode(LC::c_gt, $a, $b); }
|
|
| expr_nounarymath[a] '<' expr[b] { $$ = new BinaryOpNode(LC::c_lt, $a, $b); }
|
|
| expr_nounarymath[a] tEQ expr[b] { $$ = new BinaryOpNode(LC::c_eq, $a, $b); }
|
|
| expr_nounarymath[a] tNEQ expr[b] { $$ = new BinaryOpNode(LC::c_neq, $a, $b); }
|
|
| expr_nounarymath[a] tGE expr[b] { $$ = new BinaryOpNode(LC::c_ge, $a, $b); }
|
|
| expr_nounarymath[a] tLE expr[b] { $$ = new BinaryOpNode(LC::c_le, $a, $b); }
|
|
| expr_nounarymath[a] tAND expr[b] { $$ = new BinaryOpNode(LC::c_and, $a, $b); }
|
|
| expr_nounarymath[a] tOR expr[b] { $$ = new BinaryOpNode(LC::c_or, $a, $b); }
|
|
| expr_nounarymath[a] '&' expr[b] { $$ = new BinaryOpNode(LC::c_ampersand, $a, $b); }
|
|
| expr_nounarymath[a] tCONCAT expr[b] { $$ = new BinaryOpNode(LC::c_concat, $a, $b); }
|
|
| expr_nounarymath[a] tCONTAINS expr[b] { $$ = new BinaryOpNode(LC::c_contains, $a, $b); }
|
|
| expr_nounarymath[a] tSTARTS expr[b] { $$ = new BinaryOpNode(LC::c_starts, $a, $b); }
|
|
;
|
|
|
|
expr_noeq: simpleexpr
|
|
| sprite
|
|
| expr_noeq[a] '+' expr_noeq[b] { $$ = new BinaryOpNode(LC::c_add, $a, $b); }
|
|
| expr_noeq[a] '-' expr_noeq[b] { $$ = new BinaryOpNode(LC::c_sub, $a, $b); }
|
|
| expr_noeq[a] '*' expr_noeq[b] { $$ = new BinaryOpNode(LC::c_mul, $a, $b); }
|
|
| expr_noeq[a] '/' expr_noeq[b] { $$ = new BinaryOpNode(LC::c_div, $a, $b); }
|
|
| expr_noeq[a] tMOD expr_noeq[b] { $$ = new BinaryOpNode(LC::c_mod, $a, $b); }
|
|
| expr_noeq[a] '>' expr_noeq[b] { $$ = new BinaryOpNode(LC::c_gt, $a, $b); }
|
|
| expr_noeq[a] '<' expr_noeq[b] { $$ = new BinaryOpNode(LC::c_lt, $a, $b); }
|
|
| expr_noeq[a] tNEQ expr_noeq[b] { $$ = new BinaryOpNode(LC::c_neq, $a, $b); }
|
|
| expr_noeq[a] tGE expr_noeq[b] { $$ = new BinaryOpNode(LC::c_ge, $a, $b); }
|
|
| expr_noeq[a] tLE expr_noeq[b] { $$ = new BinaryOpNode(LC::c_le, $a, $b); }
|
|
| expr_noeq[a] tAND expr_noeq[b] { $$ = new BinaryOpNode(LC::c_and, $a, $b); }
|
|
| expr_noeq[a] tOR expr_noeq[b] { $$ = new BinaryOpNode(LC::c_or, $a, $b); }
|
|
| expr_noeq[a] '&' expr_noeq[b] { $$ = new BinaryOpNode(LC::c_ampersand, $a, $b); }
|
|
| expr_noeq[a] tCONCAT expr_noeq[b] { $$ = new BinaryOpNode(LC::c_concat, $a, $b); }
|
|
| expr_noeq[a] tCONTAINS expr_noeq[b] { $$ = new BinaryOpNode(LC::c_contains, $a, $b); }
|
|
| expr_noeq[a] tSTARTS expr_noeq[b] { $$ = new BinaryOpNode(LC::c_starts, $a, $b); }
|
|
;
|
|
|
|
sprite: tSPRITE expr tINTERSECTS simpleexpr { $$ = new IntersectsNode($expr, $simpleexpr); }
|
|
| tSPRITE expr tWITHIN simpleexpr { $$ = new WithinNode($expr, $simpleexpr); }
|
|
;
|
|
|
|
exprlist: /* empty */ { $$ = new NodeList; }
|
|
| nonemptyexprlist
|
|
;
|
|
|
|
nonemptyexprlist: expr[item] {
|
|
NodeList *list = new NodeList;
|
|
list->push_back($item);
|
|
$$ = list; }
|
|
| nonemptyexprlist[prev] ',' expr[item] {
|
|
$prev->push_back($item);
|
|
$$ = $prev; }
|
|
;
|
|
|
|
%%
|
|
|
|
int yyreport_syntax_error(const yypcontext_t *ctx) {
|
|
int res = 0;
|
|
|
|
Common::String msg = "syntax error, ";
|
|
|
|
// Report the unexpected token.
|
|
yysymbol_kind_t lookahead = yypcontext_token(ctx);
|
|
if (lookahead != YYSYMBOL_YYEMPTY)
|
|
msg += Common::String::format("unexpected %s", yysymbol_name(lookahead));
|
|
|
|
// Report the tokens expected at this point.
|
|
enum { TOKENMAX = 10 };
|
|
yysymbol_kind_t expected[TOKENMAX];
|
|
|
|
int n = yypcontext_expected_tokens(ctx, expected, TOKENMAX);
|
|
if (n < 0)
|
|
// Forward errors to yyparse.
|
|
res = n;
|
|
else
|
|
for (int i = 0; i < n; ++i)
|
|
msg += Common::String::format("%s %s", i == 0 ? ": expected" : " or", yysymbol_name(expected[i]));
|
|
|
|
yyerror(msg.c_str());
|
|
|
|
return res;
|
|
}
|