Windows2003-3790/multimedia/directx/dmusic/dmscript/engparse.cpp
2020-09-30 16:53:55 +02:00

1047 lines
27 KiB
C++

// Copyright (c) 1999 Microsoft Corporation. All rights reserved.
//
// Implementation of Parser.
//
//#define LIMITEDVBSCRIPT_LOGPARSER // §§
#include "stdinc.h"
#include "enginc.h"
#include "engparse.h"
#include "engerror.h"
#include "engexpr.h"
#ifdef LIMITEDVBSCRIPT_LOGPARSER
#include "englog.h"
#endif
// initial sizes for hash tables
// §§ tune these values?
const int g_cInitialRoutineLookup = 32;
const int g_cInitialGlobalsLookup = 32;
const int g_cInitialVarRefsLookup = 64;
const int g_cInitialAttrsLookup = 32;
const int g_cInitialLocalsLookup = 32;
// The parser will just hold weak references to the passed interfaces because we know that the parser is fully
// contained in the lifetime of its parent -- CAudioVBScriptEngine.
Parser::Parser(Lexer &lexer, Script &script, IActiveScriptSite *pActiveScriptSite, IDispatch *pGlobalDispatch)
: m_lexer(lexer),
m_plkuRoutines(NULL),
m_plkuGlobals(NULL),
m_plkuVarRefs(NULL),
m_plkuNames(NULL),
m_pActiveScriptSite(pActiveScriptSite),
m_script(script),
m_pGlobalDispatch(pGlobalDispatch)
{
m_plkuRoutines = new Lookup(&m_hr, m_script.strings, g_cInitialRoutineLookup);
if (!m_plkuRoutines)
m_hr = E_OUTOFMEMORY;
if (FAILED(m_hr))
return;
m_plkuGlobals = new Lookup(&m_hr, m_script.strings, g_cInitialGlobalsLookup);
if (!m_plkuGlobals)
m_hr = E_OUTOFMEMORY;
if (FAILED(m_hr))
return;
m_plkuVarRefs = new Lookup(&m_hr, m_script.strings, g_cInitialVarRefsLookup);
if (!m_plkuVarRefs)
m_hr = E_OUTOFMEMORY;
if (FAILED(m_hr))
return;
m_plkuNames = new Lookup(&m_hr, m_script.strings, g_cInitialAttrsLookup);
if (!m_plkuNames)
m_hr = E_OUTOFMEMORY;
if (FAILED(m_hr))
return;
// Set up the built in constants as the first global variables.
for (int i = 0; i < g_cBuiltInConstants; ++i)
{
Variables::index iSlot = m_script.globals.Next();
Strings::index iStr;
m_hr = m_plkuGlobals->FindOrAdd(g_rgszBuiltInConstants[i], iSlot, iStr);
if (FAILED(m_hr))
return;
assert(m_hr == S_FALSE);
Variable variable(iStr);
m_hr = m_script.globals.Add(variable);
if (FAILED(m_hr))
return;
}
assert(m_script.globals.Next() == g_cBuiltInConstants); // they occupy the slots 0 to g_cBuiltInConstants - 1
ParseScript();
}
Parser::~Parser()
{
delete m_plkuRoutines;
delete m_plkuGlobals;
delete m_plkuVarRefs;
delete m_plkuNames;
}
// top-level loop that parses the script
void
Parser::ParseScript()
{
if (FAILED(m_hr))
{
assert(false);
return;
}
// make sure the lexer has a token to start with
if (!m_lexer)
{
if (m_lexer.error_num())
Error(PARSEERR_LexerError);
return;
}
if (Skip(TOKEN_linebreak) || !m_lexer)
return;
// parse subs and dims...
bool fSub = false; // used to ensure that add Dim statements occur before all Sub statements
for (;;)
{
if (m_lexer == TOKEN_dim)
{
if (fSub)
Error(PARSEERR_DimAfterSub);
else
ParseDim();
}
else if (m_lexer == TOKEN_sub)
{
fSub = true;
ParseSub();
}
else
{
Error(PARSEERR_ExpectedSubOrDim);
}
if (FAILED(m_hr) || !m_lexer)
return;
// must be followed by line break
if (Expect(TOKEN_linebreak, PARSEERR_ExpectedLineBreak) || !m_lexer)
return;
}
}
// pre: at sub
// post: beyond end sub
void
Parser::ParseSub()
{
if (FAILED(m_hr))
{
assert(false);
return;
}
// sub followed by identifier
assert(m_lexer == TOKEN_sub);
if (ExpectNext(TOKEN_identifier, PARSEERR_ExpectedIdentifier))
return;
const char *pszName = m_lexer.identifier_name();
// check if there's already a variable with this name
Variables::index iVar;
Strings::index iStrVar;
if (m_plkuGlobals->Find(pszName, iVar, iStrVar))
{
Error(PARSEERR_DuplicateVariable);
return;
}
Routines::index iSlot = m_script.routines.Next();
Strings::index iStr;
m_hr = m_plkuRoutines->FindOrAdd(pszName, iSlot, iStr);
if (FAILED(m_hr))
return;
if (m_hr == S_FALSE)
{
Routine routine(iStr);
m_hr = m_script.routines.Add(routine);
if (FAILED(m_hr))
return;
}
else
{
Error(PARSEERR_DuplicateRoutine);
return;
}
if (ExpectNext(TOKEN_linebreak, PARSEERR_ExpectedLineBreak))
return;
Lookup lkuLocals(&m_hr, m_script.strings, g_cInitialLocalsLookup);
if (FAILED(m_hr))
return;
m_script.routines[iSlot].istmtBody = ParseStatements(iSlot, lkuLocals);
if (FAILED(m_hr))
return;
if (m_lexer != TOKEN_end)
{
if (m_lexer == TOKEN_dim)
{
// AudioVBScript disallows dim statements except at the top of the script.
// If one was found in the sub, users should receive the more specific error message.
Error(PARSEERR_DimAfterSub);
}
else
{
Error(PARSEERR_ExpectedEndSub);
}
return;
}
if (Advance())
return;
if (Expect(TOKEN_sub, PARSEERR_ExpectedSub))
return;
#ifdef LIMITEDVBSCRIPT_LOGPARSER
LogRoutine(m_script, iSlot);
#endif
}
// pre: at dim
// post: beyond dim <identifier>
void
Parser::ParseDim()
{
if (FAILED(m_hr))
{
assert(false);
return;
}
assert(m_lexer == TOKEN_dim);
if (ExpectNext(TOKEN_identifier, PARSEERR_ExpectedIdentifier))
return;
Variables::index iSlot = m_script.globals.Next();
Strings::index iStr;
m_hr = m_plkuGlobals->FindOrAdd(m_lexer.identifier_name(), iSlot, iStr);
if (FAILED(m_hr))
return;
if (m_hr == S_FALSE)
{
Variable variable(iStr);
m_hr = m_script.globals.Add(variable);
if (FAILED(m_hr))
return;
}
else
{
Error(PARSEERR_DuplicateVariable);
return;
}
if (Advance())
return;
}
// pre: at line break preceding the expected statements
// post: after end of line at token that isn't the start of another statement
Statements::index
Parser::ParseStatements(Routines::index irtnContext, Lookup &lkuLocals)
{
if (FAILED(m_hr))
{
assert(false);
return 0;
}
assert(m_lexer == TOKEN_linebreak);
if (Skip(TOKEN_linebreak))
return 0;
Statements::index istmtStart = m_script.statements.Next();
for (;;) // ever
{
bool fBreakLoop = false;
switch (m_lexer)
{
case TOKEN_if:
ParseIf(irtnContext, lkuLocals);
break;
case TOKEN_set:
if (Advance())
return 0;
ParseAssignmentOrCall(irtnContext, lkuLocals, true, false);
break;
case TOKEN_call:
if (Advance())
return 0;
ParseAssignmentOrCall(irtnContext, lkuLocals, false, true);
break;
case TOKEN_identifier:
case TOKEN_identifierdot:
ParseAssignmentOrCall(irtnContext, lkuLocals, false, false);
break;
default:
fBreakLoop = true;
break;
}
if (fBreakLoop)
break;
if (FAILED(m_hr) || Expect(TOKEN_linebreak, PARSEERR_ExpectedLineBreak))
return 0;
}
m_hr = m_script.statements.Add(Statement(Statement::cons_end(), 0));
if (FAILED(m_hr))
return 0;
return istmtStart;
}
// pre: at identifier or identifierdot (ambiguous as to whether this is going to be an assignment "x = 1" or call "x(1)")
// post: at line break beyond statement
void
Parser::ParseAssignmentOrCall(Routines::index irtnContext, Lookup &lkuLocals, bool fSet, bool fCall)
{
assert(!(fSet && fCall));
if (FAILED(m_hr))
{
assert(false);
return;
}
assert(m_lexer == TOKEN_identifier || m_lexer == TOKEN_identifierdot);
NameReference nref;
ParseNameReference(irtnContext, lkuLocals, nref);
if (FAILED(m_hr))
return;
if (fCall ? ExpectNext(TOKEN_lparen, PARSEERR_ExpectedLparen) : Advance())
return;
if (m_lexer == TOKEN_op_eq)
{
VariableReferences::index ivarrefLHS = VariableReferenceFromNameReference(irtnContext, lkuLocals, nref);
if (FAILED(m_hr))
return;
Assignments::index iasgn = ParseAssignment(irtnContext, lkuLocals, fSet, ivarrefLHS);
if (FAILED(m_hr))
return;
m_hr = m_script.statements.Add(Statement(Statement::cons_asgn(), iasgn, m_lexer.line()));
if (FAILED(m_hr))
return;
}
else
{
if (fSet)
{
Error(PARSEERR_ExpectedEq);
return;
}
// add the call and its statement
Calls::index icall = CallFromNameReference(irtnContext, lkuLocals, nref, fCall);
if (FAILED(m_hr))
return;
m_hr = m_script.statements.Add(Statement(Statement::cons_call(), icall, m_lexer.line()));
if (FAILED(m_hr))
return;
}
}
// pre: at identifierdot
// post: at identifier
VariableReferences::index Parser::ParseDottedVariableReference(Routines::index irtnContext, Lookup &lkuLocals)
{
return VariableReferenceInternal(irtnContext, lkuLocals, NULL);
}
// pre: at =
// post: at line break beyond statement
Assignments::index
Parser::ParseAssignment(Routines::index irtnContext, Lookup &lkuLocals, bool fSet, VariableReferences::index ivarrefLHS)
{
if (FAILED(m_hr))
{
assert(false);
return 0;
}
// make sure they're not trying to assign to one of the built in constants
VariableReference &vr = m_script.varrefs[ivarrefLHS];
if (vr.k == VariableReference::_global && vr.ivar < g_cBuiltInConstants)
{
Error(PARSEERR_AssignedToBuiltInConstant);
return 0;
}
assert(m_lexer == TOKEN_op_eq);
if (Skip(TOKEN_op_eq))
return 0;
ExprBlocks::index iexprRHS = ParseExpression(irtnContext, lkuLocals, NULL, false, false);
if (FAILED(m_hr))
return 0;
if (m_lexer != TOKEN_linebreak)
{
Error(PARSEERR_InvalidExprLineBreak);
return 0;
}
Assignments::index iasgn = m_script.asgns.Next();
m_hr = m_script.asgns.Add(Assignment(fSet, ivarrefLHS, iexprRHS));
if (FAILED(m_hr))
return 0;
return iasgn;
}
// pre: at if
// post: beyond end if
void
Parser::ParseIf(Routines::index irtnContext, Lookup &lkuLocals)
{
if (FAILED(m_hr))
{
assert(false);
return;
}
// Temporarily place if blocks in separate slots.
// After completing the whole if statement then we'll append them to m_script.ifs.
// This is necessary because we could end up recursively parsing nested ifs and the parent if
// can't have its if blocks interleaved with its children.
IfBlocks ifsTemp;
// add the main if statement, which needs to go on first before we add the statements from its body
Statements::index istmtIf = m_script.statements.Next();
m_hr = m_script.statements.Add(Statement(Statement::cons_if(), m_lexer.line()));
if (FAILED(m_hr))
return;
bool fFirst = true;
do
{
// determine what part of the if (if/elseif/else) we're dealing with
bool fCondition = true;
if (fFirst)
{
assert(m_lexer == TOKEN_if);
fFirst = false;
}
else
{
assert(m_lexer == TOKEN_elseif || m_lexer == TOKEN_else);
if (m_lexer == TOKEN_else)
fCondition = false;
}
if (Advance())
return;
ExprBlocks::index iexprIf = 0;
if (fCondition)
{
// read the condition followed by then
iexprIf = ParseExpression(irtnContext, lkuLocals, NULL, false, false);
if (FAILED(m_hr))
return;
if (Expect(TOKEN_then, PARSEERR_ExpectedThen))
return;
}
// line break
if (m_lexer != TOKEN_linebreak)
{
Error(!fCondition && m_lexer == TOKEN_if ? PARSEERR_IntendedElseIf : PARSEERR_ExpectedLineBreak);
return;
}
// statements
Statements::index istmtIfBody = ParseStatements(irtnContext, lkuLocals);
if (FAILED(m_hr))
return;
// add the if block
if (fCondition)
m_hr = ifsTemp.Add(IfBlock(iexprIf, istmtIfBody));
else
m_hr = ifsTemp.Add(IfBlock(istmtIfBody));
if (FAILED(m_hr))
return;
}
while (m_lexer != TOKEN_end);
if (Advance())
return;
if (Expect(TOKEN_if, PARSEERR_ExpectedIf))
return;
// copy the temp if blocks into the script's real blocks
IfBlocks::index iifIf = m_script.ifs.Next();
IfBlocks::index iifTempNext = ifsTemp.Next();
for (IfBlocks::index iifTemp = 0; iifTemp < iifTempNext; ++iifTemp)
{
m_hr = m_script.ifs.Add(ifsTemp[iifTemp]);
if (FAILED(m_hr))
return;
}
// terminate the if blocks
m_hr = m_script.ifs.Add(IfBlock());
if (FAILED(m_hr))
return;
// now set the main if statement's if blocks and tail
Statement &stmtIf = m_script.statements[istmtIf];
stmtIf.iif = iifIf;
stmtIf.istmtIfTail = m_script.statements.Next();
}
// pre: none (at location where expression is exprected)
// post: beyond last token that is part of a legal expression
ExprBlocks::index
Parser::ParseExpression(Routines::index irtnContext, Lookup &lkuLocals, ExprBlocks *peblocks, bool fAllowRparenAtEnd, bool fDetectSpecialErrorForSubCallWithParen)
{
// if peblocks is non-null then the expression is appended there
// otherwise it goes onto the blocks in the script
ExprBlocks &eblocks = peblocks ? *peblocks : m_script.exprs;
Expression expr(m_script, m_stack, peblocks);
if (m_lexer == TOKEN_stringliteral)
{
// a string literal is the only element of an expression
Strings::index iStr;
m_hr = m_script.strings.Add(m_lexer.stringliteral_text(), iStr);
if (FAILED(m_hr))
return 0;
Values::index ival = m_script.vals.Next();
m_hr = m_script.vals.Add(Value(Value::cons_strvalue(), iStr));
if (FAILED(m_hr))
return 0;
m_hr = expr.Add(ExprBlock(ExprBlock::cons_val(), ival));
if (FAILED(m_hr))
return 0;
if (Advance())
return 0;
}
else
{
// The format of an expression is:
// 1) zero or more unary operators
// 2) a value
// 3) either end here or have a binary operator and go back to step 1
// Oh yeah? What about parentheses?
// * If a left paren is encountered in step 2, we increase a paren count and go back to stage 1.
// * In stage three, if there is paren count then a right paren is expected instead of ending.
// After a matching right paren, we decrease the paren count and pop back to stage 3.
UINT cParens = 0;
for (;;)
{
// stage 1
while (CheckOperatorType(m_lexer, false, true, false, false))
{
// replace minus with sub so that the expression evaluator can identify unary (negation) vs binary (subtraction)
m_hr = expr.Add(ExprBlock(ExprBlock::cons_op(), m_lexer == TOKEN_op_minus ? TOKEN_sub : m_lexer));
if (FAILED(m_hr))
return 0;
if (Advance())
return 0;
}
// stage 2
if (m_lexer == TOKEN_lparen)
{
m_hr = expr.Add(ExprBlock(ExprBlock::cons_op(), m_lexer));
if (FAILED(m_hr))
return 0;
if (Advance())
return 0;
++cParens;
continue;
}
else if (m_lexer == TOKEN_identifier || m_lexer == TOKEN_identifierdot)
{
NameReference nref;
ParseNameReference(irtnContext, lkuLocals, nref);
if (FAILED(m_hr))
return 0;
if (Advance())
return 0;
if (m_lexer == TOKEN_lparen)
{
// add the call and the expression block
Calls::index icall = CallFromNameReference(irtnContext, lkuLocals, nref, true);
m_hr = expr.Add(ExprBlock(ExprBlock::cons_call(), icall));
if (FAILED(m_hr))
return 0;
}
else
{
VariableReferences::index ivarref = VariableReferenceFromNameReference(irtnContext, lkuLocals, nref);
if (FAILED(m_hr))
return 0;
Values::index ival = m_script.vals.Next();
m_hr = m_script.vals.Add(Value(Value::cons_varref(), ivarref));
if (FAILED(m_hr))
return 0;
m_hr = expr.Add(ExprBlock(ExprBlock::cons_val(), ival));
if (FAILED(m_hr))
return 0;
}
}
else if (m_lexer == TOKEN_numericliteral)
{
Values::index ival = m_script.vals.Next();
m_hr = m_script.vals.Add(Value(Value::cons_numvalue(), m_lexer.numericliteral_val()));
if (FAILED(m_hr))
return 0;
m_hr = expr.Add(ExprBlock(ExprBlock::cons_val(), ival));
if (FAILED(m_hr))
return 0;
if (Advance())
return 0;
}
else
{
Error(m_lexer == TOKEN_stringliteral ? PARSEERR_StringInExprBlock: PARSEERR_ExpectedExprValue);
return 0;
}
// stage 3
while (m_lexer == TOKEN_rparen)
{
if (cParens == 0)
{
if (fAllowRparenAtEnd)
break;
Error(PARSEERR_UnexpectedRparen);
return 0;
}
m_hr = expr.Add(ExprBlock(ExprBlock::cons_op(), m_lexer));
if (FAILED(m_hr))
return 0;
if (Advance())
return 0;
--cParens;
}
if (!CheckOperatorType(m_lexer, false, false, true, false))
{
if (cParens > 0)
{
if (fDetectSpecialErrorForSubCallWithParen && cParens == 1 && m_lexer == TOKEN_comma)
{
// This special error is needed to give a helpful error message in cases like the following:
// Segment1.Play(IsSecondary, AP)
// Here the user has accidentally called a sub using parentheses when they shouldn't.
Error(PARSEERR_ParensUsedCallingSub);
}
else
{
Error(PARSEERR_ExpectedRparen);
}
return 0;
}
// *** this is the only successful exit point from the loop ***
break;
}
expr.Add(ExprBlock(ExprBlock::cons_op(), m_lexer));
if (Advance())
return 0;
}
}
ExprBlocks::index iexprStart = eblocks.Next();
m_hr = expr.Generate();
if (FAILED(m_hr))
return 0;
return iexprStart;
}
// if fParenthesized is true
// pre: at lparen
// post: beyond rparen
// if fParenthesized is false
// pre: none (at location where expression for first parameter is expected)
// post: at linebreak
ExprBlocks::index Parser::ParseParameters(Routines::index irtnContext, Lookup &lkuLocals, bool fParenthesized)
{
if (FAILED(m_hr))
{
assert(false);
return 0;
}
if (fParenthesized)
{
assert(m_lexer == TOKEN_lparen);
if (Advance())
return 0;
}
// Temporarily place expression blocks in separate slots.
// After completing the parameters then we'll append them to m_script.exprs.
// This is necessary because we could end up recursively parsing nested calls inside an expression and
// the parent parameters can't have their blocks interleaved with subexpression calls.
ExprBlocks exprsTemp;
Token tStop = fParenthesized ? TOKEN_rparen : TOKEN_linebreak;
ParseErr perrExpectedFinish = fParenthesized ? PARSEERR_InvalidExprRparen : PARSEERR_InvalidExprLineBreak;
bool fFirstParam = true;
while (m_lexer != tStop)
{
if (!fFirstParam)
{
if (Expect(TOKEN_comma, perrExpectedFinish) || !m_lexer)
return 0;
}
if (m_lexer == TOKEN_comma)
{
// This parameter is omitted. Save it as an empty expression.
// Example:
// MySong.Play , , , OldPlayingSong
// There the first three parameters are omitted.
m_hr = exprsTemp.Add(ExprBlock(ExprBlock::cons_omitted()));
if (FAILED(m_hr))
return 0;
m_hr = exprsTemp.Add(ExprBlock(ExprBlock::cons_end()));
if (FAILED(m_hr))
return 0;
}
else
{
// The last parameter is set to true to detect a special error when we're calling a sub (!fParenthesized) and when this is
// the first parameter to that sub (fFirstParam). This will detect a comma and give a better error message in cases like
// the following:
// Segment1.Play(IsSecondary, AP)
// Here the user has accidentally called a sub using parameters when they shouldn't.
ExprBlocks::index iexpr = ParseExpression(irtnContext, lkuLocals, &exprsTemp, fParenthesized, !fParenthesized && fFirstParam);
if (FAILED(m_hr))
return 0;
}
fFirstParam = false;
}
if (fParenthesized)
{
assert(m_lexer == TOKEN_rparen);
if (Advance())
return 0;
}
else
{
assert(m_lexer == TOKEN_linebreak);
}
// terminate the parameters
m_hr = exprsTemp.Add(ExprBlock(ExprBlock::cons_end()));
if (FAILED(m_hr))
return 0;
// copy from the temporary blocks into the script
ExprBlocks::index iexprFirstInScript = m_script.exprs.Next();
ExprBlocks::index iexprLastInTemp = exprsTemp.Next();
for (ExprBlocks::index iexpr = 0; iexpr < iexprLastInTemp; ++iexpr)
{
m_hr = m_script.exprs.Add(exprsTemp[iexpr]);
if (FAILED(m_hr))
return 0;
}
return iexprFirstInScript;
}
void Parser::ParseNameReference(Routines::index irtnContext, Lookup &lkuLocals, NameReference &nref)
{
nref.fSingleItem = m_lexer == TOKEN_identifier;
nref.ivarrefMultiple = 0;
if (nref.fSingleItem)
{
nref.strSingle = m_lexer.identifier_name();
if (!nref.strSingle)
{
m_hr = E_OUTOFMEMORY;
}
}
else
{
nref.ivarrefMultiple = ParseDottedVariableReference(irtnContext, lkuLocals);
}
}
VariableReferences::index Parser::VariableReferenceFromNameReference(Routines::index irtnContext, Lookup &lkuLocals, const NameReference &nref)
{
VariableReferences::index ivarref =
nref.fSingleItem
? VariableReferenceInternal(irtnContext, lkuLocals, nref.strSingle)
: nref.ivarrefMultiple;
return ivarref;
}
Calls::index Parser::CallFromNameReference(Routines::index irtnContext, Lookup &lkuLocals, const NameReference &nref, bool fParametersParenthesized)
{
Call c;
if (nref.fSingleItem)
{
// resolve the name in our temporary parsing name table
Names_Parse::index iSlotName = m_names.Next();
Strings::index iStrName = 0;
m_hr = m_plkuNames->FindOrAdd(nref.strSingle, iSlotName, iStrName);
if (FAILED(m_hr))
return 0;
if (m_hr == S_FALSE)
{
m_hr = m_names.Add(Name_Parse(iStrName));
if (FAILED(m_hr))
return 0;
}
c.k = Call::_global;
c.istrname = iStrName;
}
else
{
c.k = Call::_dereferenced;
c.ivarref = nref.ivarrefMultiple;
}
c.iexprParams = ParseParameters(irtnContext, lkuLocals, fParametersParenthesized);
if (FAILED(m_hr))
return 0;
// add the call
Calls::index icall = m_script.calls.Next();
m_hr = m_script.calls.Add(c);
if (FAILED(m_hr))
return 0;
return icall;
}
VariableReferences::index Parser::VariableReferenceInternal(Routines::index irtnContext, Lookup &lkuLocals, const char *pszName)
{
if (FAILED(m_hr))
{
assert(false);
return 0;
}
assert(pszName || m_lexer == TOKEN_identifierdot);
ReferenceNames::index irname = m_script.rnames.Next();
// resolve the first item, which is a variable in the script
bool fLocal = false;
Variables::index ivarBase;
Strings::index iStrBase;
const char *pszBase = pszName ? pszName : m_lexer.identifier_name();
// check if there's already a routine with this name
Variables::index iRtn;
Strings::index iStrRtn;
if (m_plkuRoutines->Find(pszBase, iRtn, iStrRtn))
{
Error(PARSEERR_ExpectedVariableButRoutineFound);
return 0;
}
// try the globals
if (!m_plkuGlobals->Find(pszBase, ivarBase, iStrBase))
{
// try the locals
if (lkuLocals.Find(pszBase, ivarBase, iStrBase))
{
fLocal = true;
}
else
{
// try the global dispatch
DISPID dispid = GetDispID(m_pGlobalDispatch, pszBase);
if (dispid != DISPID_UNKNOWN)
{
// add it as a global
// §§ Possible performance optimization. Oops. An unintended consequence is that the script
// will reserve a variant slot for this as a global variable at runtime. Could do something
// so save this memory if that's a problem.
ivarBase = m_script.globals.Next();
m_hr = m_plkuGlobals->FindOrAdd(pszBase, ivarBase, iStrBase);
if (FAILED(m_hr))
return 0;
assert(m_hr == S_FALSE); // we already tried Find, above so must be added
Variable variable(iStrBase, dispid);
m_hr = m_script.globals.Add(variable);
if (FAILED(m_hr))
return 0;
}
else
{
// add to the locals
fLocal = true;
ivarBase = m_script.routines[irtnContext].ivarNextLocal;
m_hr = lkuLocals.FindOrAdd(pszBase, ivarBase, iStrBase);
if (FAILED(m_hr))
return 0;
assert(m_hr == S_FALSE); // we already tried Find, above so must be added
assert(ivarBase == m_script.routines[irtnContext].ivarNextLocal);
++m_script.routines[irtnContext].ivarNextLocal;
}
}
}
// save the name
m_hr = m_script.rnames.Add(ReferenceName(iStrBase));
if (FAILED(m_hr))
return 0;
if (!pszName)
{
// remaining items are scoped outside the script, so just record their names
do
{
// next is identifier or identifierdot
if (Advance())
return 0;
if (m_lexer != TOKEN_identifier && m_lexer != TOKEN_identifierdot)
{
Error(PARSEERR_ExpectedIdentifier);
return 0;
}
// resolve the name in our temporary parsing name table
Names_Parse::index iSlotName = m_names.Next();
Strings::index iStrName = 0;
m_hr = m_plkuNames->FindOrAdd(m_lexer.identifier_name(), iSlotName, iStrName);
if (FAILED(m_hr))
return 0;
if (m_hr == S_FALSE)
{
m_hr = m_names.Add(Name_Parse(iStrName));
if (FAILED(m_hr))
return 0;
}
// save the name
m_hr = m_script.rnames.Add(ReferenceName(iStrName));
if (FAILED(m_hr))
return 0;
}
while (m_lexer != TOKEN_identifier);
}
// terminates the rnames
m_hr = m_script.rnames.Add(ReferenceName(-1));
if (FAILED(m_hr))
return 0;
//
// make and return the reference
//
VariableReferences::index ivarref = m_script.varrefs.Next();
m_hr = m_script.varrefs.Add(VariableReference(fLocal ? VariableReference::_local : VariableReference::_global, irname, ivarBase));
if (FAILED(m_hr))
return 0;
return ivarref;
}
void
Parser::Error(ParseErr iErr)
{
static const char *s_rgpszErrorText[] =
{
"Unexpected error!", // shouldn't ever get this error
"Expected Sub or Dim statement",
"Expected identifier",
"Expected line break",
"Expected End Sub",
"Expected Sub",
"Expected statement",
"Expected '('",
"Expected '='",
"All Dim statements must occur before all Sub statements",
"Invalid expression or missing line break",
"Invalid expression or missing Then",
"Invalid expression or missing ')'",
"Strings may not appear inside numerical expressions",
"Invalid expression -- expected a number or variable",
"A variable with this name already exists",
"Another routine with this name already exists",
"Invalid expression -- expected ')'",
"Invalid expression -- encountered ')' without a preceding '('",
"Expected Then",
"Expected End If",
"Expected If",
"Expected line break or ElseIf should be a single word without space before If",
"The values True, False, and Nothing are read-only and cannot be set",
"Cannot use parentheses when calling a Sub",
"Sub name used where variable was expected"
};
assert(ARRAY_SIZE(s_rgpszErrorText) == PARSEERR_Max);
if (FAILED(m_hr))
{
// Something forgot to check m_hr. We were already in an error state previously so leave that error as is and assert.
assert(false);
return;
}
if (iErr < 0 || iErr > PARSEERR_Max)
{
assert(false);
iErr = PARSEERR_LexerError;
}
m_hr = DMUS_E_AUDIOVBSCRIPT_SYNTAXERROR;
// The error number should be passed as PARSEERR_LexerError if and only if the lexer is in an error state.
// In this case we'll get our description from the lexer itelf. Otherwise we look it up in the table.
assert((iErr == PARSEERR_LexerError) == (m_lexer == TOKEN_eof && m_lexer.error_num()));
const char *pszErr = (m_lexer == TOKEN_eof && m_lexer.error_num()) ? m_lexer.error_descr() : s_rgpszErrorText[iErr];
CActiveScriptError *perr = new CActiveScriptError(m_hr, m_lexer, pszErr);
if (perr)
{
m_pActiveScriptSite->OnScriptError(perr);
perr->Release();
}
}