2016-01-28 14:52:40 +01:00
|
|
|
/** @file
|
|
|
|
|
*
|
|
|
|
|
* @par History
|
|
|
|
|
* - 2005/07/26 Shinigami: if you use 'Include ":blah:blubb";' eCompile will search for
|
|
|
|
|
* ":blah:blubb.inc" and ":blah:include/blubb.inc". It will use
|
|
|
|
|
* first file found. If both files exist eCompile will print a Warning.
|
|
|
|
|
* - 2005/07/28 Shinigami: Assignment inside Condition Check will produce Warning on -v5 only
|
|
|
|
|
* - 2005-09-07 Folko: No longer warn about unused variables in BASIC style for loops
|
|
|
|
|
* - 2005/09/08 Shinigami: Will warn about unused variables in BASIC style for loops on -v5 only
|
|
|
|
|
* - 2005/09/25 Shinigami: BugFix inside FileCheck for multiple include of same File
|
2018-04-03 04:13:03 +02:00
|
|
|
* e.g.: inside scripts extcmd est extcmd.src:
|
2016-01-28 14:52:40 +01:00
|
|
|
* Include "../../../pkg/std/housing/include/test";
|
|
|
|
|
* Include ":housing:test";
|
|
|
|
|
* Include ":housing:include/test";
|
|
|
|
|
* will be handled as same file now
|
|
|
|
|
* - 2005/11/26 Shinigami: changed "strcmp" into "stricmp" to suppress Script Errors
|
|
|
|
|
*/
|
2014-08-30 12:25:08 +02:00
|
|
|
|
|
|
|
|
|
2014-11-01 20:09:12 +01:00
|
|
|
#include "compiler.h"
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2018-01-29 21:55:48 +01:00
|
|
|
#include <cstddef>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <exception>
|
|
|
|
|
#include <ostream>
|
|
|
|
|
#include <stdexcept>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
2014-08-30 12:25:08 +02:00
|
|
|
#include "../clib/clib.h"
|
|
|
|
|
#include "../clib/filecont.h"
|
|
|
|
|
#include "../clib/fileutil.h"
|
2018-01-29 21:55:48 +01:00
|
|
|
#include "../clib/logfacility.h"
|
2018-01-01 21:49:30 +01:00
|
|
|
#include "../clib/passert.h"
|
2014-08-30 12:25:08 +02:00
|
|
|
#include "../clib/stlutil.h"
|
|
|
|
|
#include "../clib/strutil.h"
|
|
|
|
|
#include "../plib/pkg.h"
|
|
|
|
|
#include "compctx.h"
|
2020-08-05 00:01:07 -07:00
|
|
|
#include "compiler/LegacyFunctionOrder.h"
|
2014-08-30 12:25:08 +02:00
|
|
|
#include "compilercfg.h"
|
2018-01-01 21:49:30 +01:00
|
|
|
#include "eprog.h"
|
2020-04-24 12:45:14 -07:00
|
|
|
#include "expression.h"
|
2018-01-01 21:49:30 +01:00
|
|
|
#include "fmodule.h"
|
2014-08-30 12:25:08 +02:00
|
|
|
#include "modules.h"
|
|
|
|
|
#include "objmembers.h"
|
2018-01-29 21:55:48 +01:00
|
|
|
#include "symcont.h"
|
|
|
|
|
#include "token.h"
|
|
|
|
|
#include "tokens.h"
|
|
|
|
|
#include "userfunc.h"
|
2018-02-03 19:05:22 +01:00
|
|
|
#include <format/format.h>
|
2018-05-21 14:44:49 +02:00
|
|
|
#include <utf8/utf8.h>
|
2014-11-01 20:09:12 +01:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
namespace Pol
|
|
|
|
|
{
|
|
|
|
|
namespace Bscript
|
|
|
|
|
{
|
2020-06-03 19:38:09 -07:00
|
|
|
extern int include_debug;
|
|
|
|
|
namespace Legacy
|
|
|
|
|
{
|
2016-02-19 19:26:35 +01:00
|
|
|
bool Compiler::check_filecase_;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::string getpathof( const std::string& fname )
|
|
|
|
|
{
|
|
|
|
|
std::string::size_type pos = fname.find_last_of( "\\/" );
|
|
|
|
|
if ( pos == std::string::npos )
|
|
|
|
|
return "./";
|
|
|
|
|
else
|
|
|
|
|
return fname.substr( 0, pos + 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Scope::varexists( const std::string& varname, unsigned& idx ) const
|
|
|
|
|
{
|
|
|
|
|
for ( int i = static_cast<int>( variables_.size() - 1 ); i >= 0; --i )
|
|
|
|
|
{
|
|
|
|
|
if ( Clib::stringicmp( varname, variables_[i].name ) == 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
if ( variables_[i].unused )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( nullptr, "Warning: variable '", variables_[i].name,
|
|
|
|
|
"' declared as unused but used.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
variables_[i].used = true;
|
|
|
|
|
idx = i;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Scope::varexists( const std::string& varname ) const
|
|
|
|
|
{
|
|
|
|
|
for ( int i = static_cast<int>( variables_.size() - 1 ); i >= 0; --i )
|
|
|
|
|
{
|
|
|
|
|
if ( Clib::stringicmp( varname, variables_[i].name ) == 0 )
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BlockDesc& Scope::pushblock()
|
|
|
|
|
{
|
|
|
|
|
blockdescs_.push_back( BlockDesc() );
|
|
|
|
|
return blockdescs_.back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Scope::popblock( bool varsOnly = false )
|
|
|
|
|
{
|
|
|
|
|
BlockDesc& bd = blockdescs_.back();
|
|
|
|
|
|
|
|
|
|
for ( ; bd.varcount; bd.varcount-- ) // To enable popping variables only
|
|
|
|
|
{
|
|
|
|
|
Variable& bk = variables_.back();
|
2019-10-05 13:19:38 +02:00
|
|
|
if ( !bk.used && !bk.unused )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( &bk.ctx, "Warning: local variable '", bk.name, "' not used.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
variables_.pop_back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !varsOnly )
|
|
|
|
|
blockdescs_.pop_back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Scope::addvar( const std::string& varname, const CompilerContext& ctx, bool warn_on_notused,
|
|
|
|
|
bool unused )
|
|
|
|
|
{
|
|
|
|
|
for ( size_t i = variables_.size() - blockdescs_.back().varcount; i < variables_.size(); ++i )
|
|
|
|
|
{
|
|
|
|
|
if ( Clib::stringicmp( varname, variables_[i].name ) == 0 )
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error( "Variable " + varname + " is already in scope." );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Variable newvar;
|
|
|
|
|
newvar.name = varname;
|
|
|
|
|
newvar.ctx = ctx;
|
|
|
|
|
newvar.used = !warn_on_notused;
|
|
|
|
|
newvar.unused = unused;
|
|
|
|
|
variables_.push_back( newvar );
|
|
|
|
|
blockdescs_.back().varcount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Scope::addvalue()
|
|
|
|
|
{
|
|
|
|
|
blockdescs_.back().valcount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::enterblock( eb_label_ok eblabel, eb_break_ok ebbreak, eb_continue_ok ebcontinue )
|
|
|
|
|
{
|
|
|
|
|
program->enterblock();
|
|
|
|
|
|
|
|
|
|
BlockDesc& bd = localscope.pushblock();
|
|
|
|
|
bd.varcount = 0;
|
|
|
|
|
bd.label_ok = eblabel;
|
|
|
|
|
bd.break_ok = ebbreak;
|
|
|
|
|
bd.continue_ok = ebcontinue;
|
|
|
|
|
|
|
|
|
|
if ( bd.label_ok == CanBeLabelled )
|
|
|
|
|
{
|
|
|
|
|
bd.label = latest_label;
|
|
|
|
|
latest_label = "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
bd.label = "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void Compiler::enterblock( eb_label_ok et )
|
|
|
|
|
{
|
|
|
|
|
enterblock( et, et ? BreakOk : BreakNotOk, et ? ContinueOk : ContinueNotOk );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::patchblock_breaks( unsigned breakPC )
|
|
|
|
|
{
|
|
|
|
|
// now, patch up the GOTO part of BREAK statements.
|
|
|
|
|
// they each have a LEAVE_BLOCK appropriate to where they are.
|
|
|
|
|
const BlockDesc& bd = localscope.blockdesc();
|
|
|
|
|
for ( auto patchip : bd.break_tokens )
|
|
|
|
|
{
|
|
|
|
|
patchoffset( patchip, breakPC ); // program->tokens.next()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::patchblock_continues( unsigned continuePC )
|
|
|
|
|
{
|
|
|
|
|
const BlockDesc& bd = localscope.blockdesc();
|
|
|
|
|
for ( auto patchip : bd.continue_tokens )
|
|
|
|
|
{
|
|
|
|
|
patchoffset( patchip, continuePC );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::emit_leaveblock()
|
|
|
|
|
{
|
|
|
|
|
if ( localscope.numVarsInBlock() )
|
|
|
|
|
{ // local variables were declared in this scope. We need to kill 'em.
|
|
|
|
|
program->append(
|
|
|
|
|
StoredToken( Mod_Basic, CTRL_LEAVE_BLOCK, TYP_CONTROL, localscope.numVarsInBlock() ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::leaveblock( unsigned breakPC, unsigned continuePC )
|
|
|
|
|
{
|
|
|
|
|
emit_leaveblock();
|
|
|
|
|
patchblock_breaks( breakPC );
|
|
|
|
|
patchblock_continues( continuePC );
|
|
|
|
|
|
|
|
|
|
localscope.popblock();
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Compiler::Compiler()
|
|
|
|
|
: SmartParser(),
|
|
|
|
|
current_file_path( "" ),
|
|
|
|
|
curSourceFile( 0 ),
|
|
|
|
|
inExpr( 0 ),
|
|
|
|
|
inFunction( 0 ),
|
|
|
|
|
haveProgram( false ),
|
|
|
|
|
compiling_include( false ),
|
|
|
|
|
programPos( 0 ),
|
|
|
|
|
nProgramArgs( 0 ),
|
|
|
|
|
program_ctx(),
|
2018-10-09 16:36:35 +02:00
|
|
|
program_source( nullptr ),
|
2016-02-19 19:26:35 +01:00
|
|
|
included(),
|
|
|
|
|
referencedPathnames(),
|
|
|
|
|
program( new EScriptProgram )
|
|
|
|
|
{
|
|
|
|
|
setQuiet( 1 );
|
|
|
|
|
err = PERR_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Compiler::~Compiler()
|
|
|
|
|
{
|
|
|
|
|
while ( !delete_these_arrays.empty() )
|
|
|
|
|
{
|
|
|
|
|
char* s = delete_these_arrays.back();
|
|
|
|
|
delete[] s;
|
|
|
|
|
delete_these_arrays.pop_back();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Compiler::globalexists( const std::string& varname, unsigned& idx, CompilerContext* ctx ) const
|
|
|
|
|
{
|
|
|
|
|
for ( unsigned i = 0; i < static_cast<unsigned>( globals_.size() ); ++i )
|
|
|
|
|
{
|
|
|
|
|
if ( Clib::stringicmp( varname, globals_[i].name ) == 0 )
|
|
|
|
|
{
|
|
|
|
|
idx = i;
|
|
|
|
|
if ( ctx )
|
|
|
|
|
*ctx = globals_[i].ctx;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Compiler::varexists( const std::string& varname ) const
|
|
|
|
|
{
|
|
|
|
|
unsigned idx;
|
|
|
|
|
if ( localscope.varexists( varname, idx ) )
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if ( globalexists( varname, idx ) )
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::isLegal( Token& token )
|
|
|
|
|
{
|
|
|
|
|
if ( inExpr && ( token.id == TOK_ASSIGN ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 5 )
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( nullptr, "Warning! possible incorrect assignment.\n", "Near: ", curLine,
|
|
|
|
|
"\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1; // assignments valid everywhere. back to simple parser
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Candidate
|
|
|
|
|
{
|
|
|
|
|
Candidate( int module, int funcidx ) : module( module ), funcidx( funcidx ), modfunc( nullptr ) {}
|
|
|
|
|
int module;
|
|
|
|
|
int funcidx;
|
|
|
|
|
ModuleFunction* modfunc;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int Compiler::isFunc( Token& token, ModuleFunction** pmf )
|
|
|
|
|
{
|
|
|
|
|
typedef std::vector<Candidate> Candidates;
|
|
|
|
|
Candidates candidates;
|
|
|
|
|
std::string modulename;
|
|
|
|
|
std::string funcname;
|
|
|
|
|
|
|
|
|
|
if ( const char* colon = strchr( token.tokval(), ':' ) )
|
|
|
|
|
{
|
|
|
|
|
std::string tmp( token.tokval(), colon );
|
|
|
|
|
if ( tmp.length() >= 9 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "'", tmp, "' is too long to be a module name.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
modulename = tmp;
|
|
|
|
|
funcname = std::string( colon + 2 );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
modulename = "";
|
|
|
|
|
funcname = token.tokval();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ( unsigned i = 0; i < program->modules.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
if ( !modulename.empty() &&
|
|
|
|
|
!( modulename == program->modules[i]->modulename ) ) // STLport doesn't like != here
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int funcidx;
|
|
|
|
|
if ( program->modules[i]->isFunc( funcname.c_str(), pmf, &funcidx ) )
|
|
|
|
|
{
|
|
|
|
|
candidates.push_back( Candidate( i, funcidx ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( candidates.empty() )
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else if ( candidates.size() == 1 )
|
|
|
|
|
{
|
|
|
|
|
token.module =
|
|
|
|
|
static_cast<unsigned char>( candidates[0].module ); // WAS module,we're using relative now
|
|
|
|
|
token.type = TYP_FUNC;
|
|
|
|
|
token.id = TOK_FUNC;
|
|
|
|
|
token.lval = candidates[0].funcidx;
|
|
|
|
|
token.userfunc = ( *pmf )->uf;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Function '", funcname,
|
|
|
|
|
"' exists in more than module. It must be qualified.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
for ( Candidates::const_iterator itr = candidates.begin(); itr != candidates.end(); ++itr )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "\t", program->modules[itr->module]->modulename.get(), "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::addModule( FunctionalityModule* module )
|
|
|
|
|
{
|
|
|
|
|
module->fillFunctionsByName();
|
|
|
|
|
program->modules.push_back( module );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::isUserFunc( Token& token, UserFunction** f )
|
|
|
|
|
{
|
|
|
|
|
if ( token.id != TOK_IDENT )
|
|
|
|
|
return 0;
|
|
|
|
|
passert( token.tokval() );
|
|
|
|
|
|
|
|
|
|
auto itr = userFunctions.find( token.tokval() );
|
|
|
|
|
if ( itr != userFunctions.end() )
|
|
|
|
|
{
|
|
|
|
|
token.module = Mod_Basic;
|
|
|
|
|
token.type = TYP_USERFUNC;
|
|
|
|
|
token.id = TOK_USERFUNC;
|
|
|
|
|
token.userfunc = &( *itr ).second;
|
|
|
|
|
*f = &( *itr ).second;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
int Compiler::getArrayElements( Expression& expr, CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id != TOK_LPAREN )
|
|
|
|
|
return 0;
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
|
|
|
|
|
// it's valid to have a right-paren immediately following, so check for that:
|
|
|
|
|
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
/*
|
|
|
|
|
if we get an rparen HERE, it means the script has something like
|
|
|
|
|
var x := array ( 2, 3, );
|
|
|
|
|
report this as an error.
|
|
|
|
|
*/
|
|
|
|
|
if ( token.id == TOK_RPAREN )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error(
|
|
|
|
|
"Expected expression following comma before right-brace in array initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected comma in array initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
2020-04-28 17:26:11 -07:00
|
|
|
res = read_subexpression( expr, ctx,
|
|
|
|
|
EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
expr.CA.push( new Token( TOK_INSERTINTO, TYP_OPERATOR ) );
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Token '", token, "' unexpected in array initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::getNewArrayElements( Expression& expr, CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
// the left-brace has already been eaten
|
|
|
|
|
|
|
|
|
|
// it's valid to have a right-brace immediately following, so check for that:
|
|
|
|
|
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
// if we get an rbrace HERE, it means the script has something like
|
|
|
|
|
// var x := array { 2, 3, };
|
|
|
|
|
// report this as an error.
|
|
|
|
|
if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error(
|
|
|
|
|
"Expected expression following comma before right-brace in array initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
// we're expecting an expression, not a comma, at this point
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected comma in array initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
2020-04-28 17:26:11 -07:00
|
|
|
res = read_subexpression( expr, ctx,
|
|
|
|
|
EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_RIGHTBRACE_TERM_ALLOWED );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
expr.CA.push( new Token( TOK_INSERTINTO, TYP_OPERATOR ) );
|
|
|
|
|
|
|
|
|
|
// the element can be followed by a comma, or by a rightbrace. eat either.
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Token '", token, "' unexpected in array initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::getStructMembers( Expression& expr, CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id != TOK_LBRACE )
|
|
|
|
|
return 0;
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
|
|
|
|
|
// it's valid to have a right-brace immediately following, so check for that:
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
// if we get an rbrace HERE, it means the script has something like
|
|
|
|
|
// var x := struct { x, y, };
|
|
|
|
|
// report this as an error.
|
|
|
|
|
if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error(
|
|
|
|
|
"Expected expression following comma before right-brace in struct initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected comma in struct element list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( token.id == TOK_IDENT || token.id == TOK_STRING )
|
|
|
|
|
{
|
|
|
|
|
Token ident_tkn;
|
2020-06-15 22:26:33 -07:00
|
|
|
getTokenWithoutConversions( ctx, ident_tkn );
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( token.id == TOK_ASSIGN )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
// something like struct { a := 5 };
|
2020-04-28 17:26:11 -07:00
|
|
|
res = read_subexpression(
|
|
|
|
|
expr, ctx, EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_RIGHTBRACE_TERM_ALLOWED );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
auto addmem = new Token( ident_tkn );
|
|
|
|
|
addmem->id = INS_ADDMEMBER_ASSIGN;
|
|
|
|
|
|
|
|
|
|
expr.CA.push( addmem );
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_EQUAL1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token: '", token, "'. Did you mean := for assign?\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto addmem = new Token( ident_tkn );
|
|
|
|
|
addmem->id = INS_ADDMEMBER2;
|
|
|
|
|
expr.CA.push( addmem );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token in struct initializer list: ", token, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Token '", token, "' unexpected in struct initializer list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// unreachable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::getDictionaryMembers( Expression& expr, CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id != TOK_LBRACE )
|
|
|
|
|
return 0;
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
|
|
|
|
|
// it's valid to have a right-brace immediately following, so check for that:
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
// if we get an rbrace HERE, it means the script has something like
|
|
|
|
|
// var x := dictionary { "x", "y", };
|
|
|
|
|
// report this as an error.
|
|
|
|
|
if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error(
|
|
|
|
|
"Expected expression following comma before right-brace in dictionary initializer "
|
|
|
|
|
"list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected comma in dictionary element list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// first get the key expression.
|
2020-04-28 17:26:11 -07:00
|
|
|
res = read_subexpression( expr, ctx,
|
|
|
|
|
EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_DICTKEY_TERM_ALLOWED |
|
|
|
|
|
EXPR_FLAG_RIGHTBRACE_TERM_ALLOWED );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
// if the key is followed by "->", then grab the value
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_DICTKEY )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
// get the value expression
|
|
|
|
|
|
2020-04-28 17:26:11 -07:00
|
|
|
res = read_subexpression( expr, ctx,
|
|
|
|
|
EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_RIGHTBRACE_TERM_ALLOWED );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// an uninit
|
|
|
|
|
expr.CA.push( new Token( INS_UNINIT, TYP_OPERAND ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expr.CA.push( new Token( INS_DICTIONARY_ADDMEMBER, TYP_OPERATOR ) );
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_RBRACE )
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Token '", token, "' unexpected in struct element list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// unreachable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::getMethodArguments( Expression& expr, CompilerContext& ctx, int& nargs )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
nargs = 0;
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
passert( token.id == TOK_LPAREN );
|
|
|
|
|
passert( res == 0 );
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected comma in array element list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
2020-04-28 17:26:11 -07:00
|
|
|
res = read_subexpression( expr, ctx,
|
|
|
|
|
EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
++nargs;
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Token '", token, "' unexpected in array element list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// unreachable
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-03 19:05:22 +01:00
|
|
|
int Compiler::getFunctionPArgument( Expression& /*expr*/, CompilerContext& ctx, Token* ref_tkn )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
res = getToken( ctx, *ref_tkn );
|
|
|
|
|
if ( res < 0 || ref_tkn->id != TOK_USERFUNC )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected user function reference.\n" );
|
2018-02-03 19:05:22 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
ref_tkn->id = TOK_FUNCREF;
|
|
|
|
|
ref_tkn->type = TYP_OPERAND;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
getUserArgs is called from IIP.
|
|
|
|
|
It is being converted from calling IIP (and thus stuffing
|
|
|
|
|
expression data onto ex) to calling readexpr() and
|
|
|
|
|
appending the argument expressions onto the passed
|
|
|
|
|
ex object.
|
|
|
|
|
|
|
|
|
|
Overall function:
|
|
|
|
|
Read the parameters actually passed.
|
|
|
|
|
Append expressions for parameters to passed expression.
|
|
|
|
|
Append a JSR instruction
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
class ParamPassed
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
ParamPassed( const std::string& name ) : name(name) {}
|
|
|
|
|
|
|
|
|
|
string name;
|
|
|
|
|
Expression expr;
|
|
|
|
|
};
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int Compiler::getUserArgs( Expression& ex, CompilerContext& ctx, bool inject_jsr )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
|
|
|
|
|
typedef std::map<std::string, Expression> ParamList;
|
|
|
|
|
ParamList params_passed;
|
|
|
|
|
// std::vector<std::string> func_params;
|
|
|
|
|
|
|
|
|
|
int any_named = 0;
|
|
|
|
|
UserFunction* userfunc = userfunc_;
|
|
|
|
|
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( token.id != TOK_LPAREN )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected '(' after function name '", userfunc->name, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
res = -1;
|
|
|
|
|
err = PERR_MISSLPAREN;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
std::string varname;
|
|
|
|
|
Token tk;
|
|
|
|
|
|
|
|
|
|
CompilerContext tctx( ctx );
|
|
|
|
|
|
2020-06-15 22:26:33 -07:00
|
|
|
res = getTokenWithoutConversions( tctx, tk );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( tk.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
if ( params_passed.empty() )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "right paren not allowed here\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( params_passed.size() >= userfunc->parameters.size() )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Too many parameters passed to ", userfunc->name, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tk.id == TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
Token tk2;
|
|
|
|
|
res = getToken( tctx, tk2 );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( tk2.id == TOK_ASSIGN )
|
|
|
|
|
{
|
|
|
|
|
any_named = 1;
|
|
|
|
|
varname = tk.tokval();
|
|
|
|
|
ctx = tctx; /* skip past the 'variable :=' part */
|
|
|
|
|
}
|
|
|
|
|
else if ( tk2.id == TOK_EQUAL1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token: '", tk2, "'. Did you mean := for assign?\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( varname == "" )
|
|
|
|
|
{
|
|
|
|
|
if ( any_named )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "unnamed args cannot follow named args\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
varname = userfunc->parameters[params_passed.size()].name;
|
|
|
|
|
}
|
|
|
|
|
// FIXME case sensitivity!
|
|
|
|
|
if ( params_passed.find( varname ) != params_passed.end() )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Variable ", varname, " passed more than once to ", userfunc->name, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Expression& arg_expr = params_passed[varname];
|
|
|
|
|
|
2020-04-28 14:02:16 -07:00
|
|
|
res =
|
|
|
|
|
readexpr( arg_expr, ctx, EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
Token tmp;
|
|
|
|
|
res = peekToken( ctx, tmp );
|
|
|
|
|
if ( res )
|
|
|
|
|
return res;
|
|
|
|
|
if ( tmp.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, tmp );
|
|
|
|
|
}
|
|
|
|
|
else if ( tmp.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Token '", token, "' unexpected (expected comma or right-paren)\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ( UserFunction::Parameters::const_iterator itr = userfunc->parameters.begin();
|
|
|
|
|
itr != userfunc->parameters.end(); ++itr )
|
|
|
|
|
{
|
|
|
|
|
if ( params_passed.find( itr->name ) == params_passed.end() ) // not passed
|
|
|
|
|
{
|
|
|
|
|
if ( itr->have_default )
|
|
|
|
|
{
|
|
|
|
|
ex.CA.push( new Token( itr->dflt_value ) );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Function ", userfunc->name, ": Parameter ", itr->name,
|
|
|
|
|
" was not passed, and there is no default.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Expression& arg_expr = params_passed[itr->name];
|
2020-04-28 14:02:16 -07:00
|
|
|
ex.consume_tokens( arg_expr );
|
2016-02-19 19:26:35 +01:00
|
|
|
params_passed.erase( itr->name );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !params_passed.empty() )
|
|
|
|
|
{
|
|
|
|
|
for ( const auto& elem : params_passed )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Parameter '", elem.first, "' passed by name to ", userfunc->name,
|
|
|
|
|
", which takes no such parameter.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
passert( params_passed.empty() );
|
|
|
|
|
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( token.id != TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
res = -1;
|
|
|
|
|
err = PERR_MISSRPAREN;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( inject_jsr )
|
|
|
|
|
{
|
|
|
|
|
auto t = new Token( CTRL_MAKELOCAL, TYP_CONTROL );
|
|
|
|
|
t->dbg_filenum = ctx.dbg_filenum;
|
|
|
|
|
t->dbg_linenum = ctx.line;
|
|
|
|
|
ex.CA.push( t );
|
|
|
|
|
t = new Token( Mod_Basic, CTRL_JSR_USERFUNC, TYP_CONTROL, userfunc );
|
|
|
|
|
t->dbg_filenum = ctx.dbg_filenum;
|
|
|
|
|
t->dbg_linenum = ctx.line;
|
|
|
|
|
ex.CA.push( t );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::addToken( Token& token )
|
|
|
|
|
{
|
|
|
|
|
program->addToken( token );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::convert_variables( Expression& expr ) const
|
|
|
|
|
{
|
|
|
|
|
for ( auto& tkn : expr.tokens )
|
|
|
|
|
{
|
|
|
|
|
if ( tkn->id == TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
unsigned idx;
|
|
|
|
|
if ( localscope.varexists( tkn->tokval(), idx ) )
|
|
|
|
|
{
|
|
|
|
|
tkn->id = TOK_LOCALVAR;
|
|
|
|
|
tkn->lval = idx;
|
|
|
|
|
}
|
|
|
|
|
else if ( globalexists( tkn->tokval(), idx ) )
|
|
|
|
|
{
|
|
|
|
|
tkn->id = TOK_GLOBALVAR;
|
|
|
|
|
tkn->lval = idx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Compiler::validate( const Expression& expr, CompilerContext& ctx ) const
|
|
|
|
|
{
|
|
|
|
|
for ( unsigned i = 0; i < static_cast<unsigned>( expr.tokens.size() ); i++ )
|
|
|
|
|
{
|
|
|
|
|
Token* tkn = expr.tokens[i];
|
|
|
|
|
|
|
|
|
|
if ( tkn->id == TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
if ( !varexists( tkn->tokval() ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Variable ", tkn->tokval(), " has not been declared on line ", ctx.line,
|
|
|
|
|
".\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ( tkn->id == TOK_EQUAL1 )
|
|
|
|
|
{
|
|
|
|
|
// Single '=' sign? Special error statement (since it could be a typo?)
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Deprecated '=' found: did you mean '==' or ':='?\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-05 13:19:38 +02:00
|
|
|
if ( tkn->deprecated )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( &ctx, "Warning: Found deprecated ",
|
|
|
|
|
( tkn->type == TYP_OPERATOR ? "operator " : "token " ), "'", tkn->tokval(),
|
|
|
|
|
"' on line ", ctx.line, " of ", ctx.filename, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tkn->type == TYP_OPERATOR )
|
|
|
|
|
{
|
|
|
|
|
int right_idx = i - 1;
|
|
|
|
|
if ( right_idx < 0 )
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error( "Unbalanced operator: " + Clib::tostring( *tkn ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int left_idx = right_idx - expr.get_num_tokens( i - 1 );
|
|
|
|
|
if ( left_idx < 0 )
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error( "Unbalanced operator: " + Clib::tostring( *tkn ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tkn->type == TYP_UNARY_OPERATOR )
|
|
|
|
|
{
|
|
|
|
|
int operand_idx = i - 1;
|
|
|
|
|
if ( operand_idx < 0 )
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error( "Unbalanced operator: " + Clib::tostring( *tkn ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Compiler::substitute_constant( Token* tkn ) const
|
|
|
|
|
{
|
|
|
|
|
auto srch = constants.find( tkn->tokval() );
|
|
|
|
|
if ( srch != constants.end() )
|
|
|
|
|
{
|
|
|
|
|
int dbg_filenum = tkn->dbg_filenum;
|
|
|
|
|
int dbg_linenum = tkn->dbg_linenum;
|
|
|
|
|
*tkn = ( *srch ).second;
|
|
|
|
|
tkn->dbg_filenum = dbg_filenum;
|
|
|
|
|
tkn->dbg_linenum = dbg_linenum;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::substitute_constants( Expression& expr ) const
|
|
|
|
|
{
|
|
|
|
|
for ( auto& tkn : expr.tokens )
|
|
|
|
|
{
|
|
|
|
|
if ( tkn->id == TOK_IDENT )
|
|
|
|
|
substitute_constant( tkn );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::readexpr( Expression& expr, CompilerContext& ctx, unsigned flags )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
reinit( expr );
|
|
|
|
|
res = IIP( expr, ctx, flags );
|
|
|
|
|
if ( res != 1 )
|
|
|
|
|
return res;
|
|
|
|
|
while ( !expr.CA.empty() )
|
|
|
|
|
{
|
|
|
|
|
Token* token = expr.CA.front();
|
|
|
|
|
expr.CA.pop();
|
|
|
|
|
expr.tokens.push_back( token );
|
|
|
|
|
}
|
|
|
|
|
if ( ( flags & EXPR_FLAG_CONSUME_RESULT ) && !expr.tokens.empty() )
|
|
|
|
|
{
|
|
|
|
|
auto tkn = new Token( TOK_CONSUMER, TYP_UNARY_OPERATOR );
|
|
|
|
|
expr.tokens.push_back( tkn );
|
|
|
|
|
}
|
|
|
|
|
substitute_constants( expr );
|
|
|
|
|
convert_variables( expr );
|
|
|
|
|
expr.optimize();
|
2020-04-23 19:12:04 -07:00
|
|
|
expr.replace_elvis();
|
2016-02-19 19:26:35 +01:00
|
|
|
res = validate( expr, ctx );
|
|
|
|
|
if ( res != 0 )
|
|
|
|
|
return -1;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-28 17:26:11 -07:00
|
|
|
int Compiler::read_subexpression( Expression& expr, CompilerContext& ctx, unsigned flags )
|
|
|
|
|
{
|
|
|
|
|
Expression subexpression;
|
|
|
|
|
int res = readexpr( subexpression, ctx, flags );
|
|
|
|
|
if ( res >= 0 )
|
|
|
|
|
expr.consume_tokens( subexpression );
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
void Compiler::inject( Expression& expr )
|
|
|
|
|
{
|
|
|
|
|
for ( Expression::Tokens::const_iterator itr = expr.tokens.begin(); itr != expr.tokens.end();
|
|
|
|
|
++itr )
|
|
|
|
|
{
|
|
|
|
|
addToken( *( *itr ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::getExpr( CompilerContext& ctx, unsigned flags, size_t* exprlen, Expression* pex )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
if ( pex )
|
|
|
|
|
{
|
|
|
|
|
res = readexpr( *pex, ctx, flags );
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( exprlen != nullptr )
|
2016-02-19 19:26:35 +01:00
|
|
|
*exprlen = pex->tokens.size();
|
|
|
|
|
inject( *pex );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Expression ex;
|
|
|
|
|
res = readexpr( ex, ctx, flags );
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( exprlen != nullptr )
|
2016-02-19 19:26:35 +01:00
|
|
|
*exprlen = ex.tokens.size();
|
|
|
|
|
inject( ex );
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::getExpr2( CompilerContext& ctx, unsigned expr_flags, Expression* pex )
|
|
|
|
|
{
|
|
|
|
|
int orig_inExpr;
|
|
|
|
|
int res;
|
|
|
|
|
orig_inExpr = inExpr;
|
|
|
|
|
inExpr = 1;
|
2018-10-09 16:36:35 +02:00
|
|
|
res = getExpr( ctx, expr_flags, nullptr, pex );
|
2016-02-19 19:26:35 +01:00
|
|
|
inExpr = orig_inExpr;
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
getExpr3
|
|
|
|
|
get an expression, must be contained in parenthesis.
|
|
|
|
|
*/
|
|
|
|
|
int Compiler::getExprInParens( CompilerContext& ctx, Expression* pex )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( token.id != TOK_LPAREN )
|
|
|
|
|
{
|
|
|
|
|
err = PERR_MISSLPAREN;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int res = getExpr2( ctx, EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED, pex );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
err = PERR_NONE;
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( token.id != TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
err = PERR_MISSRPAREN;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::getSimpleExpr( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
int res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_LPAREN )
|
|
|
|
|
{
|
|
|
|
|
res = getExprInParens( ctx );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_IDENT || token.id == TOK_FUNC || token.id == TOK_USERFUNC ||
|
|
|
|
|
token.id == TOK_ARRAY || token.id == TOK_LBRACE )
|
|
|
|
|
{
|
|
|
|
|
res = getExpr2( ctx, EXPR_FLAG_SINGLE_ELEMENT );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected variable, function or parenthesized expression, got '", token,
|
|
|
|
|
"'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::eatToken( CompilerContext& ctx, BTokenId tokenid )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
int res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( ctx, "Error reading token, expected ", Token( tokenid, TYP_RESERVED ), "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( token.id != tokenid )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( ctx, "Expected ", Token( tokenid, TYP_RESERVED ), ", got ", token, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleDoClause( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "DO clause..\n";
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
unsigned body_start = prog_tokens->next();
|
|
|
|
|
enterblock( CanBeLabelled );
|
|
|
|
|
|
|
|
|
|
Token endblock_tkn;
|
|
|
|
|
int res;
|
2018-10-09 16:36:35 +02:00
|
|
|
res = readblock( ctx, level, RSV_DOWHILE, nullptr, &endblock_tkn );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
emit_leaveblock();
|
|
|
|
|
|
|
|
|
|
program->update_dbg_pos( endblock_tkn );
|
|
|
|
|
|
|
|
|
|
// continue should jump to where the WHILE expression is evaluated,
|
|
|
|
|
// which is the next token after this block
|
|
|
|
|
patchblock_continues( prog_tokens->next() );
|
|
|
|
|
program->setstatementbegin();
|
|
|
|
|
|
|
|
|
|
localscope.popblock( true ); // Pop only variables.
|
|
|
|
|
res = getExpr( ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_JMPIFTRUE, TYP_RESERVED, body_start ) );
|
|
|
|
|
// break should completely exit, of course.
|
|
|
|
|
patchblock_breaks( prog_tokens->next() );
|
|
|
|
|
localscope.popblock(); // Pop block.
|
|
|
|
|
|
|
|
|
|
// do-while loops until its expression evaluates to false.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Compiler::handleRepeatUntil( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "REPEAT clause..\n";
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
unsigned body_start = prog_tokens->next();
|
|
|
|
|
enterblock( CanBeLabelled );
|
|
|
|
|
|
|
|
|
|
Token endblock_tkn;
|
|
|
|
|
int res;
|
2018-10-09 16:36:35 +02:00
|
|
|
res = readblock( ctx, level, RSV_UNTIL, nullptr, &endblock_tkn );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_leaveblock();
|
|
|
|
|
|
|
|
|
|
program->update_dbg_pos( endblock_tkn );
|
|
|
|
|
// continue should jump to where the UNTIL expression is evaluated.
|
|
|
|
|
patchblock_continues( prog_tokens->next() );
|
|
|
|
|
program->setstatementbegin();
|
|
|
|
|
localscope.popblock( true );
|
|
|
|
|
res = getExpr( ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
// repeat-until loops until its expression evaluates to true.
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_JMPIFFALSE, TYP_RESERVED, body_start ) );
|
|
|
|
|
// break should completely exit, of course.
|
|
|
|
|
patchblock_breaks( prog_tokens->next() );
|
|
|
|
|
localscope.popblock();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleSwitch( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
int res = getExprInParens( ctx ); // (expr) (parens required)
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
unsigned default_posn = 0;
|
|
|
|
|
unsigned casecmp_posn = 0;
|
|
|
|
|
std::vector<unsigned> jmpend;
|
|
|
|
|
std::vector<unsigned char> caseblock;
|
|
|
|
|
|
|
|
|
|
program->append( StoredToken( Mod_Basic, INS_CASEJMP, TYP_RESERVED, 0 ), &casecmp_posn );
|
|
|
|
|
|
|
|
|
|
// overview:
|
|
|
|
|
// we grab some case OPTIONs,
|
|
|
|
|
// then we grab some code.
|
|
|
|
|
// until we peek-see an OPTION or an ENDCASE
|
|
|
|
|
bool done = false;
|
|
|
|
|
bool onlydefault = true;
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
while ( !done )
|
|
|
|
|
{
|
|
|
|
|
bool anycases = false;
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_LONG || token.id == CTRL_LABEL || token.id == TOK_STRING )
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
Okay, some trickery. First, we'll handle the 'default' label, 'cause
|
|
|
|
|
it isn't tricky.
|
|
|
|
|
The complication is that sometimes a label is actually a constant.
|
|
|
|
|
*/
|
|
|
|
|
if ( token.id == CTRL_LABEL && stricmp( token.tokval(), "default" ) == 0 )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( default_posn != 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "CASE statement can have only one DEFAULT clause.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
default_posn = prog_tokens->next();
|
|
|
|
|
anycases = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
A label may be a constant, say CONST_VAR: If it is, substitute that value.
|
|
|
|
|
*/
|
|
|
|
|
if ( token.id == CTRL_LABEL )
|
|
|
|
|
{
|
|
|
|
|
substitute_constant( &token );
|
|
|
|
|
/*
|
|
|
|
|
A label that wasn't a constant can't be used in a CASE statement.
|
|
|
|
|
If it's followed by a while, etc, it won't be flagged illegal, though.
|
|
|
|
|
this may be a Bad Thing.
|
|
|
|
|
*/
|
|
|
|
|
if ( token.id == CTRL_LABEL )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
Token dummy; // don't overwrite the token we just substituted
|
|
|
|
|
getToken( ctx, dummy );
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_LONG || token.id == TOK_STRING )
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
If we read a Long or a String, it'll be followed by a colon.
|
|
|
|
|
If it was a label, the colon is gone.
|
|
|
|
|
*/
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
|
|
|
|
|
res = eatToken( ctx, RSV_COLON );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
anycases = true;
|
|
|
|
|
onlydefault = false;
|
|
|
|
|
if ( token.id == TOK_LONG )
|
|
|
|
|
{
|
|
|
|
|
unsigned short offset = static_cast<unsigned short>( prog_tokens->next() );
|
|
|
|
|
unsigned char* tmppch = reinterpret_cast<unsigned char*>( &offset );
|
|
|
|
|
caseblock.push_back( tmppch[0] );
|
|
|
|
|
caseblock.push_back( tmppch[1] );
|
|
|
|
|
caseblock.push_back( CASE_TYPE_LONG ); // FIXME hardcoded
|
|
|
|
|
tmppch = reinterpret_cast<unsigned char*>( &token.lval );
|
|
|
|
|
caseblock.push_back( tmppch[0] );
|
|
|
|
|
caseblock.push_back( tmppch[1] );
|
|
|
|
|
caseblock.push_back( tmppch[2] );
|
|
|
|
|
caseblock.push_back( tmppch[3] );
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == CTRL_LABEL || token.id == TOK_STRING )
|
|
|
|
|
{
|
|
|
|
|
if ( strlen( token.tokval() ) >= 254 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "String expressions in CASE statements must be <= 253 characters.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
unsigned short offset = static_cast<unsigned short>( prog_tokens->next() );
|
|
|
|
|
unsigned char* tmppch = reinterpret_cast<unsigned char*>( &offset );
|
|
|
|
|
caseblock.push_back( tmppch[0] );
|
|
|
|
|
caseblock.push_back( tmppch[1] );
|
|
|
|
|
caseblock.push_back( static_cast<unsigned char>( strlen( token.tokval() ) ) );
|
|
|
|
|
const char* str = token.tokval();
|
|
|
|
|
size_t len = strlen( str );
|
|
|
|
|
|
|
|
|
|
for ( size_t i = 0; i < len; ++i )
|
|
|
|
|
caseblock.push_back( str[i] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == RSV_ENDSWITCH && anycases ) // only accept if OPTIONs exist!
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
done = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{ // something else. we'll assume a statement.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( done )
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// we've grabbed the OPTIONs. Now grab the code, until we get to an OPTION or ENDCASE
|
|
|
|
|
enterblock( CanBeLabelled, BreakOk, ContinueNotOk );
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == RSV_ENDSWITCH )
|
|
|
|
|
{
|
|
|
|
|
// Only accept ENDCASE if OPTIONs exist!
|
|
|
|
|
if ( anycases )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
done = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_LONG )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == CTRL_LABEL )
|
|
|
|
|
{
|
|
|
|
|
if ( stricmp( token.tokval(), "default" ) == 0 )
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
substitute_constant( &token );
|
|
|
|
|
if ( token.id != CTRL_LABEL )
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_STRING )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we're about to grab code. there needs to have been at least one OPTION, then.
|
|
|
|
|
if ( !anycases )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "CASE statement with no options!\n", "Found '", token.tokval(), "'",
|
|
|
|
|
( token.id == CTRL_LABEL ? " but no such constant is defined.\n"
|
|
|
|
|
: " prematurely.\n" ) );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = getStatement( ctx, level );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
// NOTE, the "jump to -> jump to" optimizer will be helpful here, optimizing breaks.
|
|
|
|
|
|
|
|
|
|
emit_leaveblock();
|
|
|
|
|
patchblock_breaks( prog_tokens->next() );
|
|
|
|
|
|
|
|
|
|
localscope.popblock();
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
|
|
|
|
|
if ( !done )
|
|
|
|
|
{
|
|
|
|
|
unsigned jmpend_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_GOTO, TYP_RESERVED, 0 ), &jmpend_posn );
|
|
|
|
|
jmpend.push_back( jmpend_posn );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// patchblock_breaks( program->tokens.next() );
|
|
|
|
|
// program->leaveblock();
|
|
|
|
|
|
|
|
|
|
// if only a 'default' block was defined, print a warning
|
2019-10-05 13:19:38 +02:00
|
|
|
if ( onlydefault )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( &ctx, "Warning: CASE block only has a DEFAULT clause defined.\n",
|
|
|
|
|
"near: ", curLine, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if no default specified, pretend 'default' was specified at the end.
|
|
|
|
|
if ( default_posn == 0 )
|
|
|
|
|
default_posn = prog_tokens->next();
|
|
|
|
|
|
|
|
|
|
// the default case must go at the end.
|
2018-04-03 04:13:03 +02:00
|
|
|
// if (1)
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
|
|
|
|
unsigned char* tmppch = reinterpret_cast<unsigned char*>( &default_posn );
|
|
|
|
|
caseblock.push_back( tmppch[0] );
|
|
|
|
|
caseblock.push_back( tmppch[1] );
|
|
|
|
|
caseblock.push_back( CASE_TYPE_DEFAULT );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while ( !jmpend.empty() )
|
|
|
|
|
{
|
|
|
|
|
unsigned posn = jmpend.back();
|
|
|
|
|
jmpend.pop_back();
|
|
|
|
|
patchoffset( posn, prog_tokens->next() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now, we have to emit the casecmp block.
|
|
|
|
|
unsigned caseblock_posn;
|
|
|
|
|
auto casecmp_raw = new unsigned char[caseblock.size()];
|
|
|
|
|
for ( size_t i = 0; i < caseblock.size(); ++i )
|
|
|
|
|
casecmp_raw[i] = caseblock[i];
|
|
|
|
|
program->symbols.append( casecmp_raw, static_cast<unsigned int>( caseblock.size() ),
|
|
|
|
|
caseblock_posn );
|
|
|
|
|
delete[] casecmp_raw;
|
|
|
|
|
patchoffset( casecmp_posn, caseblock_posn );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleForEach( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
Token itrvar;
|
|
|
|
|
|
|
|
|
|
int res;
|
|
|
|
|
CompilerContext foreach_ctx( ctx );
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, itrvar );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( itrvar.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "FOREACH iterator must be an identifier, got ", itrvar, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = eatToken( ctx, TOK_IN );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
The outer block is a hidden block. It can't be labelled, but the
|
|
|
|
|
inner one can. This way, 'break' and 'continue' won't touch
|
|
|
|
|
the iterator variable, expression, and counter that we have in
|
|
|
|
|
this hidden block.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
res = getSimpleExpr( ctx );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
/*
|
|
|
|
|
When these are evaluated, the value stack should look like this:
|
|
|
|
|
(result of EXPR)
|
|
|
|
|
*/
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
|
|
|
|
|
unsigned initforeach_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, INS_INITFOREACH, TYP_RESERVED, 0 ), &initforeach_posn );
|
|
|
|
|
/*
|
|
|
|
|
INITFOREACH creates three local variables, placeholders for the iterator,
|
|
|
|
|
expression, and counter. Only the iterator can be accessed, for now.
|
|
|
|
|
*/
|
|
|
|
|
program->addlocalvar( itrvar.tokval() );
|
|
|
|
|
localscope.addvar( itrvar.tokval(), foreach_ctx );
|
|
|
|
|
program->addlocalvar( "_" + std::string( itrvar.tokval() ) + "_expr" );
|
|
|
|
|
localscope.addvar( "_" + std::string( itrvar.tokval() ) + "_expr", foreach_ctx, false );
|
|
|
|
|
program->addlocalvar( "_" + std::string( itrvar.tokval() ) + "_counter" );
|
|
|
|
|
localscope.addvar( "_" + std::string( itrvar.tokval() ) + "_iter", foreach_ctx, false );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsigned iter_posn = program->tokens.next();
|
|
|
|
|
enterblock( CanBeLabelled );
|
|
|
|
|
Token endforeach_token;
|
2018-10-09 16:36:35 +02:00
|
|
|
res = readblock( ctx, level, RSV_ENDFOREACH, nullptr, &endforeach_token );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
emit_leaveblock();
|
|
|
|
|
|
|
|
|
|
unsigned stepforeach_posn;
|
|
|
|
|
program->update_dbg_pos( endforeach_token );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, INS_STEPFOREACH, TYP_RESERVED, iter_posn ),
|
|
|
|
|
&stepforeach_posn );
|
|
|
|
|
patchoffset( initforeach_posn, stepforeach_posn );
|
|
|
|
|
|
|
|
|
|
patchblock_continues( stepforeach_posn );
|
|
|
|
|
patchblock_breaks( program->tokens.next() );
|
|
|
|
|
localscope.popblock();
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
|
|
|
|
|
// FIXME this isn't right - continue needs to refer to one block, while break
|
|
|
|
|
// needs to refer to another!
|
|
|
|
|
// ie continue should use the inner block, while break should use the outer.
|
|
|
|
|
leaveblock( 0, 0 );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Compiler::handleReturn( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
int res = peekToken( ctx, token );
|
|
|
|
|
if ( res )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_SEMICOLON )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
unsigned posn = 0;
|
|
|
|
|
program->symbols.append( "", posn );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, TOK_STRING, TYP_OPERAND, posn ) );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
res = getExpr2( ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( inFunction )
|
|
|
|
|
{
|
2018-10-09 16:36:35 +02:00
|
|
|
program->append( StoredToken( Mod_Basic, RSV_RETURN, TYP_RESERVED, 0 ), nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-10-09 16:36:35 +02:00
|
|
|
program->append( StoredToken( Mod_Basic, CTRL_PROGEND, TYP_CONTROL, 0 ), nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleExit( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( token.id != TOK_SEMICOLON )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Missing ';'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
err = PERR_MISSINGDELIM;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_EXIT, TYP_RESERVED, 0 ) );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleBlock( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "BEGIN block..\n";
|
|
|
|
|
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
if ( token.id == RSV_ENDB )
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if ( getStatement( ctx, level ) == -1 )
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( !ctx.s[0] )
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( token.id != RSV_ENDB )
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
leaveblock( 0, 0 );
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::readFunctionDeclaration( CompilerContext& ctx, UserFunction& userfunc )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
Token funcName;
|
|
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
userfunc.ctx = ctx;
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, funcName );
|
|
|
|
|
if ( res )
|
|
|
|
|
return res;
|
|
|
|
|
bool first_time = true;
|
|
|
|
|
if ( first_time )
|
|
|
|
|
{
|
|
|
|
|
if ( funcName.id != TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
if ( funcName.id == TOK_FUNC )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "'", funcName.tokval(), "' is already defined as a function.\n",
|
|
|
|
|
"Near: ", curLine, "\n", ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected an identifier, got ", funcName, " instead.\n", "Near: ", curLine,
|
|
|
|
|
"\n", ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
userfunc.name = funcName.tokval();
|
|
|
|
|
Token lparen;
|
|
|
|
|
res = getToken( ctx, lparen );
|
|
|
|
|
if ( res )
|
|
|
|
|
return res;
|
|
|
|
|
if ( lparen.id != TOK_LPAREN )
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
We have eaten the left paren. Next follows:
|
|
|
|
|
RIGHT_PAREN, OR ( [refto] varname [:= default ] { COMMA or RIGHT_PAREN })
|
|
|
|
|
*/
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
2020-06-15 22:26:33 -07:00
|
|
|
res = getTokenWithoutConversions( ctx, token );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res )
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
bool pass_by_reference = false;
|
|
|
|
|
bool unused = false;
|
|
|
|
|
|
|
|
|
|
if ( token.id == TOK_RPAREN )
|
|
|
|
|
break;
|
|
|
|
|
if ( token.id == TOK_REFTO )
|
|
|
|
|
{
|
|
|
|
|
pass_by_reference = true;
|
2020-06-15 22:26:33 -07:00
|
|
|
res = getTokenWithoutConversions( ctx, token );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res )
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id == TOK_UNUSED )
|
|
|
|
|
{
|
|
|
|
|
unused = true;
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res )
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id != TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
userfunc.parameters.resize( userfunc.parameters.size() + 1 );
|
|
|
|
|
UserParam& param = userfunc.parameters.back();
|
|
|
|
|
param.name = token.tokval();
|
|
|
|
|
param.pass_by_reference = pass_by_reference;
|
|
|
|
|
param.unused = unused;
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
if ( token.id == TOK_ASSIGN )
|
|
|
|
|
{
|
|
|
|
|
// We have a default argument.
|
|
|
|
|
if ( unused )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Default arguments are not allowed in unused parameters\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
param.have_default = 1;
|
|
|
|
|
getToken( ctx, token ); // Eat the assignment operator
|
|
|
|
|
|
|
|
|
|
Expression ex;
|
|
|
|
|
if ( readexpr( ex, ctx, EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED | EXPR_FLAG_COMMA_TERM_ALLOWED ) !=
|
|
|
|
|
1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading expression in const declaration\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ex.tokens.size() != 1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Const expression must be optimizable\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
param.dflt_value = *( ex.tokens.back() );
|
|
|
|
|
if ( param.dflt_value.type != TYP_OPERAND )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "[", funcName.tokval(),
|
|
|
|
|
"]: Only simple operands are allowed as default arguments (", token,
|
|
|
|
|
" is not allowed)\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_EQUAL1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token: '", token, "'. Did you mean := for assign?\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
param.have_default = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool mismatched_end( const Token& token, BTokenId correct )
|
|
|
|
|
{
|
|
|
|
|
if ( token.id == correct )
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == RSV_ENDFOREACH || token.id == RSV_ENDIF ||
|
|
|
|
|
// token.id == RSV_ENDB ||
|
|
|
|
|
token.id == RSV_DOWHILE || token.id == RSV_ENDWHILE || token.id == RSV_UNTIL ||
|
|
|
|
|
token.id == RSV_ENDFOR || token.id == RSV_ENDFUNCTION || token.id == RSV_ENDSWITCH ||
|
|
|
|
|
token.id == RSV_ENDPROGRAM || token.id == RSV_ENDENUM )
|
|
|
|
|
{
|
|
|
|
|
Token t( correct, TYP_RESERVED );
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected ", t, " before ", token, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleBracketedIf( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
CompilerContext save_ctx( ctx );
|
|
|
|
|
|
|
|
|
|
std::vector<unsigned> jumpend;
|
|
|
|
|
|
|
|
|
|
Token token;
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "if clause..\n";
|
|
|
|
|
|
|
|
|
|
token.id = RSV_ST_IF;
|
|
|
|
|
|
|
|
|
|
EScriptProgramCheckpoint checkpt( *program );
|
|
|
|
|
size_t jumpend_size = jumpend.size();
|
|
|
|
|
|
|
|
|
|
bool discard_rest = false;
|
|
|
|
|
// bool discarded_all = true;
|
|
|
|
|
bool included_any_tests = false;
|
|
|
|
|
unsigned if_token_posn = static_cast<unsigned>( -1 );
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
while ( token.id == RSV_ST_IF || token.id == RSV_ELSEIF )
|
|
|
|
|
{
|
|
|
|
|
EScriptProgramCheckpoint checkpt_expr( *program );
|
|
|
|
|
// dump(cout);
|
|
|
|
|
program->setstatementbegin();
|
|
|
|
|
Expression ex;
|
|
|
|
|
int res = getExprInParens( ctx, &ex ); // (expr) (parens required)
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
// dump(cout);
|
|
|
|
|
bool patch_if_token = true;
|
2020-06-14 04:24:49 -07:00
|
|
|
const unsigned last_if_token_posn = if_token_posn;
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( ex.tokens.back()->id == TOK_LOG_NOT )
|
|
|
|
|
{
|
|
|
|
|
if_token_posn = prog_tokens->count() - 1;
|
|
|
|
|
prog_tokens->atPut1( StoredToken( Mod_Basic, RSV_JMPIFTRUE, TYP_RESERVED, 0 ),
|
|
|
|
|
if_token_posn );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_JMPIFFALSE, TYP_RESERVED, 0 ), &if_token_posn );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool discard_this = discard_rest;
|
|
|
|
|
|
|
|
|
|
if ( !discard_rest && ex.tokens.size() == 1 && ex.tokens[0]->id == TOK_LONG )
|
|
|
|
|
{
|
|
|
|
|
if ( ex.tokens[0]->lval )
|
|
|
|
|
{
|
|
|
|
|
discard_rest = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
discard_this = true;
|
|
|
|
|
}
|
|
|
|
|
rollback( *program, checkpt_expr ); // don't need the expression or the jump,
|
|
|
|
|
// even if we're keeping the block
|
|
|
|
|
patch_if_token = false;
|
2020-06-14 04:24:49 -07:00
|
|
|
if_token_posn = last_if_token_posn;
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// discarded_all = discard_rest;
|
|
|
|
|
if ( !discard_rest )
|
|
|
|
|
included_any_tests = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// THEN is optional, currently.
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
if ( token.id == RSV_THEN )
|
|
|
|
|
getToken( ctx, token ); // 'then'
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "then clause..\n";
|
|
|
|
|
|
|
|
|
|
// dump(cout);
|
|
|
|
|
// get the part we do
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
if ( token.id == RSV_ELSEIF || token.id == RSV_ELSE || token.id == RSV_ENDIF )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = getStatement( ctx, level );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in IF statement starting at ", save_ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
leaveblock( 0, 0 );
|
|
|
|
|
if ( !ctx.s[0] )
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
// dump(cout);
|
|
|
|
|
if ( !discard_this )
|
|
|
|
|
{
|
|
|
|
|
checkpt.commit( *program );
|
|
|
|
|
jumpend_size = jumpend.size();
|
|
|
|
|
}
|
|
|
|
|
// dump(cout);
|
|
|
|
|
// dump(cout);
|
|
|
|
|
|
|
|
|
|
// this will be committed only when the next ELSEIF or ELSE is committed
|
|
|
|
|
if ( token.id == RSV_ELSEIF || token.id == RSV_ELSE )
|
|
|
|
|
{
|
|
|
|
|
unsigned temp_posn;
|
|
|
|
|
program->update_dbg_pos( token );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_GOTO, TYP_RESERVED, 0 ), &temp_posn );
|
|
|
|
|
jumpend.push_back( temp_posn );
|
|
|
|
|
|
|
|
|
|
if ( token.id == RSV_ELSEIF )
|
|
|
|
|
{
|
|
|
|
|
readCurLine( ctx );
|
|
|
|
|
savesourceline();
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( patch_if_token )
|
|
|
|
|
{
|
|
|
|
|
StoredToken tkn;
|
|
|
|
|
prog_tokens->atGet1( if_token_posn, tkn );
|
|
|
|
|
tkn.offset = static_cast<unsigned short>( prog_tokens->next() );
|
|
|
|
|
prog_tokens->atPut1( tkn, if_token_posn );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dump(cout);
|
|
|
|
|
if ( discard_this )
|
|
|
|
|
{
|
|
|
|
|
rollback( *program, checkpt );
|
|
|
|
|
while ( jumpend.size() > jumpend_size )
|
|
|
|
|
jumpend.pop_back();
|
|
|
|
|
|
|
|
|
|
if ( last_if_token_posn != static_cast<unsigned>( -1 ) &&
|
|
|
|
|
last_if_token_posn < prog_tokens->count() )
|
|
|
|
|
{
|
|
|
|
|
StoredToken tkn;
|
|
|
|
|
prog_tokens->atGet1( last_if_token_posn, tkn );
|
|
|
|
|
tkn.offset = static_cast<unsigned short>( prog_tokens->next() );
|
|
|
|
|
prog_tokens->atPut1( tkn, last_if_token_posn );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// dump(cout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
if ( token.id != RSV_ENDIF && token.id != RSV_ELSE )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected ELSE or ENDIF after IF statement starting at ", save_ctx,
|
|
|
|
|
"Did not expect: ", token, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if an ELSE follows, grab the ELSE and following statement
|
|
|
|
|
if ( token.id == RSV_ELSE )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token ); // eat the else
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "else clause..\n";
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
peekToken( ctx, token );
|
|
|
|
|
if ( token.id == RSV_ENDIF )
|
|
|
|
|
break;
|
|
|
|
|
if ( mismatched_end( token, RSV_ENDIF ) )
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
int res = getStatement( ctx, level );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
leaveblock( 0, 0 );
|
|
|
|
|
}
|
|
|
|
|
// eat the ENDIF
|
|
|
|
|
if ( !ctx.s[0] )
|
|
|
|
|
return -1;
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
if ( token.id != RSV_ENDIF )
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if ( discard_rest && !included_any_tests )
|
|
|
|
|
{
|
|
|
|
|
rollback( *program, checkpt );
|
|
|
|
|
while ( jumpend.size() > jumpend_size )
|
|
|
|
|
jumpend.pop_back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while ( !jumpend.empty() )
|
|
|
|
|
{
|
|
|
|
|
unsigned pc = jumpend.back();
|
|
|
|
|
jumpend.pop_back();
|
|
|
|
|
// patch up orig. IF token to skip past if false
|
|
|
|
|
|
|
|
|
|
prog_tokens->atPut1( StoredToken( Mod_Basic, RSV_GOTO, TYP_RESERVED, prog_tokens->next() ),
|
|
|
|
|
pc );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Compiler::readblock( CompilerContext& ctx, int level, BTokenId endtokenid,
|
|
|
|
|
BTokenId* last_statement_id, Token* pBlockEndToken )
|
|
|
|
|
{
|
|
|
|
|
CompilerContext tctx( ctx );
|
|
|
|
|
int res;
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == endtokenid )
|
|
|
|
|
{
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( pBlockEndToken != nullptr )
|
2016-02-19 19:26:35 +01:00
|
|
|
getToken( ctx, *pBlockEndToken );
|
|
|
|
|
else
|
|
|
|
|
getToken( ctx, token ); // eat the end-token
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( last_statement_id != nullptr )
|
2016-02-19 19:26:35 +01:00
|
|
|
*last_statement_id = token.id;
|
|
|
|
|
res = getStatement( ctx, level );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in block beginning at ", tctx, "End-of-File detected, expected '",
|
|
|
|
|
Token( Mod_Basic, endtokenid, TYP_RESERVED ), "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleBracketedWhile( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "while clause..\n";
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
unsigned conditional_expr_posn = prog_tokens->next();
|
|
|
|
|
int res = getExprInParens( ctx ); // (expr) (parens required)
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
unsigned test_expr_token_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_JMPIFFALSE, TYP_RESERVED, 0 ),
|
|
|
|
|
&test_expr_token_posn );
|
|
|
|
|
|
|
|
|
|
enterblock( CanBeLabelled );
|
|
|
|
|
|
|
|
|
|
Token endblock_tkn;
|
2018-10-09 16:36:35 +02:00
|
|
|
res = readblock( ctx, level, RSV_ENDWHILE, nullptr, &endblock_tkn );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
program->update_dbg_pos( endblock_tkn );
|
|
|
|
|
emit_leaveblock();
|
|
|
|
|
|
|
|
|
|
// jump back to conditional expression
|
2018-10-09 19:07:53 +02:00
|
|
|
program->append( StoredToken( Mod_Basic, RSV_GOTO, TYP_RESERVED, conditional_expr_posn ),
|
|
|
|
|
nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
// Control should jump past the loop when the expr evaluates to false.
|
|
|
|
|
unsigned exit_loop_posn = prog_tokens->next();
|
|
|
|
|
prog_tokens->atPut1( StoredToken( Mod_Basic, RSV_JMPIFFALSE, TYP_RESERVED, exit_loop_posn ),
|
|
|
|
|
test_expr_token_posn );
|
|
|
|
|
|
|
|
|
|
// continues re-test the expression, which is at the top of the loop.
|
|
|
|
|
patchblock_continues( conditional_expr_posn );
|
|
|
|
|
// breaks exit the loop, which has been wholly emitted by now.
|
|
|
|
|
patchblock_breaks( prog_tokens->count() );
|
|
|
|
|
|
|
|
|
|
localscope.popblock();
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
allowed formats for declare:
|
|
|
|
|
local a;
|
|
|
|
|
local a array;
|
|
|
|
|
local a := 5;
|
|
|
|
|
local a := expr;
|
|
|
|
|
local a, b;
|
|
|
|
|
local a, b := expr, ...;
|
|
|
|
|
*/
|
|
|
|
|
int Compiler::handleVarDeclare( CompilerContext& ctx, unsigned save_id )
|
|
|
|
|
{
|
|
|
|
|
CompilerContext savectx( ctx );
|
|
|
|
|
Token tk_varname, tk_delim;
|
|
|
|
|
int done = 0;
|
|
|
|
|
if ( save_id == RSV_GLOBAL && !inGlobalScope() )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Globals can only be declared at global scope.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( save_id == RSV_LOCAL && inGlobalScope() )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Locals can only be declared within a block or function.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
formats: varname followed by comma or semicolon
|
|
|
|
|
varname followed by "array", then by comma/semicolon
|
|
|
|
|
varname followed by ':=', then an initializer, then comma/semicolon.
|
|
|
|
|
*/
|
|
|
|
|
CompilerContext thisctx( ctx );
|
|
|
|
|
|
|
|
|
|
getToken( ctx, tk_varname );
|
|
|
|
|
if ( tk_varname.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Non-identifier declared as a variable: '", tk_varname.tokval(), "'\n",
|
|
|
|
|
"Token: ", tk_varname, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( constants.find( tk_varname.tokval() ) != constants.end() )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( tk_varname.tokval(), " is already a defined constant.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add this variable to the current block/scope
|
|
|
|
|
unsigned varindex = 0;
|
|
|
|
|
|
|
|
|
|
if ( inGlobalScope() )
|
|
|
|
|
{
|
|
|
|
|
unsigned idx;
|
|
|
|
|
CompilerContext gctx;
|
|
|
|
|
if ( !globalexists( tk_varname.tokval(), idx, &gctx ) )
|
|
|
|
|
{
|
|
|
|
|
Variable v;
|
|
|
|
|
v.name = tk_varname.tokval();
|
|
|
|
|
v.used = true;
|
|
|
|
|
v.ctx = savectx;
|
|
|
|
|
|
|
|
|
|
varindex = static_cast<unsigned>( globals_.size() );
|
|
|
|
|
globals_.push_back( v );
|
|
|
|
|
program->globalvarnames.push_back( tk_varname.tokval() );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Global Variable '", tk_varname.tokval(), "' is already declared at ",
|
|
|
|
|
gctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unsigned idx;
|
2019-10-05 13:19:38 +02:00
|
|
|
if ( globalexists( tk_varname.tokval(), idx ) )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( &ctx, "Warning: Local variable '", tk_varname.tokval(),
|
|
|
|
|
"' hides Global variable of same name.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
varindex = localscope.numVariables();
|
|
|
|
|
program->addlocalvar( tk_varname.tokval() );
|
|
|
|
|
|
|
|
|
|
localscope.addvar( tk_varname.tokval(), ctx );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// grab the comma, semicolon, := or array declare token
|
|
|
|
|
getToken( ctx, tk_delim );
|
|
|
|
|
|
|
|
|
|
// note save_id is RSV_LOCAL or RSV_GLOBAL
|
|
|
|
|
program->append( StoredToken( Mod_Basic, save_id, TYP_RESERVED, varindex ), thisctx );
|
|
|
|
|
|
|
|
|
|
if ( tk_delim.id == TOK_ARRAY )
|
|
|
|
|
{
|
|
|
|
|
// declaring an array.
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( &ctx, "Warning! Deprecated array-declaration syntax used.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
program->append( StoredToken( Mod_Basic, INS_DECLARE_ARRAY, TYP_RESERVED, 0 ) );
|
|
|
|
|
|
|
|
|
|
getToken( ctx, tk_delim );
|
|
|
|
|
}
|
|
|
|
|
else if ( tk_delim.id == TOK_ASSIGN )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
res = getExpr( ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED | EXPR_FLAG_COMMA_TERM_ALLOWED );
|
|
|
|
|
if ( res <= 0 )
|
|
|
|
|
return res;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, TOK_ASSIGN, TYP_OPERATOR, 0 ) );
|
|
|
|
|
|
|
|
|
|
getToken( ctx, tk_delim );
|
|
|
|
|
}
|
|
|
|
|
program->append( StoredToken( Mod_Basic, TOK_CONSUMER, TYP_UNARY_OPERATOR, 0 ) );
|
|
|
|
|
|
|
|
|
|
if ( tk_delim.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if ( tk_delim.id == TOK_SEMICOLON )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if ( tk_delim.id == TOK_EQUAL1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token: '", tk_delim, "'. Did you mean := for assign?\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token: ", tk_delim, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
} while ( !done );
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
2018-01-29 21:55:48 +01:00
|
|
|
// FIXME: Dead code since ages, left here because I have no idea if bug or feature...
|
|
|
|
|
// // insert a consumer to eat the evaluated result from the expr.
|
|
|
|
|
// program->append( StoredToken( Mod_Basic, TOK_CONSUMER, TYP_UNARY_OPERATOR, 0 ) );
|
|
|
|
|
// return 0;
|
2019-10-05 13:19:38 +02:00
|
|
|
} // namespace Bscript
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
allowed formats for declaring const:
|
|
|
|
|
const a := expr;
|
|
|
|
|
*/
|
|
|
|
|
int Compiler::handleConstDeclare( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token tk_varname, tk_assign;
|
|
|
|
|
// int done = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2018-04-03 04:13:03 +02:00
|
|
|
formats: varname followed by comma or semicolon
|
2016-02-19 19:26:35 +01:00
|
|
|
varname followed by "array", then by comma/semicolon
|
|
|
|
|
varname followed by ':=', then an initializer, then comma/semicolon.
|
|
|
|
|
*/
|
|
|
|
|
getToken( ctx, tk_varname );
|
|
|
|
|
if ( tk_varname.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected identifier after const declaration\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( constants.count( tk_varname.tokval() ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Constant ", tk_varname, " has already been defined.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// grab the := token
|
|
|
|
|
getToken( ctx, tk_assign );
|
|
|
|
|
if ( tk_assign.id != TOK_ASSIGN )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected := after identifier in const declaration\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Expression ex;
|
|
|
|
|
if ( readexpr( ex, ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED ) != 1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading expression in const declaration\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ex.tokens.size() != 1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Const expression must be optimizable\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constants.insert( Constants::value_type( tk_varname.tokval(), *ex.tokens.back() ) );
|
|
|
|
|
delete ex.tokens.back();
|
|
|
|
|
ex.tokens.pop_back();
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
allowed formats for declaring enum
|
|
|
|
|
enum FOO
|
|
|
|
|
const a := expr;
|
|
|
|
|
*/
|
|
|
|
|
int Compiler::handleEnumDeclare( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token tk_enum_tag;
|
|
|
|
|
|
|
|
|
|
// First, grab the enum tag.
|
|
|
|
|
// TODO: validate it isn't already used
|
|
|
|
|
|
|
|
|
|
if ( getToken( ctx, tk_enum_tag ) < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading enum tag\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tk_enum_tag.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected an enum tag after 'enum'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int next_counter = 0;
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
Token tk_varname, tk_assign;
|
|
|
|
|
// int done = 0;
|
|
|
|
|
if ( getToken( ctx, tk_varname ) < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading identifier in enum declaration\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tk_varname.id == RSV_ENDENUM )
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
if ( tk_varname.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected identifier in enum statement, got ", tk_varname, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Token tmp;
|
|
|
|
|
// now, the forms. THis should be followed by a comma, an 'endenum', or a ':='
|
|
|
|
|
if ( peekToken( ctx, tmp ) < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading token in enum statement\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( tmp.id == TOK_ASSIGN )
|
|
|
|
|
{
|
|
|
|
|
Token _tmp;
|
|
|
|
|
getToken( ctx, _tmp );
|
|
|
|
|
Expression ex;
|
|
|
|
|
// FIXME doesn't work if expression is right before enum
|
|
|
|
|
if ( readexpr( ex, ctx, EXPR_FLAG_COMMA_TERM_ALLOWED | EXPR_FLAG_ENDENUM_TERM_ALLOWED ) != 1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading expression in enum declaration\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( !peekToken( ctx, _tmp ) )
|
|
|
|
|
{ // might be a comma, or an endenum
|
|
|
|
|
if ( _tmp.id == TOK_COMMA )
|
|
|
|
|
getToken( ctx, _tmp );
|
|
|
|
|
}
|
|
|
|
|
if ( ex.tokens.size() != 1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Enum expression must be optimizable\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
Token* tkn = ex.tokens.back();
|
|
|
|
|
if ( tkn->id == TOK_LONG )
|
|
|
|
|
next_counter = tkn->lval + 1;
|
|
|
|
|
else
|
|
|
|
|
++next_counter;
|
|
|
|
|
constants.insert( Constants::value_type( tk_varname.tokval(), *tkn ) );
|
|
|
|
|
delete tkn;
|
|
|
|
|
ex.tokens.pop_back();
|
|
|
|
|
}
|
|
|
|
|
else if ( tmp.id == TOK_COMMA )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, tmp );
|
|
|
|
|
Token tkn( TOK_LONG, TYP_OPERAND );
|
|
|
|
|
tkn.lval = next_counter++;
|
|
|
|
|
constants.insert( Constants::value_type( tk_varname.tokval(), tkn ) );
|
|
|
|
|
}
|
|
|
|
|
else if ( tmp.id == RSV_ENDENUM )
|
|
|
|
|
{
|
|
|
|
|
Token tkn( TOK_LONG, TYP_OPERAND );
|
|
|
|
|
tkn.lval = next_counter++;
|
|
|
|
|
constants.insert( Constants::value_type( tk_varname.tokval(), tkn ) );
|
|
|
|
|
// we'll pick this one up next pass
|
|
|
|
|
}
|
|
|
|
|
else if ( tmp.id == TOK_EQUAL1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token: '", tmp, "'. Did you mean := for assign?\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unexpected token ", tmp, " in enum statement\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Compiler::useModule( const char* modulename )
|
|
|
|
|
{
|
|
|
|
|
for ( const auto& elem : program->modules )
|
|
|
|
|
{
|
|
|
|
|
if ( modulename == elem->modulename )
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<FunctionalityModule> compmodl( new FunctionalityModule( modulename ) );
|
|
|
|
|
|
|
|
|
|
std::string filename_part = modulename;
|
|
|
|
|
filename_part += ".em";
|
|
|
|
|
|
|
|
|
|
std::string filename_full = current_file_path + filename_part;
|
|
|
|
|
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
if ( !Clib::FileExists( filename_full.c_str() ) )
|
|
|
|
|
{
|
|
|
|
|
std::string try_filename_full = compilercfg.ModuleDirectory + filename_part;
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << try_filename_full << "\n";
|
|
|
|
|
if ( Clib::FileExists( try_filename_full.c_str() ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << try_filename_full << "\n";
|
|
|
|
|
// cout << "Using " << try_filename << endl;
|
|
|
|
|
filename_full = try_filename_full;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << filename_full << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char* orig_mt;
|
|
|
|
|
char* mt;
|
|
|
|
|
|
|
|
|
|
if ( getFileContents( filename_full.c_str(), &orig_mt ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unable to find module ", modulename, "\n", "\t(Filename: ", filename_full,
|
|
|
|
|
")\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mt = orig_mt;
|
|
|
|
|
CompilerContext mod_ctx( filename_full, program->add_dbg_filename( filename_full ), mt );
|
|
|
|
|
|
|
|
|
|
std::string save = current_file_path;
|
|
|
|
|
current_file_path = getpathof( filename_full );
|
|
|
|
|
|
|
|
|
|
int res = -1;
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
Token tk_dummy;
|
|
|
|
|
res = peekToken( mod_ctx, tk_dummy );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading token in module ", modulename, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
free( orig_mt );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if ( res == 1 )
|
|
|
|
|
{
|
|
|
|
|
addModule( compmodl.release() );
|
|
|
|
|
free( orig_mt );
|
|
|
|
|
res = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if ( tk_dummy.id == RSV_CONST )
|
|
|
|
|
{
|
|
|
|
|
getToken( mod_ctx, tk_dummy );
|
|
|
|
|
int _res = handleConstDeclare( mod_ctx );
|
|
|
|
|
if ( _res < 0 )
|
|
|
|
|
break;
|
|
|
|
|
else
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
std::unique_ptr<UserFunction> puserfunc( new UserFunction );
|
|
|
|
|
if ( readFunctionDeclaration( mod_ctx, *puserfunc ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading function declaration in module ", modulename, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
free( orig_mt );
|
|
|
|
|
res = -1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Token tk_semicolon;
|
|
|
|
|
if ( getToken( mod_ctx, tk_semicolon ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( filename_full, ": Error in declaration for ", puserfunc->name, ":\n",
|
|
|
|
|
" Expected a semicolon, got end-of-file or error\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
free( orig_mt );
|
|
|
|
|
res = -1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if ( tk_semicolon.id != TOK_SEMICOLON )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( filename_full, ": Error in declaration for ", puserfunc->name, ":\n",
|
|
|
|
|
" Expected a semicolon, got '", tk_semicolon, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
free( orig_mt );
|
|
|
|
|
res = -1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UserFunction* uf = puserfunc.release();
|
|
|
|
|
compmodl->addFunction( uf->name.c_str(), static_cast<int>( uf->parameters.size() ), uf );
|
|
|
|
|
}
|
|
|
|
|
current_file_path = save;
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Format:
|
|
|
|
|
Use module;
|
|
|
|
|
|
|
|
|
|
Consider: at the beginning of program parsing, peek at the first token.
|
|
|
|
|
While it's a 'Use', handle it. This way, from looking at the top of a program, you
|
|
|
|
|
can tell what modules it uses - a simple way for someone to guage a script's
|
|
|
|
|
capabilities / risks, from a security standpoint.
|
|
|
|
|
*/
|
|
|
|
|
int Compiler::handleUse( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token tk_module_name, tk_semicolon;
|
|
|
|
|
|
|
|
|
|
if ( getToken( ctx, tk_module_name ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in USE statement: USE should be followed by a module name.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( tk_module_name.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in USE statement: Expected identifier, got '", tk_module_name, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( getToken( ctx, tk_semicolon ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in USE statement (module ", tk_module_name,
|
|
|
|
|
"): Expected ';', got end-of-file or error\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( tk_semicolon.id != TOK_SEMICOLON )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in USE statement (module ", tk_module_name, "): Expected ';', got '",
|
|
|
|
|
tk_semicolon, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( strlen( tk_module_name.tokval() ) > 10 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in USE statement: Module names must be <= 10 characters\n",
|
|
|
|
|
"Module specified was: '", tk_module_name, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return useModule( tk_module_name.tokval() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::includeModule( const std::string& modulename )
|
|
|
|
|
{
|
2018-04-03 04:13:03 +02:00
|
|
|
// cout << "includeModule(" << modulename << "). includes(" << included.size() << "):";
|
|
|
|
|
// for( INCLUDES::const_iterator citr = included.begin(); citr != included.end(); ++citr )
|
|
|
|
|
// {
|
|
|
|
|
// cout << " " << (*citr);
|
|
|
|
|
// }
|
|
|
|
|
// cout << endl;
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
std::string filename_part = modulename;
|
|
|
|
|
filename_part += ".inc";
|
|
|
|
|
|
|
|
|
|
std::string filename_full = current_file_path + filename_part;
|
|
|
|
|
|
|
|
|
|
if ( filename_part[0] == ':' )
|
|
|
|
|
{
|
2018-10-09 16:36:35 +02:00
|
|
|
const Plib::Package* pkg = nullptr;
|
2016-02-19 19:26:35 +01:00
|
|
|
std::string path;
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( Plib::pkgdef_split( filename_part, nullptr, &pkg, &path ) )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( pkg != nullptr )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
|
|
|
|
filename_full = pkg->dir() + path;
|
|
|
|
|
std::string try_filename_full = pkg->dir() + "include/" + path;
|
|
|
|
|
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
if ( !Clib::FileExists( filename_full.c_str() ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << try_filename_full << "\n";
|
|
|
|
|
if ( Clib::FileExists( try_filename_full.c_str() ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << try_filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
filename_full = try_filename_full;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
if ( Clib::FileExists( try_filename_full.c_str() ) )
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Warning: Found '", filename_full.c_str(), "' and '",
|
|
|
|
|
try_filename_full.c_str(), "'! Will use first file!\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
filename_full = compilercfg.PolScriptRoot + path;
|
|
|
|
|
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
|
|
|
|
INFO_PRINT << "Searching for " << filename_full << "\n";
|
|
|
|
|
if ( Clib::FileExists( filename_full.c_str() ) )
|
|
|
|
|
INFO_PRINT << "Found " << filename_full << "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unable to read include file '", modulename, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
if ( !Clib::FileExists( filename_full.c_str() ) )
|
|
|
|
|
{
|
|
|
|
|
std::string try_filename_full = compilercfg.IncludeDirectory + filename_part;
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << try_filename_full << "\n";
|
|
|
|
|
if ( Clib::FileExists( try_filename_full.c_str() ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << try_filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
// cout << "Using " << try_filename << endl;
|
|
|
|
|
filename_full = try_filename_full;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << filename_full << "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string filename_check = Clib::FullPath( filename_full.c_str() );
|
|
|
|
|
if ( included.count( filename_check ) )
|
|
|
|
|
return 0;
|
|
|
|
|
included.insert( filename_check );
|
|
|
|
|
|
|
|
|
|
referencedPathnames.push_back( filename_full );
|
|
|
|
|
|
|
|
|
|
char* orig_mt;
|
|
|
|
|
|
|
|
|
|
if ( getFileContents( filename_full.c_str(), &orig_mt ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unable to find module ", modulename, "\n", "\t(Filename: ", filename_full,
|
|
|
|
|
")\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CompilerContext mod_ctx( filename_full, program->add_dbg_filename( filename_full ), orig_mt );
|
|
|
|
|
|
|
|
|
|
std::string save = current_file_path;
|
|
|
|
|
current_file_path = getpathof( filename_full );
|
|
|
|
|
|
|
|
|
|
int res = compileContext( mod_ctx );
|
|
|
|
|
|
|
|
|
|
current_file_path = save;
|
|
|
|
|
|
|
|
|
|
free( orig_mt );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
else
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleInclude( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token tk_module_name, tk_semicolon;
|
|
|
|
|
|
|
|
|
|
if ( getToken( ctx, tk_module_name ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in INCLUDE statement: INCLUDE should be followed by a module name.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( tk_module_name.id != TOK_IDENT && tk_module_name.id != TOK_STRING )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in INCLUDE statement: Expected identifier, got '", tk_module_name,
|
|
|
|
|
"'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( getToken( ctx, tk_semicolon ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in INCLUDE statement (module ", tk_module_name,
|
|
|
|
|
"): Expected ';', got end-of-file or error\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( tk_semicolon.id != TOK_SEMICOLON )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in INCLUDE statement (module ", tk_module_name, "): Expected ';', got '",
|
|
|
|
|
tk_semicolon, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return includeModule( tk_module_name.tokval() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Compiler::insertBreak( const std::string& label )
|
|
|
|
|
{
|
|
|
|
|
// Now, we've eaten the break; or break label; and 'label' contains the label, if any.
|
|
|
|
|
// first, we must find the block level this refers to.
|
|
|
|
|
unsigned numVarsToKill = 0;
|
|
|
|
|
for ( int i = static_cast<int>( localscope.blockdescs_.size() - 1 ); i >= 0; --i )
|
|
|
|
|
{
|
|
|
|
|
BlockDesc& bd = localscope.blockdescs_[i];
|
|
|
|
|
|
|
|
|
|
numVarsToKill += bd.varcount;
|
|
|
|
|
|
|
|
|
|
if ( bd.break_ok && ( label == "" || // we didn't pick, and this is closest
|
|
|
|
|
label == bd.label ) ) // this is the one we picked
|
|
|
|
|
{
|
|
|
|
|
if ( numVarsToKill )
|
|
|
|
|
{ // local variables were declared in this scope. We need to kill 'em.
|
|
|
|
|
|
|
|
|
|
program->append( StoredToken( Mod_Basic, CTRL_LEAVE_BLOCK, TYP_CONTROL, numVarsToKill ),
|
2018-10-09 16:36:35 +02:00
|
|
|
nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned goto_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_GOTO, TYP_RESERVED, 0 ), &goto_posn );
|
|
|
|
|
bd.break_tokens.push_back( goto_posn );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Couldn't find an appropriate break point", ( label != "" ? " for label " : "" ),
|
|
|
|
|
label, ".\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// break statements come in the following forms:
|
2018-04-03 04:13:03 +02:00
|
|
|
// break;
|
|
|
|
|
// break label;
|
2016-02-19 19:26:35 +01:00
|
|
|
// we'll emit a LEAVE_BLOCK token if necessary, and
|
|
|
|
|
// a GOTO which will be patched in when the block
|
|
|
|
|
// is completed.
|
|
|
|
|
int Compiler::handleBreak( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token tk;
|
|
|
|
|
std::string label;
|
|
|
|
|
|
|
|
|
|
if ( getToken( ctx, tk ) || ( ( tk.id != TOK_IDENT ) && ( tk.id != TOK_SEMICOLON ) ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "break statement: expected 'break;' or 'break label;'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tk.id == TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
label = tk.tokval();
|
|
|
|
|
if ( getToken( ctx, tk ) || tk.id != TOK_SEMICOLON )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "break statement: expected 'break;' or 'break label;'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
label = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return insertBreak( label );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// continue statements come in the following forms:
|
2018-04-03 04:13:03 +02:00
|
|
|
// continue;
|
|
|
|
|
// continue label;
|
2016-02-19 19:26:35 +01:00
|
|
|
// we'll emit a LEAVE_BLOCK token if necessary, and
|
|
|
|
|
// a GOTO which will point at the continuePC of the block.
|
|
|
|
|
int Compiler::handleContinue( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token tk;
|
|
|
|
|
std::string label;
|
|
|
|
|
|
|
|
|
|
if ( getToken( ctx, tk ) || ( ( tk.id != TOK_IDENT ) && ( tk.id != TOK_SEMICOLON ) ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "continue statement: expected 'continue;' or 'continue label;'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tk.id == TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
label = tk.tokval();
|
|
|
|
|
if ( getToken( ctx, tk ) || tk.id != TOK_SEMICOLON )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "continue statement: expected 'continue;' or 'continue label;'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
label = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now, we've eaten the continue; or continue label; and 'label' contains the label, if any.
|
|
|
|
|
// first, we must find the block level this refers to.
|
|
|
|
|
unsigned numVarsToKill = 0;
|
|
|
|
|
for ( int i = static_cast<int>( localscope.blockdescs_.size() - 1 ); i >= 0; --i )
|
|
|
|
|
{
|
|
|
|
|
BlockDesc& bd = localscope.blockdescs_[i];
|
|
|
|
|
numVarsToKill += bd.varcount;
|
|
|
|
|
|
|
|
|
|
if ( bd.continue_ok && ( label == "" || // we didn't pick, and this is closest
|
|
|
|
|
label == bd.label ) ) // this is the one we picked
|
|
|
|
|
{
|
|
|
|
|
if ( numVarsToKill )
|
|
|
|
|
{ // local variables were declared in this scope. We need to kill 'em.
|
|
|
|
|
|
|
|
|
|
program->append( StoredToken( Mod_Basic, CTRL_LEAVE_BLOCK, TYP_CONTROL, numVarsToKill ),
|
2018-10-09 16:36:35 +02:00
|
|
|
nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned goto_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_GOTO, TYP_RESERVED, 0 ), &goto_posn );
|
|
|
|
|
bd.continue_tokens.push_back( goto_posn );
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Couldn't find an appropriate continue point",
|
|
|
|
|
( label != "" ? " for label " : "" ), label, ".\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// BASIC-style FOR loop:
|
|
|
|
|
// FOR I := 1 to 5 DO
|
|
|
|
|
// FOR I
|
|
|
|
|
//
|
|
|
|
|
// Emitted Code:
|
|
|
|
|
// (starting expr value)
|
|
|
|
|
// (ending expr value)
|
|
|
|
|
// INITFOR
|
|
|
|
|
// statement_part:
|
|
|
|
|
// (code block)
|
|
|
|
|
// NEXTFOR(statement_part)
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
int Compiler::handleBracketedFor_basic( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
CompilerContext for_ctx( ctx );
|
|
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
Token itrvar, tmptoken;
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, itrvar );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( itrvar.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "FOR iterator must be an identifier, got ", itrvar, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( localscope.varexists( itrvar.tokval() ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "FOR iterator '", itrvar, "' hides a local variable.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = eatToken( ctx, TOK_ASSIGN );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
res = getExpr( ctx, EXPR_FLAG_TO_TERM_ALLOWED );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
res = eatToken( ctx, RSV_TO );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
res = getExpr2( ctx, EXPR_FLAG_AUTO_TERM_ALLOWED );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
unsigned initfor_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, INS_INITFOR, TYP_RESERVED, 0 ), &initfor_posn );
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
INITFOR creates two local variables, placeholders for the iterator
|
|
|
|
|
and end value. Only the iterator can be accessed, for now.
|
|
|
|
|
*/
|
|
|
|
|
program->addlocalvar( itrvar.tokval() );
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 5 )
|
2016-02-19 19:26:35 +01:00
|
|
|
localscope.addvar( itrvar.tokval(), for_ctx );
|
|
|
|
|
else
|
|
|
|
|
localscope.addvar( itrvar.tokval(), for_ctx, false );
|
|
|
|
|
program->addlocalvar( "_" + std::string( itrvar.tokval() ) + "_end" );
|
|
|
|
|
localscope.addvar( "_" + std::string( itrvar.tokval() ) + "_end", for_ctx, false );
|
|
|
|
|
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
unsigned again_posn = prog_tokens->next();
|
|
|
|
|
enterblock( CanBeLabelled );
|
|
|
|
|
Token endblock_tkn;
|
2018-10-09 16:36:35 +02:00
|
|
|
res = readblock( ctx, 1, RSV_ENDFOR, nullptr, &endblock_tkn );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
emit_leaveblock();
|
|
|
|
|
|
|
|
|
|
program->update_dbg_pos( endblock_tkn );
|
|
|
|
|
unsigned nextfor_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, INS_NEXTFOR, TYP_RESERVED, again_posn ), &nextfor_posn );
|
|
|
|
|
|
|
|
|
|
patchblock_continues( nextfor_posn );
|
|
|
|
|
patchblock_breaks( prog_tokens->next() );
|
|
|
|
|
localscope.popblock();
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
|
|
|
|
|
leaveblock( 0, 0 );
|
|
|
|
|
|
|
|
|
|
patchoffset( initfor_posn, prog_tokens->next() );
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleBracketedFor_c( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
Token tkn;
|
|
|
|
|
getToken( ctx, tkn );
|
|
|
|
|
if ( tkn.id != TOK_LPAREN )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "FOR: expected '('\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enterblock( CanBeLabelled );
|
|
|
|
|
|
|
|
|
|
Expression initial_expr;
|
|
|
|
|
Expression predicate_expr;
|
|
|
|
|
Expression iterate_expr;
|
|
|
|
|
if ( readexpr( initial_expr, ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED ) != 1 )
|
|
|
|
|
return -1;
|
|
|
|
|
if ( readexpr( predicate_expr, ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED ) != 1 )
|
|
|
|
|
return -1;
|
|
|
|
|
if ( readexpr( iterate_expr, ctx, EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED ) != 1 )
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
Token tkn;
|
|
|
|
|
if ( getToken( ctx, tkn ) || tkn.id != TOK_RPAREN )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "FOR: expected '('\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
The c-style 'for' statement gets generated as follows:
|
|
|
|
|
given:
|
|
|
|
|
for( initial; predicate; iterate ) statement;
|
|
|
|
|
initial;
|
|
|
|
|
again:
|
|
|
|
|
predicate;
|
|
|
|
|
if true goto statement_part;
|
|
|
|
|
break;
|
|
|
|
|
statement_part:
|
|
|
|
|
statement;
|
|
|
|
|
iterate_part:
|
|
|
|
|
iterate;
|
|
|
|
|
goto again;
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inject( initial_expr );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, TOK_CONSUMER, TYP_UNARY_OPERATOR, 0 ) );
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
unsigned againPC = prog_tokens->next();
|
|
|
|
|
inject( predicate_expr );
|
|
|
|
|
|
|
|
|
|
unsigned if_posn;
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_JMPIFFALSE, TYP_RESERVED, 0 ), &if_posn );
|
|
|
|
|
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
Token endblock_tkn;
|
2018-10-09 16:36:35 +02:00
|
|
|
int res = readblock( ctx, 1, RSV_ENDFOR, nullptr, &endblock_tkn );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
leaveblock( 0, 0 );
|
|
|
|
|
|
|
|
|
|
unsigned continuePC = prog_tokens->next();
|
|
|
|
|
|
|
|
|
|
program->update_dbg_pos( endblock_tkn );
|
|
|
|
|
inject( iterate_expr );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, TOK_CONSUMER, TYP_UNARY_OPERATOR, 0 ) );
|
|
|
|
|
|
2018-10-09 16:36:35 +02:00
|
|
|
program->append( StoredToken( Mod_Basic, RSV_GOTO, TYP_RESERVED, againPC ), nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
patchoffset( if_posn, prog_tokens->next() );
|
|
|
|
|
|
|
|
|
|
leaveblock( prog_tokens->next(), continuePC );
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleFor( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
Token tkn;
|
|
|
|
|
int res;
|
|
|
|
|
res = peekToken( ctx, tkn );
|
|
|
|
|
if ( res )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in FOR statement\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( tkn.id == TOK_LPAREN )
|
|
|
|
|
return handleBracketedFor_c( ctx );
|
|
|
|
|
else
|
|
|
|
|
return handleBracketedFor_basic( ctx );
|
|
|
|
|
}
|
|
|
|
|
void Compiler::emitFileLine( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
int cnt = program->tokens.count();
|
|
|
|
|
program->fileline.resize( cnt + 1 );
|
|
|
|
|
program->fileline[cnt] =
|
2018-10-28 09:31:58 +01:00
|
|
|
ctx.filename + ", Line " + Clib::tostring( static_cast<unsigned int>( ctx.line ) );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
void Compiler::emitFileLineIfFileChanged( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
std::string last;
|
|
|
|
|
if ( program->fileline.size() )
|
|
|
|
|
last = program->fileline.back();
|
|
|
|
|
|
|
|
|
|
if ( program->fileline.size() == program->tokens.count() + 1 || last.empty() ||
|
|
|
|
|
last.substr( 0, ctx.filename.size() ) != ctx.filename )
|
|
|
|
|
{
|
|
|
|
|
emitFileLine( ctx );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void Compiler::savesourceline()
|
|
|
|
|
{
|
|
|
|
|
int cnt = program->tokens.count();
|
|
|
|
|
program->sourcelines.resize( cnt + 1 );
|
|
|
|
|
program->sourcelines[cnt] = curLine;
|
|
|
|
|
}
|
|
|
|
|
int Compiler::_getStatement( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
unsigned last_position = 0;
|
|
|
|
|
|
|
|
|
|
readCurLine( ctx );
|
|
|
|
|
|
|
|
|
|
if ( include_debug )
|
|
|
|
|
{
|
|
|
|
|
program->symbols.append( curLine, last_position );
|
|
|
|
|
|
|
|
|
|
DebugToken DT;
|
|
|
|
|
DT.sourceFile = curSourceFile;
|
|
|
|
|
DT.offset = static_cast<unsigned int>( ctx.s - ctx.s_begin );
|
|
|
|
|
DT.strOffset = last_position;
|
|
|
|
|
|
|
|
|
|
program->symbols.append( &DT, sizeof DT, last_position );
|
|
|
|
|
}
|
|
|
|
|
StoredToken stok( Mod_Basic, CTRL_STATEMENTBEGIN, TYP_CONTROL, last_position );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// reinit();
|
|
|
|
|
|
|
|
|
|
Token token;
|
|
|
|
|
|
|
|
|
|
if ( peekToken( ctx, token ) == 1 )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token ); // trailing whitespace can hurt.
|
|
|
|
|
return 1; // all done!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( token.id != RSV_FUNCTION && token.id != RSV_PROGRAM )
|
|
|
|
|
{
|
|
|
|
|
savesourceline();
|
|
|
|
|
emitFileLineIfFileChanged( ctx );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( include_debug )
|
|
|
|
|
program->append( stok, &last_position );
|
|
|
|
|
else
|
|
|
|
|
last_position = program->tokens.count();
|
|
|
|
|
|
2019-10-05 13:19:38 +02:00
|
|
|
if ( token.deprecated )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( &ctx, "Warning: Found deprecated token '", token.tokval(), "' on line ",
|
|
|
|
|
ctx.line, " of ", ctx.filename, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( token.type == TYP_RESERVED )
|
|
|
|
|
{
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
switch ( token.id )
|
|
|
|
|
{
|
|
|
|
|
case RSV_OPTION_BRACKETED:
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "_OptionBracketed is obsolete.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
// bracketed_if_ = true;
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
case RSV_EXPORTED:
|
|
|
|
|
case RSV_FUNCTION:
|
|
|
|
|
return handleBracketedFunction2( ctx, level, token.type );
|
|
|
|
|
|
|
|
|
|
case RSV_USE_MODULE:
|
|
|
|
|
return handleUse( ctx );
|
|
|
|
|
case RSV_INCLUDE_FILE:
|
|
|
|
|
return handleInclude( ctx );
|
|
|
|
|
|
|
|
|
|
case RSV_PROGRAM:
|
|
|
|
|
return handleProgram( ctx, level + 1 );
|
|
|
|
|
case RSV_RETURN:
|
|
|
|
|
return handleReturn( ctx );
|
|
|
|
|
case RSV_EXIT:
|
|
|
|
|
return handleExit( ctx );
|
|
|
|
|
|
|
|
|
|
case RSV_ST_IF:
|
|
|
|
|
return handleBracketedIf( ctx, level + 1 );
|
|
|
|
|
|
|
|
|
|
case RSV_FOR:
|
|
|
|
|
return handleFor( ctx );
|
|
|
|
|
case RSV_FOREACH:
|
|
|
|
|
return handleForEach( ctx, level + 1 );
|
|
|
|
|
case RSV_SWITCH:
|
|
|
|
|
return handleSwitch( ctx, level + 1 );
|
|
|
|
|
case RSV_REPEAT:
|
|
|
|
|
return handleRepeatUntil( ctx, level + 1 );
|
|
|
|
|
case RSV_DO:
|
|
|
|
|
return handleDoClause( ctx, level + 1 );
|
|
|
|
|
case RSV_WHILE:
|
|
|
|
|
return handleBracketedWhile( ctx, level + 1 );
|
|
|
|
|
|
|
|
|
|
case RSV_BREAK:
|
|
|
|
|
return handleBreak( ctx );
|
|
|
|
|
case RSV_CONTINUE:
|
|
|
|
|
return handleContinue( ctx );
|
|
|
|
|
|
|
|
|
|
case RSV_VAR:
|
|
|
|
|
return handleVarDeclare( ctx, inGlobalScope() ? RSV_GLOBAL : RSV_LOCAL );
|
|
|
|
|
case RSV_CONST:
|
|
|
|
|
return handleConstDeclare( ctx );
|
|
|
|
|
case RSV_ENUM:
|
|
|
|
|
return handleEnumDeclare( ctx );
|
|
|
|
|
|
2018-01-29 21:55:48 +01:00
|
|
|
// DEPRECATED:
|
2018-04-03 04:13:03 +02:00
|
|
|
// case RSV_BEGIN: return handleBlock(ctx, level+1);
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
default:
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unhandled reserved word: ", token, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
2018-04-03 04:13:03 +02:00
|
|
|
// assert(0);
|
2016-02-19 19:26:35 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ( token.type == TYP_LABEL )
|
|
|
|
|
{
|
|
|
|
|
if ( !quiet )
|
|
|
|
|
INFO_PRINT << "Label found! " << token << "\n";
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
|
|
|
|
|
Token precedes;
|
|
|
|
|
res = peekToken( ctx, precedes );
|
|
|
|
|
if ( res != 0 ||
|
|
|
|
|
( precedes.id != RSV_SWITCH && precedes.id != RSV_FOREACH && precedes.id != RSV_REPEAT &&
|
|
|
|
|
precedes.id != RSV_WHILE && precedes.id != RSV_DO && precedes.id != RSV_FOR ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error(
|
|
|
|
|
"Illegal location for label: ", token.tokval(), "\n",
|
|
|
|
|
"Labels can only come before DO, WHILE, FOR, FOREACH, REPEAT, and CASE statements.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
latest_label = token.tokval();
|
|
|
|
|
token.lval = last_position;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t exprlen;
|
|
|
|
|
res = getExpr( ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED | EXPR_FLAG_CONSUME_RESULT, &exprlen );
|
|
|
|
|
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( exprlen != 0 )
|
|
|
|
|
{
|
|
|
|
|
StoredToken tmptoken;
|
|
|
|
|
StoredTokenContainer* prog_tokens = &program->tokens;
|
|
|
|
|
prog_tokens->atGet1( prog_tokens->count() - 1, tmptoken );
|
|
|
|
|
if ( tmptoken.id == TOK_CONSUMER )
|
|
|
|
|
{
|
|
|
|
|
prog_tokens->atGet1( prog_tokens->count() - 2, tmptoken );
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-21 21:33:34 +02:00
|
|
|
switch ( tmptoken.id )
|
|
|
|
|
{
|
|
|
|
|
case TOK_ASSIGN:
|
|
|
|
|
case TOK_PLUSEQUAL:
|
|
|
|
|
case TOK_MINUSEQUAL:
|
|
|
|
|
case TOK_TIMESEQUAL:
|
|
|
|
|
case TOK_DIVIDEEQUAL:
|
|
|
|
|
case TOK_MODULUSEQUAL:
|
|
|
|
|
case INS_SUBSCRIPT_ASSIGN:
|
|
|
|
|
case INS_SUBSCRIPT_ASSIGN_CONSUME:
|
|
|
|
|
case INS_MULTISUBSCRIPT_ASSIGN:
|
|
|
|
|
case INS_ASSIGN_CONSUME:
|
|
|
|
|
case TOK_ADDMEMBER:
|
|
|
|
|
case TOK_DELMEMBER:
|
|
|
|
|
case TOK_FUNC:
|
|
|
|
|
case INS_CALL_METHOD:
|
|
|
|
|
case TOK_USERFUNC:
|
|
|
|
|
case CTRL_JSR_USERFUNC:
|
|
|
|
|
case INS_ASSIGN_LOCALVAR:
|
|
|
|
|
case INS_ASSIGN_GLOBALVAR:
|
|
|
|
|
case INS_SET_MEMBER:
|
|
|
|
|
case INS_SET_MEMBER_CONSUME:
|
|
|
|
|
case INS_SET_MEMBER_ID:
|
|
|
|
|
case INS_SET_MEMBER_ID_CONSUME:
|
|
|
|
|
case INS_CALL_METHOD_ID:
|
|
|
|
|
case INS_SET_MEMBER_ID_CONSUME_PLUSEQUAL:
|
|
|
|
|
case INS_SET_MEMBER_ID_CONSUME_MINUSEQUAL:
|
|
|
|
|
case INS_SET_MEMBER_ID_CONSUME_TIMESEQUAL:
|
|
|
|
|
case INS_SET_MEMBER_ID_CONSUME_DIVIDEEQUAL:
|
|
|
|
|
case INS_SET_MEMBER_ID_CONSUME_MODULUSEQUAL:
|
|
|
|
|
case TOK_UNPLUSPLUS:
|
|
|
|
|
case TOK_UNMINUSMINUS:
|
2019-09-22 11:04:57 +02:00
|
|
|
case TOK_UNPLUSPLUS_POST:
|
|
|
|
|
case TOK_UNMINUSMINUS_POST:
|
2019-09-26 07:06:45 +02:00
|
|
|
case INS_SET_MEMBER_ID_UNPLUSPLUS:
|
|
|
|
|
case INS_SET_MEMBER_ID_UNMINUSMINUS:
|
|
|
|
|
case INS_SET_MEMBER_ID_UNPLUSPLUS_POST:
|
|
|
|
|
case INS_SET_MEMBER_ID_UNMINUSMINUS_POST:
|
2019-09-21 21:33:34 +02:00
|
|
|
// ok! These operators actually accomplish something
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2019-10-05 13:19:38 +02:00
|
|
|
if ( tmptoken.id == TOK_EQUAL1 )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_warning( &ctx,
|
|
|
|
|
"Warning: Equals test result ignored. Did you mean := for assign?\n",
|
|
|
|
|
"near: ", curLine, "\n" );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// warn code has no effect/value lost
|
|
|
|
|
compiler_warning( &ctx, "Warning: Result of operation may have no effect.\n",
|
|
|
|
|
"Token ID: ", tmptoken.id, "\n", "near: ", curLine, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
2019-09-21 21:33:34 +02:00
|
|
|
break;
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
2018-04-03 04:13:03 +02:00
|
|
|
// cout << "Statement: " << Parser.CA << endl;
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
int Compiler::getStatement( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
ctx.skipws();
|
|
|
|
|
ctx.skipcomments();
|
|
|
|
|
|
|
|
|
|
CompilerContext savectx( ctx );
|
|
|
|
|
program->setcontext( savectx );
|
|
|
|
|
program->setstatementbegin();
|
|
|
|
|
int res = 0;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
res = _getStatement( ctx, level );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
|
|
|
|
fmt::Writer _tmp;
|
|
|
|
|
_tmp << "Error compiling statement at ";
|
|
|
|
|
savectx.printOnShort( _tmp );
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( _tmp.str() );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch ( std::exception& ex )
|
|
|
|
|
{
|
|
|
|
|
fmt::Writer _tmp;
|
|
|
|
|
_tmp << "Error compiling statement at ";
|
|
|
|
|
savectx.printOnShort( _tmp );
|
|
|
|
|
_tmp << ex.what() << "\n";
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( _tmp.str() );
|
2016-02-19 19:26:35 +01:00
|
|
|
res = -1;
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pass 2 function: just skip past, to the ENDFUNCTION.
|
|
|
|
|
int Compiler::handleBracketedFunction2( CompilerContext& ctx, int /*level*/, int tokentype )
|
|
|
|
|
{
|
|
|
|
|
CompilerContext save_ctx( ctx );
|
|
|
|
|
int res = -1;
|
|
|
|
|
Token tk_funcname;
|
|
|
|
|
|
|
|
|
|
if ( tokentype == RSV_EXPORTED )
|
|
|
|
|
{
|
|
|
|
|
Token tk_function;
|
|
|
|
|
res = getToken( ctx, tk_function );
|
|
|
|
|
if ( res )
|
|
|
|
|
return res;
|
|
|
|
|
if ( tk_function.id != RSV_FUNCTION )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected 'function' after 'exported'.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( inFunction )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Can't declare a function inside another function.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
getToken( ctx, tk_funcname );
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
break;
|
|
|
|
|
if ( token.id == RSV_ENDFUNCTION )
|
|
|
|
|
{
|
|
|
|
|
// we already grabbed the function body.
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( !ctx.s[0] )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "End-of-File detected, expected 'ENDFUNCTION'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error occurred reading function body for '", tk_funcname.tokval(), "'\n",
|
|
|
|
|
"Function location: ", save_ctx, "Error location: \n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleProgram( CompilerContext& ctx, int /*level*/ )
|
|
|
|
|
{
|
|
|
|
|
Token tk_progname;
|
|
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
if ( haveProgram )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "'program' function has already been defined.\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
haveProgram = true;
|
|
|
|
|
program->program_decl = curLine;
|
|
|
|
|
program_ctx = ctx;
|
|
|
|
|
const char* program_body_start = ctx.s;
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
if ( token.id == RSV_ENDPROGRAM )
|
|
|
|
|
{
|
|
|
|
|
const char* program_body_end = ctx.s;
|
|
|
|
|
size_t len = program_body_end - program_body_start + 1;
|
|
|
|
|
program_source = new char[len];
|
|
|
|
|
delete_these_arrays.push_back( program_source );
|
|
|
|
|
memcpy( program_source, program_body_start, len - 1 );
|
|
|
|
|
program_source[len - 1] = '\0';
|
|
|
|
|
program_ctx.s = program_ctx.s_begin = program_source;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "End of file detected, expected 'endprogram'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleProgram2( CompilerContext& ctx, int level )
|
|
|
|
|
{
|
|
|
|
|
Token tk_progname;
|
|
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
emitFileLine( ctx );
|
|
|
|
|
program->program_PC = program->tokens.count();
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, tk_progname );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( tk_progname.id != TOK_IDENT )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error: expected identified after 'program', got '", tk_progname, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
program->enterfunction();
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
|
|
|
|
|
res = eatToken( ctx, TOK_LPAREN );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
Token token;
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
bool unused = false;
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( res > 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "End-of-file reached reading program argument list\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id == TOK_UNUSED )
|
|
|
|
|
{
|
|
|
|
|
unused = true;
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res )
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( token.id == TOK_RPAREN )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if ( token.id == TOK_IDENT )
|
|
|
|
|
{
|
|
|
|
|
unsigned varpos;
|
|
|
|
|
if ( localscope.varexists( token.tokval(), varpos ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Program argument '", token, "' multiply defined.\n" );
|
2014-08-30 12:25:08 +02:00
|
|
|
return -1;
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
unsigned posn;
|
|
|
|
|
program->symbols.append( token.tokval(), posn );
|
2018-10-09 16:36:35 +02:00
|
|
|
program->append( StoredToken( Mod_Basic, INS_GET_ARG, TYP_OPERATOR, posn ), nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
program->addlocalvar( token.tokval() );
|
|
|
|
|
localscope.addvar( token.tokval(), ctx, true, unused );
|
|
|
|
|
|
|
|
|
|
res = peekToken( ctx, token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == TOK_COMMA )
|
|
|
|
|
getToken( ctx, token );
|
|
|
|
|
++nProgramArgs;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected arguments or right-paren in program arglist, got '", token, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
program->haveProgram = true;
|
|
|
|
|
program->expectedArgs = nProgramArgs;
|
|
|
|
|
Token endblock_tkn;
|
2018-10-09 16:36:35 +02:00
|
|
|
res = readblock( ctx, level, RSV_ENDPROGRAM, nullptr, &endblock_tkn );
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
program->update_dbg_pos( endblock_tkn );
|
|
|
|
|
leaveblock( 0, 0 );
|
|
|
|
|
program->leavefunction();
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::handleBracketedFunction3( UserFunction& userfunc, CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
StoredTokenContainer* program_tokens = &program->tokens;
|
|
|
|
|
unsigned first_PC = program_tokens->count();
|
|
|
|
|
|
|
|
|
|
emitFileLine( ctx );
|
|
|
|
|
program->function_decls.resize( program_tokens->count() + 1 );
|
|
|
|
|
program->function_decls[program_tokens->count()] = userfunc.declaration;
|
|
|
|
|
CompilerContext save_ctx( ctx );
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
|
|
|
|
|
inFunction = 1;
|
|
|
|
|
/*
|
|
|
|
|
should be begin, then statements while peektoken != end, then eat end.
|
|
|
|
|
|
|
|
|
|
getToken(s, token);
|
|
|
|
|
if (token.type != TYP_DELIMITER || token.id != DELIM_SEMICOLON) {
|
|
|
|
|
err = PERR_MISSINGDELIM;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
/* woo-hoo! recursive calls should work. */
|
|
|
|
|
// cout << "func decl: " << curLine << endl;
|
|
|
|
|
unsigned posn = 0;
|
|
|
|
|
|
|
|
|
|
if ( userfunc.exported )
|
|
|
|
|
{
|
|
|
|
|
EPExportedFunction ef;
|
|
|
|
|
ef.name = userfunc.name;
|
|
|
|
|
ef.nargs = static_cast<unsigned int>( userfunc.parameters.size() );
|
|
|
|
|
ef.PC = program_tokens->count();
|
|
|
|
|
program->exported_functions.push_back( ef );
|
|
|
|
|
|
|
|
|
|
// insert the stub:
|
|
|
|
|
program->append( StoredToken( Mod_Basic, CTRL_MAKELOCAL, TYP_CONTROL, 0 ), ctx );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, CTRL_JSR_USERFUNC, TYP_CONTROL, ef.PC + 3 ), ctx );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, CTRL_PROGEND, TYP_CONTROL, 0 ), ctx );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userfunc.position = posn = program_tokens->count();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
program->enterfunction();
|
|
|
|
|
enterblock( CanNotBeLabelled );
|
|
|
|
|
|
|
|
|
|
for ( int i = static_cast<unsigned int>( userfunc.parameters.size() - 1 ); i >= 0; --i )
|
|
|
|
|
{
|
|
|
|
|
UserParam* params = &userfunc.parameters[i];
|
|
|
|
|
program->symbols.append( params->name.c_str(), posn );
|
|
|
|
|
program->append(
|
|
|
|
|
StoredToken( Mod_Basic, params->pass_by_reference ? INS_POP_PARAM_BYREF : INS_POP_PARAM,
|
|
|
|
|
TYP_OPERATOR, posn ),
|
2018-10-09 16:36:35 +02:00
|
|
|
save_ctx, nullptr );
|
2016-02-19 19:26:35 +01:00
|
|
|
program->addlocalvar( params->name );
|
|
|
|
|
localscope.addvar( params->name, ctx, true, params->unused );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BTokenId last_statement_id;
|
|
|
|
|
Token endblock_tkn;
|
|
|
|
|
res = readblock( ctx, 1, RSV_ENDFUNCTION, &last_statement_id, &endblock_tkn );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in function '", userfunc.name, "', ", ctx, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
program->update_dbg_pos( endblock_tkn );
|
|
|
|
|
|
|
|
|
|
StoredToken tmp;
|
|
|
|
|
program_tokens->atGet1( program_tokens->count() - 1, tmp );
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
This used to check to see if the last instruction was a RETURN;
|
|
|
|
|
however, if that's in an ELSE block, that return won't get executed.
|
|
|
|
|
This means if the function really does end in a return, this will
|
|
|
|
|
generate extra code. The optimizer will have to take care of this,
|
|
|
|
|
if it can.
|
|
|
|
|
Also note, the "leaveblock" at the end will also generate an instruction
|
|
|
|
|
that will never get executed (NEVER!)
|
|
|
|
|
*/
|
|
|
|
|
if ( last_statement_id != RSV_RETURN )
|
|
|
|
|
{
|
|
|
|
|
program->symbols.append( int( 0 ), posn );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, TOK_LONG, TYP_OPERAND, posn ) );
|
|
|
|
|
program->append( StoredToken( Mod_Basic, RSV_RETURN, TYP_RESERVED, 0 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// was leaveblock(0,0)
|
|
|
|
|
localscope.popblock();
|
|
|
|
|
program->leaveblock();
|
|
|
|
|
program->leavefunction();
|
|
|
|
|
|
|
|
|
|
unsigned last_PC = program_tokens->count() - 1;
|
|
|
|
|
program->addfunction( userfunc.name, first_PC, last_PC );
|
|
|
|
|
|
|
|
|
|
inFunction = 0;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::forward_read_function( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
CompilerContext save_ctx( ctx );
|
|
|
|
|
int res;
|
|
|
|
|
Token token;
|
|
|
|
|
|
|
|
|
|
UserFunction userfunc;
|
|
|
|
|
userfunc.declaration = curLine;
|
|
|
|
|
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id == RSV_EXPORTED )
|
|
|
|
|
{
|
|
|
|
|
userfunc.exported = true;
|
|
|
|
|
res = getToken( ctx, token );
|
|
|
|
|
if ( res )
|
|
|
|
|
return res;
|
|
|
|
|
if ( token.id != RSV_FUNCTION )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Expected 'function' after 'exported'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( readFunctionDeclaration( ctx, userfunc ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( save_ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
userfunc.ctx = ctx;
|
|
|
|
|
|
|
|
|
|
const char* function_body_start = ctx.s;
|
|
|
|
|
while ( ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
Token _token;
|
|
|
|
|
res = getToken( ctx, _token );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
break;
|
|
|
|
|
if ( _token.id == RSV_ENDFUNCTION )
|
|
|
|
|
{
|
|
|
|
|
const char* function_body_end = ctx.s;
|
|
|
|
|
size_t len = function_body_end - function_body_start + 1;
|
|
|
|
|
userfunc.function_body = new char[len];
|
|
|
|
|
delete_these_arrays.push_back( userfunc.function_body );
|
|
|
|
|
memcpy( userfunc.function_body, function_body_start, len - 1 );
|
|
|
|
|
userfunc.function_body[len - 1] = '\0';
|
|
|
|
|
userfunc.ctx.s = userfunc.ctx.s_begin = userfunc.function_body;
|
|
|
|
|
userFunctions[userfunc.name] = userfunc;
|
|
|
|
|
res = 0;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( !ctx.s[0] )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "End-of-File detected, expected 'ENDFUNCTION'\n", save_ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error occurred reading function body for '", userfunc.name, "'\n",
|
|
|
|
|
"Function location: ", save_ctx, "Error location: \n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Compiler::patchoffset( unsigned instruc, unsigned newoffset )
|
|
|
|
|
{
|
|
|
|
|
StoredToken tkn;
|
|
|
|
|
|
|
|
|
|
program->tokens.atGet1( instruc, tkn );
|
|
|
|
|
tkn.offset = static_cast<unsigned short>( newoffset );
|
|
|
|
|
program->tokens.atPut1( tkn, instruc );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Compiler::compileContext( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
int res = 0;
|
|
|
|
|
// scopes.push( LocalScope() );
|
|
|
|
|
// currentscope = &scopes.top();
|
|
|
|
|
// currentscope = &scope_;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while ( ( res >= 0 ) && ctx.s[0] )
|
|
|
|
|
{
|
|
|
|
|
res = getStatement( ctx, 0 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch ( std::exception& )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Exception detected during compilation.\n", ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-09 16:36:35 +02:00
|
|
|
// currentscope = nullptr;
|
2016-02-19 19:26:35 +01:00
|
|
|
// scopes.pop();
|
|
|
|
|
// assert( scopes.empty() );
|
|
|
|
|
|
|
|
|
|
if ( res == -1 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
fmt::Writer w;
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( err || ext_err[0] )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
w << "Parse Error: " << ParseErrorStr[err];
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( ext_err[0] )
|
2019-10-05 13:19:38 +02:00
|
|
|
w << " " << ext_err;
|
|
|
|
|
w << "\n";
|
2016-02-19 19:26:35 +01:00
|
|
|
err = PERR_NONE;
|
|
|
|
|
ext_err[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
w << "Compilation Error:\n";
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
if ( curLine[0] )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
w << "Near: " << curLine << "\n";
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( w.str(), ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::compile( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
unsigned dummy = 0;
|
|
|
|
|
curSourceFile = 0;
|
|
|
|
|
// startPos = ctx.s;
|
|
|
|
|
program->symbols.append( "", dummy );
|
|
|
|
|
|
|
|
|
|
// see comment by handleUse: may want to only allow use statements at beginning of program->
|
|
|
|
|
// useModule( "implicit" );
|
|
|
|
|
useModule( "basic" );
|
|
|
|
|
useModule( "basicio" );
|
|
|
|
|
|
|
|
|
|
int res = 0;
|
|
|
|
|
res = compileContext( ctx );
|
|
|
|
|
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
// reinit();
|
|
|
|
|
|
|
|
|
|
if ( haveProgram )
|
|
|
|
|
{
|
|
|
|
|
// the local frame should be empty, so we can use it.
|
|
|
|
|
// program->append( StoredToken( Mod_Basic, CTRL_MAKELOCAL, TYP_CONTROL, 0 ) );
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
res = handleProgram2( program_ctx, 1 );
|
|
|
|
|
}
|
|
|
|
|
catch ( std::runtime_error& excep )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( excep.what(), "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
res = -1;
|
|
|
|
|
}
|
|
|
|
|
catch ( ... )
|
|
|
|
|
{
|
|
|
|
|
res = -1;
|
|
|
|
|
}
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
|
|
|
|
fmt::Writer _tmp;
|
|
|
|
|
_tmp << "Error detected in program body.\n"
|
|
|
|
|
<< "Error occurred at ";
|
|
|
|
|
program_ctx.printOnShort( _tmp );
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( _tmp.str() );
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// program->append( StoredToken( Mod_Basic, CTRL_JSR_USERFUNC, TYP_CONTROL, programPos ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned last_posn;
|
|
|
|
|
StoredToken tkn( Mod_Basic, CTRL_PROGEND, TYP_CONTROL, 0 );
|
|
|
|
|
program->append( tkn, &last_posn );
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// what am I, too good for stdio/ftell? geez...
|
|
|
|
|
// rope getline
|
|
|
|
|
int Compiler::getFileContents( const char* file, char** iv )
|
|
|
|
|
{
|
2019-10-07 21:01:26 +02:00
|
|
|
// linux fails always in the call before
|
2016-02-19 19:26:35 +01:00
|
|
|
#ifdef _WIN32
|
2019-10-07 21:01:26 +02:00
|
|
|
std::string truename = Clib::GetTrueName( file );
|
|
|
|
|
std::string filepart = Clib::GetFilePart( file );
|
|
|
|
|
if ( truename != filepart && Clib::FileExists( file ) )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-07 21:01:26 +02:00
|
|
|
if ( compilercfg.ErrorOnFileCaseMissmatch )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Case mismatch: \n", " Specified: ", filepart, "\n",
|
|
|
|
|
" Filesystem: ", truename, "\n" );
|
2019-10-07 21:01:26 +02:00
|
|
|
return -1;
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
2019-10-07 21:12:45 +02:00
|
|
|
compiler_warning( nullptr, "Case mismatch: \n", " Specified: ", filepart, "\n",
|
2019-10-07 21:01:26 +02:00
|
|
|
" Filesystem: ", truename, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
2018-10-09 16:36:35 +02:00
|
|
|
char* s = nullptr;
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
FILE* fp = fopen( file, "rb" );
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( fp == nullptr )
|
2016-02-19 19:26:35 +01:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
// Goes to the end of file
|
|
|
|
|
if ( fseek( fp, 0, SEEK_END ) != 0 )
|
|
|
|
|
{
|
|
|
|
|
fclose( fp );
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// in order to measure its size
|
|
|
|
|
int filelen = ftell( fp );
|
|
|
|
|
if ( filelen < 0 )
|
|
|
|
|
{
|
|
|
|
|
fclose( fp );
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// and then return to beginning
|
|
|
|
|
if ( fseek( fp, 0, SEEK_SET ) != 0 )
|
|
|
|
|
{
|
|
|
|
|
fclose( fp );
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2018-05-21 14:44:49 +02:00
|
|
|
if ( filelen >= 3 )
|
|
|
|
|
{
|
|
|
|
|
char bom[3];
|
|
|
|
|
if ( fread( bom, 1, 3, fp ) != 3 )
|
|
|
|
|
{
|
|
|
|
|
fclose( fp );
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( !utf8::starts_with_bom( bom, bom + 3 ) )
|
|
|
|
|
{
|
|
|
|
|
if ( fseek( fp, 0, SEEK_SET ) != 0 )
|
|
|
|
|
{
|
|
|
|
|
fclose( fp );
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
filelen -= 3;
|
|
|
|
|
}
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
s = (char*)calloc( 1, filelen + 1 );
|
|
|
|
|
if ( !s )
|
|
|
|
|
{
|
|
|
|
|
fclose( fp );
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-21 14:44:49 +02:00
|
|
|
if ( fread( s, filelen, 1, fp ) != 1 )
|
2018-04-27 23:08:26 +02:00
|
|
|
{
|
|
|
|
|
fclose( fp );
|
2018-11-01 17:57:03 +01:00
|
|
|
free( s );
|
2018-04-27 23:08:26 +02:00
|
|
|
return -1;
|
|
|
|
|
}
|
2016-02-19 19:26:35 +01:00
|
|
|
|
|
|
|
|
fclose( fp );
|
|
|
|
|
*iv = s;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Compiler::read_function_declarations_in_included_file( const char* modulename )
|
|
|
|
|
{
|
|
|
|
|
std::string filename_part = modulename;
|
|
|
|
|
filename_part += ".inc";
|
|
|
|
|
|
|
|
|
|
std::string filename_full = current_file_path + filename_part;
|
|
|
|
|
|
|
|
|
|
if ( filename_part[0] == ':' )
|
|
|
|
|
{
|
2018-10-09 16:36:35 +02:00
|
|
|
const Plib::Package* pkg = nullptr;
|
2016-02-19 19:26:35 +01:00
|
|
|
std::string path;
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( Plib::pkgdef_split( filename_part, nullptr, &pkg, &path ) )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
2018-10-09 16:36:35 +02:00
|
|
|
if ( pkg != nullptr )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
|
|
|
|
filename_full = pkg->dir() + path;
|
|
|
|
|
std::string try_filename_full = pkg->dir() + "include/" + path;
|
|
|
|
|
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2014-08-30 12:25:08 +02:00
|
|
|
INFO_PRINT << "Searching for " << filename_full << "\n";
|
|
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( !Clib::FileExists( filename_full.c_str() ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2014-08-30 12:25:08 +02:00
|
|
|
INFO_PRINT << "Searching for " << try_filename_full << "\n";
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( Clib::FileExists( try_filename_full.c_str() ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2014-08-30 12:25:08 +02:00
|
|
|
INFO_PRINT << "Found " << try_filename_full << "\n";
|
|
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
filename_full = try_filename_full;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2014-08-30 12:25:08 +02:00
|
|
|
INFO_PRINT << "Found " << filename_full << "\n";
|
|
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( Clib::FileExists( try_filename_full.c_str() ) )
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Warning: Found '", filename_full.c_str(), "' and '",
|
|
|
|
|
try_filename_full.c_str(), "'! Will use first file!\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
filename_full = compilercfg.PolScriptRoot + path;
|
|
|
|
|
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
|
|
|
|
INFO_PRINT << "Searching for " << filename_full << "\n";
|
|
|
|
|
if ( Clib::FileExists( filename_full.c_str() ) )
|
|
|
|
|
INFO_PRINT << "Found " << filename_full << "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unable to read include file '", modulename, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
if ( !Clib::FileExists( filename_full.c_str() ) )
|
|
|
|
|
{
|
|
|
|
|
std::string try_filename_full = compilercfg.IncludeDirectory + filename_part;
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Searching for " << try_filename_full << "\n";
|
|
|
|
|
if ( Clib::FileExists( try_filename_full.c_str() ) )
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << try_filename_full << "\n";
|
|
|
|
|
|
|
|
|
|
filename_full = try_filename_full;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 10 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "Found " << filename_full << "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string filename_check = Clib::FullPath( filename_full.c_str() );
|
|
|
|
|
if ( included.count( filename_check ) )
|
|
|
|
|
return true;
|
|
|
|
|
included.insert( filename_check );
|
|
|
|
|
|
|
|
|
|
char* orig_mt;
|
|
|
|
|
|
|
|
|
|
if ( getFileContents( filename_full.c_str(), &orig_mt ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Unable to read include file '", filename_full, "'\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CompilerContext mod_ctx( filename_full, program->add_dbg_filename( filename_full ), orig_mt );
|
|
|
|
|
|
|
|
|
|
std::string save = current_file_path;
|
|
|
|
|
current_file_path = getpathof( filename_full );
|
|
|
|
|
|
|
|
|
|
bool res = inner_read_function_declarations( mod_ctx );
|
|
|
|
|
|
|
|
|
|
current_file_path = save;
|
|
|
|
|
|
|
|
|
|
free( orig_mt );
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
void Compiler::readCurLine( CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
ctx.skipws();
|
|
|
|
|
ctx.skipcomments();
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
Clib::stracpy( curLine, ctx.s, sizeof curLine );
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
char* t;
|
|
|
|
|
t = strchr( curLine, '\r' );
|
|
|
|
|
if ( t )
|
|
|
|
|
t[0] = '\0';
|
|
|
|
|
t = strchr( curLine, '\n' );
|
|
|
|
|
if ( t )
|
|
|
|
|
t[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Compiler::inner_read_function_declarations( const CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
CompilerContext tctx( ctx );
|
2019-01-12 11:46:30 +01:00
|
|
|
tctx.silence_unicode_warnings = true;
|
2016-02-19 19:26:35 +01:00
|
|
|
Token tkn;
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
readCurLine( tctx );
|
|
|
|
|
|
|
|
|
|
CompilerContext save_ctx( tctx );
|
|
|
|
|
if ( getToken( tctx, tkn ) != 0 )
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ( tkn.id == RSV_CONST )
|
|
|
|
|
{
|
|
|
|
|
if ( handleConstDeclare( tctx ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in const declaration\n", tctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ( tkn.id == RSV_ENUM )
|
|
|
|
|
{
|
|
|
|
|
if ( handleEnumDeclare( tctx ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in enum declaration\n", tctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ( tkn.id == RSV_FUNCTION || tkn.id == RSV_EXPORTED )
|
|
|
|
|
{
|
|
|
|
|
tctx = save_ctx;
|
|
|
|
|
if ( forward_read_function( tctx ) )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error reading function\n", tctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
return false;
|
2014-09-28 13:44:57 +02:00
|
|
|
}
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
else if ( tkn.id == RSV_INCLUDE_FILE )
|
|
|
|
|
{
|
|
|
|
|
Token tk_module_name;
|
|
|
|
|
|
|
|
|
|
if ( getToken( tctx, tk_module_name ) == 0 &&
|
|
|
|
|
( tk_module_name.id == TOK_IDENT || tk_module_name.id == TOK_STRING ) )
|
|
|
|
|
{
|
|
|
|
|
if ( !read_function_declarations_in_included_file( tk_module_name.tokval() ) )
|
|
|
|
|
{
|
|
|
|
|
// read.. prints out an error message
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2014-09-28 13:44:57 +02:00
|
|
|
}
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
else if ( tkn.id == RSV_USE_MODULE )
|
|
|
|
|
{
|
|
|
|
|
Token tk_module_name;
|
|
|
|
|
if ( getToken( tctx, tk_module_name ) == 0 &&
|
|
|
|
|
( tk_module_name.id == TOK_IDENT || tk_module_name.id == TOK_STRING ) )
|
|
|
|
|
{
|
|
|
|
|
int res = useModule( tk_module_name.tokval() );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Compiler::read_function_declarations( const CompilerContext& ctx )
|
|
|
|
|
{
|
|
|
|
|
bool res = inner_read_function_declarations( ctx );
|
|
|
|
|
included.clear();
|
|
|
|
|
constants.clear();
|
|
|
|
|
program->clear_modules();
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::emit_function( UserFunction& uf )
|
|
|
|
|
{
|
2020-08-05 00:01:07 -07:00
|
|
|
userfunc_emit_order.push_back( uf.name );
|
2016-02-19 19:26:35 +01:00
|
|
|
CompilerContext ctx( uf.ctx );
|
|
|
|
|
// cout << "emitting " << uf.name << ": " << program->tokens.next() << endl;
|
|
|
|
|
int res = handleBracketedFunction3( uf, ctx );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Error in function '", uf.name, "'.\n", ctx );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2014-09-28 13:44:57 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
void Compiler::patch_callers( UserFunction& uf )
|
|
|
|
|
{
|
|
|
|
|
for ( unsigned i = 0; i < uf.forward_callers.size(); ++i )
|
|
|
|
|
{
|
|
|
|
|
patchoffset( uf.forward_callers[i], uf.position );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Compiler::emit_functions()
|
|
|
|
|
{
|
|
|
|
|
bool any;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
any = false;
|
|
|
|
|
for ( auto& elem : userFunctions )
|
|
|
|
|
{
|
|
|
|
|
UserFunction& uf = elem.second;
|
|
|
|
|
if ( ( uf.exported || compiling_include || !uf.forward_callers.empty() ) && !uf.emitted )
|
|
|
|
|
{
|
|
|
|
|
int res = emit_function( uf );
|
|
|
|
|
if ( res < 0 )
|
|
|
|
|
return res;
|
|
|
|
|
uf.emitted = any = true;
|
2014-09-28 13:44:57 +02:00
|
|
|
}
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
} while ( any );
|
|
|
|
|
|
|
|
|
|
for ( auto& elem : userFunctions )
|
|
|
|
|
{
|
|
|
|
|
UserFunction& uf = elem.second;
|
|
|
|
|
if ( uf.emitted )
|
|
|
|
|
{
|
|
|
|
|
patch_callers( uf );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// cout << "not emitted: " << uf.name << endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
void Compiler::rollback( EScriptProgram& prog, const EScriptProgramCheckpoint& checkpoint )
|
|
|
|
|
{
|
|
|
|
|
while ( prog.modules.size() > checkpoint.module_count )
|
|
|
|
|
{
|
|
|
|
|
delete prog.modules.back();
|
|
|
|
|
prog.modules.pop_back();
|
|
|
|
|
}
|
|
|
|
|
prog.tokens.setcount( checkpoint.tokens_count );
|
|
|
|
|
prog.symbols.setlength( checkpoint.symbols_length );
|
|
|
|
|
while ( prog.sourcelines.size() > checkpoint.sourcelines_count )
|
|
|
|
|
prog.sourcelines.pop_back();
|
|
|
|
|
while ( prog.fileline.size() > checkpoint.fileline_count )
|
|
|
|
|
prog.fileline.pop_back();
|
|
|
|
|
while ( prog.dbg_filenum.size() > checkpoint.tokens_count )
|
|
|
|
|
{
|
|
|
|
|
prog.dbg_filenum.pop_back();
|
|
|
|
|
prog.dbg_linenum.pop_back();
|
|
|
|
|
prog.dbg_ins_blocks.pop_back();
|
|
|
|
|
prog.dbg_ins_statementbegin.pop_back();
|
|
|
|
|
prog.statementbegin = false;
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
for ( auto& elem : userFunctions )
|
|
|
|
|
{
|
|
|
|
|
UserFunction& uf = elem.second;
|
|
|
|
|
while ( !uf.forward_callers.empty() && uf.forward_callers.back() >= checkpoint.tokens_count )
|
|
|
|
|
{
|
|
|
|
|
uf.forward_callers.pop_back();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
/**
|
|
|
|
|
* Given a file name, tells if this is a web script
|
|
|
|
|
*/
|
|
|
|
|
bool is_web_script( const char* file )
|
|
|
|
|
{
|
|
|
|
|
const char* ext = strstr( file, ".hsr" );
|
|
|
|
|
if ( ext && memcmp( ext, ".hsr", 5 ) == 0 )
|
|
|
|
|
return true;
|
|
|
|
|
ext = strstr( file, ".asp" );
|
|
|
|
|
if ( ext && memcmp( ext, ".asp", 5 ) == 0 )
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
/**
|
|
|
|
|
* Transforms the raw html page into a script with a single WriteHtml() instruction
|
|
|
|
|
*/
|
2020-07-02 19:46:21 -07:00
|
|
|
std::string preprocess_web_script( const std::string& input )
|
2016-02-19 19:26:35 +01:00
|
|
|
{
|
|
|
|
|
std::string output;
|
|
|
|
|
output = "use http;";
|
|
|
|
|
output += '\n';
|
|
|
|
|
|
|
|
|
|
bool reading_html = true;
|
|
|
|
|
bool source_is_emit = false;
|
2020-07-02 19:46:21 -07:00
|
|
|
const char* s = input.c_str();
|
2016-02-19 19:26:35 +01:00
|
|
|
std::string acc;
|
|
|
|
|
while ( *s )
|
|
|
|
|
{
|
|
|
|
|
if ( reading_html )
|
|
|
|
|
{
|
|
|
|
|
if ( s[0] == '<' && s[1] == '%' )
|
|
|
|
|
{
|
|
|
|
|
reading_html = false;
|
|
|
|
|
if ( !acc.empty() )
|
|
|
|
|
{
|
|
|
|
|
output += "WriteHtmlRaw( \"" + acc + "\");\n";
|
|
|
|
|
acc = "";
|
|
|
|
|
}
|
|
|
|
|
s += 2;
|
|
|
|
|
source_is_emit = ( s[0] == '=' );
|
|
|
|
|
if ( source_is_emit )
|
|
|
|
|
{
|
|
|
|
|
output += "WriteHtmlRaw( ";
|
|
|
|
|
++s;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if ( *s == '\"' )
|
|
|
|
|
acc += "\\\"";
|
|
|
|
|
else if ( *s == '\r' )
|
|
|
|
|
;
|
|
|
|
|
else if ( *s == '\n' )
|
|
|
|
|
acc += "\\n";
|
|
|
|
|
else
|
|
|
|
|
acc += *s;
|
|
|
|
|
++s;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if ( s[0] == '%' && s[1] == '>' )
|
|
|
|
|
{
|
|
|
|
|
reading_html = true;
|
|
|
|
|
s += 2;
|
|
|
|
|
if ( source_is_emit )
|
|
|
|
|
output += " );\n";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
output += *s++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( !acc.empty() )
|
|
|
|
|
output += "WriteHtmlRaw( \"" + acc + "\");\n";
|
2020-07-02 19:46:21 -07:00
|
|
|
return output;
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
|
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
/**
|
|
|
|
|
* Here starts the real complation. Reads the given file and process it
|
|
|
|
|
*
|
|
|
|
|
* @param in_file Path for the file to compile, no more validity checks are done
|
|
|
|
|
* @return <0 on error
|
|
|
|
|
*/
|
|
|
|
|
int Compiler::compileFile( const char* in_file )
|
|
|
|
|
{
|
|
|
|
|
int res = -1;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
std::string filepath = Clib::FullPath( in_file );
|
|
|
|
|
referencedPathnames.push_back( filepath );
|
|
|
|
|
current_file_path = getpathof( filepath );
|
2020-07-28 22:12:12 -07:00
|
|
|
if ( compilercfg.VerbosityLevel >= 11 )
|
2016-02-19 19:26:35 +01:00
|
|
|
INFO_PRINT << "cfp: " << current_file_path << "\n";
|
|
|
|
|
Clib::FileContents fc( filepath.c_str() );
|
|
|
|
|
|
|
|
|
|
if ( is_web_script( filepath.c_str() ) )
|
|
|
|
|
{
|
2020-07-02 19:46:21 -07:00
|
|
|
fc.set_contents( preprocess_web_script( fc.contents() ) );
|
2016-02-19 19:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CompilerContext ctx( filepath, program->add_dbg_filename( filepath ), fc.contents() );
|
|
|
|
|
|
|
|
|
|
bool bres = read_function_declarations( ctx );
|
|
|
|
|
// cout << "bres:" << bres << endl;
|
|
|
|
|
if ( !bres )
|
|
|
|
|
{
|
|
|
|
|
res = -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
res = compile( ctx ) || emit_functions();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch ( const char* s )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Exception Detected:", s, "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
res = -1;
|
|
|
|
|
}
|
|
|
|
|
catch ( std::exception& ex )
|
|
|
|
|
{
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Exception Detected:\n", ex.what(), "\n" );
|
2016-02-19 19:26:35 +01:00
|
|
|
res = -1;
|
|
|
|
|
}
|
2018-04-03 04:13:03 +02:00
|
|
|
// catch(...)
|
|
|
|
|
// {
|
|
|
|
|
// cout << "Generic Exception" << endl;
|
|
|
|
|
// res = -1;
|
|
|
|
|
// }
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
if ( res < 0 )
|
2019-10-05 13:19:38 +02:00
|
|
|
compiler_error( "Compilation failed.\n" );
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
// if (contains_tabs && Compiler.warnings_)
|
2018-04-03 04:13:03 +02:00
|
|
|
// cout << "Warning! Source contains TAB characters" << endl;
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
|
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
int Compiler::write( const char* fname )
|
|
|
|
|
{
|
|
|
|
|
return program->write( fname );
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
int Compiler::write_dbg( const char* fname, bool gen_txt )
|
|
|
|
|
{
|
|
|
|
|
return program->write_dbg( fname, gen_txt );
|
|
|
|
|
}
|
2014-08-30 12:25:08 +02:00
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
void Compiler::writeIncludedFilenames( const char* fname ) const
|
|
|
|
|
{
|
|
|
|
|
std::ofstream ofs( fname, std::ios::out | std::ios::trunc );
|
2018-04-03 04:13:03 +02:00
|
|
|
// ofs << current_file_path << endl;
|
2016-02-19 19:26:35 +01:00
|
|
|
for ( const auto& elem : referencedPathnames )
|
|
|
|
|
{
|
|
|
|
|
ofs << elem << std::endl;
|
2014-08-30 12:25:08 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-19 19:26:35 +01:00
|
|
|
void Compiler::dump( std::ostream& os )
|
|
|
|
|
{
|
|
|
|
|
program->dump( os );
|
|
|
|
|
}
|
2020-08-03 23:11:03 -07:00
|
|
|
|
|
|
|
|
bool Compiler::compile_file( const std::string& filename )
|
|
|
|
|
{
|
|
|
|
|
return compileFile( filename.c_str() ) == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Compiler::write_ecl( const std::string& pathname )
|
|
|
|
|
{
|
|
|
|
|
return write( pathname.c_str() ) == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::write_listing( const std::string& pathname )
|
|
|
|
|
{
|
|
|
|
|
std::ofstream ofs( pathname );
|
|
|
|
|
dump( ofs );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::write_dbg( const std::string& pathname, bool include_debug_text )
|
|
|
|
|
{
|
|
|
|
|
write_dbg( pathname.c_str(), include_debug_text );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compiler::write_included_filenames( const std::string& pathname )
|
|
|
|
|
{
|
|
|
|
|
writeIncludedFilenames( pathname.c_str() );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-05 00:01:07 -07:00
|
|
|
Pol::Bscript::Compiler::LegacyFunctionOrder Compiler::get_legacy_function_order() const
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> modulefunc_emit_order;
|
|
|
|
|
|
|
|
|
|
if ( program.get() )
|
|
|
|
|
{
|
|
|
|
|
for ( auto module : program->modules )
|
|
|
|
|
{
|
|
|
|
|
for ( auto function : module->used_functions )
|
|
|
|
|
{
|
|
|
|
|
std::string scoped_name =
|
|
|
|
|
std::string( module->modulename ) + "::" + std::string( function->name );
|
|
|
|
|
|
|
|
|
|
modulefunc_emit_order.push_back( scoped_name );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Pol::Bscript::Compiler::LegacyFunctionOrder{ modulefunc_emit_order, userfunc_emit_order };
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 19:38:09 -07:00
|
|
|
} // namespace Legacy
|
2018-11-01 17:57:03 +01:00
|
|
|
} // namespace Bscript
|
|
|
|
|
} // namespace Pol
|
|
|
|
|
/*
|
|
|
|
|
local x; [ "x", RSV_LOCAL, # ]
|
|
|
|
|
local x:=5; [ "x", RSV_LOCAL, 5, TOK_ASSIGN, # ]
|
|
|
|
|
local x,y:=5; [ "x", RSV_LOCAL, #, "y", TOK_LOCAL,
|
|
|
|
|
local x:=5,y;
|
|
|
|
|
|
|
|
|
|
x := 5;
|
|
|
|
|
|
|
|
|
|
declare function foo(a,b,c,d);
|
|
|
|
|
|
|
|
|
|
function foo(a,b,c,d)
|
|
|
|
|
begin
|
|
|
|
|
return
|
|
|
|
|
or
|
|
|
|
|
return "hey"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
statements:
|
|
|
|
|
|
|
|
|
|
if expr [then] statement [else statement];
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
statement;
|
|
|
|
|
while expr;
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
statements;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while expr
|
|
|
|
|
statement;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Alternative:
|
|
|
|
|
if expr
|
|
|
|
|
statments;
|
|
|
|
|
[else
|
|
|
|
|
statements; ]
|
|
|
|
|
endif;
|
|
|
|
|
*/
|