def _setVariable(lhs_v, rhs_v, interpreter): namespace_name = lhs_v.value assert (isinstance(rhs_v, Array)) if len(rhs_v) not in [2, 3]: interpreter.exception( SQFParserError( rhs_v.position, 'setVariable requires array of 2-3 elements (has %d)' % (len(rhs_v)))) # get the variable name if not isinstance(rhs_v.value[0], (String, Nothing)): interpreter.exception( SQFParserError( rhs_v.value[0].position, 'setVariable array first element must be a string (is %s)' % type(rhs_v.value[0]).__name__)) variable_name = rhs_v.value[0].value # get the value rhs_assignment = rhs_v.value[1] scope = interpreter.get_scope(variable_name, namespace_name) scope[variable_name] = rhs_assignment
def add_params(self, base_token, arguments=None): assert (isinstance(base_token, Array)) if arguments is None or isinstance(arguments, Nothing): arguments = self['_this'] if isinstance(arguments, Array) and not arguments.is_undefined: arguments = arguments.value else: arguments = self._parse_params_args(arguments, base_token) if len(arguments) > len(base_token): self.exception( SQFWarning( base_token.position, '`params` lhs (%d elements) is larger than rhs (%d elements).' ' Some arguments are ignored.' % (len(arguments), len(base_token)))) for i, token in enumerate(base_token): if isinstance(token, String): if token.value == '': continue self.add_privates([token]) if i >= len(arguments): self.exception( SQFWarning( token.position, '`params` mandatory argument %s is missing in rhs' % token)) else: self.current_scope[token.value] = arguments[i] elif isinstance(token, Array): if len(token) in (2, 3, 4): if i < len(arguments) and not isinstance( arguments[i], Nothing): argument = arguments[i] else: argument = token[1] self._add_params(token) self.current_scope[token[0].value] = argument else: self.exception( SQFParserError( base_token.position, '`params` array element must have 2-4 elements')) else: self.exception( SQFParserError( base_token.position, '`params` array element must be a string or array')) return True
def _select(lhs, rhs, interpreter): index = int(round(rhs.value)) try: return lhs[index] except IndexError: interpreter.exception( SQFParserError( lhs.position, 'selecting element %d of array of size %d' % (index, len(lhs))))
def preprocessor_stringify(token, is_variable): # Verify that token starts with appropriate char and is alphanumeric if not (is_variable or re.match(r'[a-zA-Z_]\w*', str(token))): # todo: need to fix the coordinates for errors here raise SQFParserError(get_coord(""), 'Stringification failed on invalid characters') # todo: check for invalid string created (missing ") return String("\"" + str(token) + "\"")
def _select_array(lhs, rhs, interpreter): start = rhs.value[0].value count = rhs.value[1].value if start > len(lhs.value): interpreter.exception( SQFParserError(lhs.position, 'Selecting element past size')) return lhs.value[start:start + count]
def _analyze_array(tokens, analyze_tokens, tokens_until): result = [] part = [] first_comma_found = False for token in tokens: if token == ParserKeyword(','): first_comma_found = True if not part: raise SQFParserError(get_coord(tokens_until), 'Array cannot have an empty element') result.append(analyze_tokens(part)) part = [] else: part.append(token) # an empty array is a valid array if part == [] and first_comma_found: raise SQFParserError(get_coord(tokens_until), 'Array cannot have an empty element') elif tokens: result.append(analyze_tokens(part)) return result
def add_privates(self, variables): """ Privatizes a list of variables by initializing them on the scope (as Nothing). """ for variable in variables: if not isinstance(variable, String): self.exception( SQFParserError( variable.position, 'Variable in private must be a string (is %s)' % type(variable))) continue if not variable.value.startswith('_'): self.exception( SQFParserError( variable.position, 'Cannot make global variable "%s" private (underscore missing?)' % variable.value)) continue self._add_private(variable)
def add_params(self, base_token): assert (isinstance(base_token, Array)) for token in base_token: if isinstance(token, String): if token.value == '': continue self.add_privates([token]) elif isinstance(token, Array): if len(token) in (2, 3, 4): self._add_params(token) else: self.exception( SQFParserError( base_token.position, '`params` array element must have 2-4 elements')) else: self.exception( SQFParserError( base_token.position, '`params` array element must be a string or array')) return True
def add_privates(self, variables): for variable in variables: assert (isinstance(variable, String)) name = variable.value if not name.startswith('_'): self.exception( SQFParserError( variable.position, 'Cannot make global variable "%s" private (underscore missing?)' % name)) self.current_scope[name] = self.private_default_class()
def get_ifdef_variable(tokens, ifdef_i, coord_until_here): variable = None eol_i = None for i, token in enumerate(tokens[ifdef_i:]): if type(token) == EndOfLine: eol_i = ifdef_i + i break if type(token) in (Variable, Keyword): variable = str(token) if variable is not None and eol_i is not None: return variable, eol_i raise SQFParserError(add_coords(coord_until_here, tokens[:ifdef_i]), '#ifdef statement must contain a variable')
def _getVariableArray(lhs_v, rhs_v, interpreter): # get the variable name if len(rhs_v) != 2: interpreter.exception( SQFParserError( rhs_v.position, 'getVariable requires array of 2 elements (has %d)' % (len(rhs_v)))) if not isinstance(rhs_v.value[0], (String, Nothing)): interpreter.exception( SQFParserError( rhs_v.value[0].position, 'getVariable array first element must be a string (is %s)' % type(rhs_v.value[0]).__name__)) variable = Variable(rhs_v.value[0].value) variable.position = rhs_v.value[0].position outcome = interpreter.value(variable, lhs_v.value) if outcome == Nothing(): outcome = rhs_v.value[1] return outcome
def parse_switch(interpreter, code): conditions = [] default_used = False for statement in code.base_tokens: base_tokens = statement.base_tokens # evaluate all the base_tokens, trying to obtain their values values = [] for token in base_tokens: v = interpreter.value(token) values.append(v) if type(values[0]) != SwitchType: interpreter.exception( SQFParserError( statement.position, 'Switch code can only start with "case" or "default"')) if values[0].keyword == Keyword('default'): if default_used: interpreter.exception( SQFParserError( code.position, 'Switch code contains more than 1 `default`')) default_used = True assert (isinstance(values[0].result, Code)) conditions.append(('default', values[0].result)) else: case_condition = values[0].result if len(values) == 1: conditions.append((case_condition, None)) else: assert (len(values) == 3 and values[1] == Keyword(':')) outcome_statement = values[2] conditions.append((case_condition, outcome_statement)) return conditions
def _action(self, lhs, rhs, interpreter): if isinstance(rhs, Code): result = interpreter.execute_code(rhs) if type(result) not in (Boolean, Nothing): interpreter.exception( SQFParserError( rhs.position, 'code return must be a Boolean (returns %s)' % type(result).__name__)) return None else: result = rhs return OP_OPERATIONS[self.keyword](lhs.value, result.value)
def _analyze_define(tokens): assert(tokens[0] == Preprocessor('#define')) valid_indexes = [i for i in range(len(tokens)) if not isinstance(tokens[i], ParserType)] if len(valid_indexes) < 2: raise SQFParserError(get_coord(str(tokens[0])), '#define needs at least one argument') variable = str(tokens[valid_indexes[1]]) if len(valid_indexes) == 2: return DefineStatement(tokens, variable) elif len(valid_indexes) >= 3 and valid_indexes[1] + 1 == valid_indexes[2] and isinstance(tokens[valid_indexes[2]], Statement) and tokens[valid_indexes[2]].parenthesis: args = str(tokens[valid_indexes[2]])[1:-1].split(',') remaining = tokens[valid_indexes[3]:] return DefineStatement(tokens, variable, remaining, args=args) elif len(valid_indexes) >= 3: remaining = tokens[valid_indexes[2]:] return DefineStatement(tokens, variable, remaining)
class Interpreter(BaseInterpreter): private_default_class = Nothing def __init__(self, all_vars=None): super().__init__(all_vars) self._simulation = None self._client = None @property def simulation(self): return self._simulation @property def client(self): return self._client @client.setter def client(self, client): self._client = client self._simulation = client.simulation def _add_params(self, token): super()._add_params(token) lhs = token[0].value scope = self.get_scope(lhs) scope[lhs] = token[1] def execute_token(self, token): """ Given a single token, recursively evaluate it and return its value. """ # interpret the statement recursively if isinstance(token, Statement): result = self.execute_single(statement=token) elif isinstance(token, Array): # empty statements are ignored result = Array( [self.execute_token(s)[1] for s in token.value if s]) elif token == Keyword('isServer'): result = Boolean(self.client.is_server) elif token == Keyword('isDedicated'): result = Boolean(self.client.is_dedicated) else: result = token result.position = token.position return result, self.value(result) def execute_single(self, statement): assert (not isinstance(statement, Code)) outcome = Nothing() _outcome = outcome # evaluate the types of all tokens base_tokens = statement.base_tokens values = [] tokens = [] types = [] for token in base_tokens: t, v = self.execute_token(token) values.append(v) tokens.append(t) types.append(type(v)) case_found = None for case in EXPRESSIONS: if case.is_match(values): case_found = case break if case_found is not None: outcome = case_found.execute(values, self) # todo: replace all elif below by expressions elif len(tokens) == 2 and tokens[0] == Keyword('publicVariable'): if not isinstance(tokens[1], String) or tokens[1].value.startswith('_'): raise SQFParserError( statement.position, 'Interpretation of "%s" failed' % statement) var_name = tokens[1].value scope = self.get_scope(var_name, 'missionNamespace') self.simulation.broadcast(var_name, scope[var_name]) elif len(tokens) == 2 and tokens[0] == Keyword('publicVariableServer'): if not isinstance(tokens[1], String) or tokens[1].value.startswith('_'): raise SQFParserError( statement.position, 'Interpretation of "%s" failed' % statement) var_name = tokens[1].value scope = self.get_scope(var_name, 'missionNamespace') self.simulation.broadcast(var_name, scope[var_name], -1) # -1 => to server elif len(tokens) == 2 and tokens[0] == Keyword('private'): if isinstance(values[1], String): self.add_privates([values[1]]) elif isinstance(values[1], Array): self.add_privates(values[1].value) elif isinstance(base_tokens[1], Statement) and isinstance( base_tokens[1].base_tokens[0], Variable): var = base_tokens[1].base_tokens[0] self.add_privates([String('"' + var.name + '"')]) outcome = PrivateType(var) # binary operators elif len(tokens) == 3 and tokens[1] in ( Keyword('='), Keyword('publicVariableClient')): # it is a binary statement: token, operation, token lhs = tokens[0] lhs_v = values[0] lhs_t = types[0] op = tokens[1] rhs = tokens[2] rhs_v = values[2] rhs_t = types[2] if op == Keyword('='): if isinstance(lhs, PrivateType): lhs = lhs.variable else: lhs = self.get_variable(base_tokens[0]) if not isinstance(lhs, Variable) or not isinstance( rhs_v, Type): raise SQFParserError( statement.position, 'Interpretation of "%s" failed' % statement) scope = self.get_scope(lhs.name) scope[lhs.name] = rhs_v outcome = rhs elif op == Keyword('publicVariableClient'): if not lhs_t == Number or rhs.value.startswith('_'): raise SQFParserError( statement.position, 'Interpretation of "%s" failed' % statement) client_id = lhs.value var_name = rhs.value scope = self.get_scope(var_name, 'missionNamespace') self.simulation.broadcast(var_name, scope[var_name], client_id) # code, variables and values elif len(tokens) == 1 and isinstance(tokens[0], (Type, Keyword)): outcome = values[0] else: raise SQFParserError(statement.position, 'Interpretation of "%s" failed' % statement) if statement.ending: outcome = _outcome assert (type(outcome) in (Nothing, Keyword) or not outcome.is_undefined) return outcome
class Analyzer(BaseInterpreter): """ The Analyzer. This is an interpreter that: * runs SQF statements that accepts unknown types * Stores exceptions instead of rising them. * Runs code that is declared but not called. """ COMMENTS_FOR_PRIVATE = {'IGNORE_PRIVATE_WARNING', 'USES_VARIABLES'} def __init__(self, all_vars=None): super().__init__(all_vars) self.exceptions = [] self.privates = set() self.unevaluated_interpreter_tokens = [] self._unexecuted_codes = {} self._executed_codes = {} # executed code -> result self.variable_uses = {} # a counter used by `self.assign` to identify if a variable is deleted (assigned to Anything) or not. self.delete_scope_level = 0 # list of variables that we currently know the type during the script. self.undefined_variables = set() def exception(self, exception): self.exceptions.append(exception) @staticmethod def code_key(code): return code.position, str(code) @staticmethod def exe_code_key(code, extra_scope): if extra_scope is None: extra_scope = {} return str(code), tuple( (x, type(extra_scope[x])) for x in sorted(extra_scope.keys())) def value(self, token, namespace_name=None): """ Given a single token, recursively evaluates and returns its value """ if namespace_name is None: namespace_name = self.current_namespace.name assert (isinstance(token, BaseType)) if isinstance(token, IfDefResult): for x in token.result: x.set_position(token.position) result = self.value(self.execute_token(x)) elif isinstance(token, DefineResult): token.result.set_position(token.position) result = self.value(self.execute_token(token.result)) elif isinstance(token, Statement): result = self.value(self.execute_token(token)) elif isinstance(token, Variable): scope = self.get_scope(token.name, namespace_name) if scope.level == 0 and not token.is_global: self.exception( SQFWarning( token.position, 'Local variable "%s" is not from this scope (not private)' % token)) try: result = scope[token.name] except KeyError: result = self.private_default_class() result.position = token.position key = '%s_%s_%s' % (namespace_name, scope.level, scope.normalize(token.name)) if key in self.variable_uses: self.variable_uses[key]['count'] += 1 elif isinstance(token, Array) and not token.is_undefined: result = Array( [self.value(self.execute_token(s)) for s in token.value]) result.position = token.position else: null_expressions = values_to_expressions([token], EXPRESSIONS_MAP, EXPRESSIONS) if null_expressions: result = null_expressions[0].execute([token], self) else: result = token result.position = token.position if isinstance( result, Code) and self.code_key(result) not in self._unexecuted_codes: self._unexecuted_codes[self.code_key(result)] = UnexecutedCode( result, self) return result def execute_token(self, token): """ Given a single token, recursively evaluate it without returning its value (only type) """ # interpret the statement recursively if isinstance(token, Statement): result = self.execute_single(statement=token) # we do not want the position of the statement, but of the token, so we do not # store it here elif isinstance(token, Array) and token.value is not None: result = Array([self.execute_token(s) for s in token.value]) result.position = token.position else: result = token result.position = token.position return result def execute_unexecuted_code(self, code_key, extra_scope=None, own_namespace=False): """ Executes a code in a dedicated env and put consequence exceptions in self. own_namespace: whether the execution uses the current local variables or no variables """ container = self._unexecuted_codes[code_key] analyzer = Analyzer() if not own_namespace: analyzer._namespaces = container.namespaces analyzer.variable_uses = self.variable_uses analyzer.delete_scope_level = container.delete_scope_level file = File(container.code._tokens) file.position = container.position this = Anything() this.position = container.position analyzer.execute_code(file, extra_scope=extra_scope, namespace_name=container.namespace_name, delete_mode=True) self.exceptions.extend(analyzer.exceptions) def execute_code(self, code, extra_scope=None, namespace_name='missionnamespace', delete_mode=False): key = self.code_key(code) exe_code_key = self.exe_code_key(code, extra_scope) if key in self._unexecuted_codes: del self._unexecuted_codes[key] if exe_code_key in self._executed_codes: outcome = self._executed_codes[exe_code_key] else: self.delete_scope_level += delete_mode outcome = super().execute_code(code, extra_scope, namespace_name) self.delete_scope_level -= delete_mode self._executed_codes[exe_code_key] = outcome if isinstance(code, File): for key in self._unexecuted_codes: self.execute_unexecuted_code(key) # collect `private` statements that have a variable but were not collected by the assignment operator # this check is made at the scope level for private in self.privates: self.exception( SQFWarning(private.position, 'private argument must be a string.')) # this check is made at the scope level for token in self.unevaluated_interpreter_tokens: self.exception( SQFWarning( token.position, 'helper type "%s" not evaluated' % token.__class__.__name__)) # this check is made at script level if not delete_mode: # collect variables that were not used for key in self.variable_uses: if self.variable_uses[key]['count'] == 0: variable = self.variable_uses[key]['variable'] self.exception( SQFWarning( variable.position, 'Variable "%s" not used' % variable.value)) return outcome def _parse_params_args(self, arguments, base_token): if isinstance(arguments, Anything) or (isinstance(arguments, Array) and arguments.is_undefined): return [Anything() for _ in range(len(base_token))] return super()._parse_params_args(arguments, base_token) def _add_private(self, variable): super()._add_private(variable) scope = self.current_scope key = '%s_%s_%s' % (self.current_namespace.name, scope.level, scope.normalize(variable.value)) self.variable_uses[key] = {'count': 0, 'variable': variable} def assign(self, lhs, rhs_v): """ Assigns the rhs_v to the lhs variable. """ lhs_name = lhs.name lhs_position = lhs.position scope = self.get_scope(lhs_name) try: lhs_t = type(scope[lhs.name]) except KeyError: lhs_t = self.private_default_class rhs_t = type(rhs_v) if scope.level == 0: # global variable becomes undefined when: # 1. it changes type AND # 2. it is modified on a higher delete scope (e.g. if {}) or it already has a defined type if (lhs_t != Anything or self.delete_scope_level > scope.level) and \ lhs_t != rhs_t and lhs_name not in self.undefined_variables: self.undefined_variables.add(lhs_name) if scope.level == 0: if lhs_name in self.undefined_variables: rhs_t = Anything elif lhs_t != rhs_t and self.delete_scope_level >= scope.level: rhs_t = Anything scope[lhs_name] = rhs_t() if scope.level == 0 and lhs_name.startswith('_'): self.exception( SQFWarning( lhs_position, 'Local variable "%s" assigned to an outer scope (not private)' % lhs_name)) def execute_single(self, statement): assert (isinstance(statement, Statement)) outcome = Nothing() outcome.position = statement.position base_tokens = [] for token in statement.tokens: if not statement.is_base_token(token): self.execute_other(token) else: base_tokens.append(token) if not base_tokens: return outcome # operations that cannot evaluate the value of all base_tokens if type(base_tokens[0]) == DefineStatement: return base_tokens[0] elif base_tokens[0] == Preprocessor("#include"): if len(base_tokens) != 2: exception = SQFParserError(base_tokens[0].position, "#include requires one argument") self.exception(exception) elif type(self.execute_token(base_tokens[1])) != String: exception = SQFParserError( base_tokens[0].position, "#include first argument must be a string") self.exception(exception) return outcome elif isinstance(base_tokens[0], Keyword) and base_tokens[0].value in PREPROCESSORS: # remaining preprocessors are ignored return outcome elif len(base_tokens) == 2 and base_tokens[0] == Keyword('private'): # the rhs may be a variable, so we cannot get the value rhs = self.execute_token(base_tokens[1]) if isinstance(rhs, String): self.add_privates([rhs]) elif isinstance(rhs, Array): value = self.value(rhs) if value.is_undefined: self.exception( SQFWarning( base_tokens[0].position, 'Obfuscated statement. Consider explicitly set what is private.' )) else: self.add_privates(value) elif isinstance(rhs, Variable): var = String('"' + rhs.name + '"') var.position = rhs.position self.add_privates([var]) outcome = PrivateType(rhs) outcome.position = rhs.position self.privates.add(outcome) else: self.exception( SQFParserError(base_tokens[0].position, '`private` used incorrectly')) return outcome # assignment operator elif len(base_tokens) == 3 and base_tokens[1] == Keyword('='): lhs = self.execute_token(base_tokens[0]) if isinstance(lhs, PrivateType): self.privates.remove(lhs) lhs = lhs.variable else: lhs = self.get_variable(base_tokens[0]) if not isinstance(lhs, Variable): self.exception( SQFParserError( base_tokens[0].position, 'lhs of assignment operator must be a variable')) else: # if the rhs_v is code and calls `lhs` (recursion) it will assume lhs is anything (and not Nothing) scope = self.get_scope(lhs.name) if lhs.name not in scope or isinstance(scope[lhs.name], Nothing): scope[lhs.name] = Anything() rhs_v = self.value(base_tokens[2]) self.assign(lhs, rhs_v) if not statement.ending: outcome = rhs_v return outcome # A variable can only be evaluated if we need its value, so we will not call its value until the very end. elif len(base_tokens) == 1 and type( base_tokens[0]) in (Variable, Array): return self.execute_token(base_tokens[0]) # heuristic for defines (that are thus syntactically correct): # - is keyword but upper cased # - first token string starts uppercased elif len(base_tokens) == 1 and type(base_tokens[0]) == Keyword and str( base_tokens[0])[0].isupper(): outcome = Variable(str(base_tokens[0])) outcome.position = base_tokens[0].position return outcome elif is_undefined_define(base_tokens): # get all arguments and compute their value to analyze them if isinstance(base_tokens[1].base_tokens[0], Statement): sub_tokens = base_tokens[1].base_tokens[0].base_tokens else: sub_tokens = base_tokens[0] for sub_token in sub_tokens: self.value(sub_token) # finally, build the outcome outcome = Anything() outcome.position = base_tokens[0].position return outcome # evaluate all the base_tokens, trying to obtain their values values = [] tokens = [] for token in base_tokens: t = self.execute_token(token) v = self.value(t) tokens.append(t) values.append(v) # try to find a match for any expression, both typed and un-typed case_found = None possible_expressions = values_to_expressions(values, EXPRESSIONS_MAP, EXPRESSIONS) for case in possible_expressions: if case.is_signature_match(values): # match first occurrence case_found = case break if case_found: # if exact match, we run the expression. if case_found.is_match(values): # parse and execute the string that is code (to count usage of variables) if case_found.keyword == Keyword('isnil') and type(values[1]) == String or \ case_found.keyword == Keyword('configClasses'): code_position = { 'isnil': 1, 'configclasses': 0 }[case_found.keyword.unique_token] extra_scope = { 'isnil': None, 'configclasses': { '_x': Anything() } }[case_found.keyword.unique_token] # when the string is undefined, there is no need to evaluate it. if not values[code_position].is_undefined: try: code = Code([parse(values[code_position].value)]) code.position = values[code_position].position self.execute_code(code, extra_scope=extra_scope) except SQFParserError as e: self.exceptions.append( SQFParserError( values[code_position].position, 'Error while parsing a string to code: %s' % e.message)) # finally, execute the statement outcome = case_found.execute(values, self) elif len(possible_expressions) == 1 or all_equal( [x.return_type for x in possible_expressions]): return_type = possible_expressions[0].return_type if isinstance(case_found, (ForEachExpression, ElseExpression)): outcome = Anything() elif return_type is not None: outcome = return_type() if return_type == ForType: outcome.copy(values[0]) elif case_found.keyword == Keyword('call'): outcome = Anything() else: # when a case is found but we cannot decide on the type, it is anything outcome = Anything() extra_scope = None if case_found.keyword in (Keyword('select'), Keyword('apply'), Keyword('count')): extra_scope = {'_x': Anything()} elif case_found.keyword == Keyword('foreach'): extra_scope = {'_foreachindex': Number(), '_x': Anything()} elif case_found.keyword == Keyword('catch'): extra_scope = {'_exception': Anything()} elif case_found.keyword == Keyword('spawn'): extra_scope = {'_thisScript': Script(), '_this': values[0]} elif case_found.keyword == Keyword('do') and type( values[0]) == ForType: extra_scope = {values[0].variable.value: Number()} for value, t_or_v in zip(values, case_found.types_or_values): # execute all pieces of code if t_or_v == Code and isinstance( value, Code) and self.code_key( value) not in self._executed_codes: if case_found.keyword == Keyword('spawn'): self.execute_unexecuted_code(self.code_key(value), extra_scope, True) # this code was executed, so it does not need to be evaluated on an un-executed env. del self._unexecuted_codes[self.code_key(value)] else: self.execute_code( value, extra_scope=extra_scope, namespace_name=self.current_namespace.name, delete_mode=True) # remove evaluated interpreter tokens if isinstance( value, InterpreterType ) and value in self.unevaluated_interpreter_tokens: self.unevaluated_interpreter_tokens.remove(value) assert (isinstance(outcome, Type)) elif len(values) == 1: if not isinstance(values[0], Type): self.exception( SQFParserError( statement.position, '"%s" is syntactically incorrect (missing ;?)' % statement)) outcome = values[0] elif isinstance(base_tokens[0], Variable) and base_tokens[0].is_global: # statements starting with a global are likely defined somewhere else # todo: catch globals with statements and without statements pass elif len(possible_expressions) > 0: if isinstance(possible_expressions[0], UnaryExpression): types_or_values = [] for exp in possible_expressions: types_or_values.append(exp.types_or_values[1].__name__) keyword_name = possible_expressions[0].types_or_values[0].value message = 'Unary operator "%s" only accepts argument of types [%s] (rhs is %s)' % \ (keyword_name, ','.join(types_or_values), values[1].__class__.__name__) elif isinstance(possible_expressions[0], BinaryExpression): types_or_values = [] for exp in possible_expressions: types_or_values.append('(%s,%s)' % (exp.types_or_values[0].__name__, exp.types_or_values[2].__name__)) keyword_name = possible_expressions[0].types_or_values[1].value message = 'Binary operator "{0}" arguments must be [{1}]'.format( keyword_name, ','.join(types_or_values)) if values[0].__class__.__name__ not in [ x[0] for x in types_or_values ]: message += ' (lhs is %s' % values[0].__class__.__name__ if values[0].__class__.__name__ not in [ x[1] for x in types_or_values ]: message += ', rhs is %s)' % values[2].__class__.__name__ else: message += ')' else: assert False self.exception(SQFParserError(values[1].position, message)) # so the error does not propagate further outcome = Anything() outcome.position = base_tokens[0].position else: helper = ' '.join( ['<%s(%s)>' % (type(t).__name__, t) for t in tokens]) self.exception( SQFParserError( base_tokens[-1].position, 'can\'t interpret statement (missing ;?): %s' % helper)) # so the error does not propagate further outcome = Anything() outcome.position = base_tokens[0].position if isinstance(outcome, InterpreterType) and \ outcome not in self.unevaluated_interpreter_tokens and type(outcome) not in (SwitchType, PrivateType, DefineStatement): # switch type can be not evaluated, e.g. for `case A; case B: {}` self.unevaluated_interpreter_tokens.append(outcome) assert (isinstance(outcome, BaseType)) # the position of Private is different because it can be passed from analyzer to analyzer, # and we want to keep the position of the outermost analyzer. if not isinstance(outcome, PrivateType): outcome.position = base_tokens[0].position if statement.ending: outcome = Nothing() outcome.position = base_tokens[0].position return outcome
def execute_single(self, statement): assert (isinstance(statement, Statement)) outcome = Nothing() outcome.position = statement.position base_tokens = [] for token in statement.tokens: if not statement.is_base_token(token): self.execute_other(token) else: base_tokens.append(token) if not base_tokens: return outcome # operations that cannot evaluate the value of all base_tokens if type(base_tokens[0]) == DefineStatement: return base_tokens[0] elif base_tokens[0] == Preprocessor("#include"): if len(base_tokens) != 2: exception = SQFParserError(base_tokens[0].position, "#include requires one argument") self.exception(exception) elif type(self.execute_token(base_tokens[1])) != String: exception = SQFParserError( base_tokens[0].position, "#include first argument must be a string") self.exception(exception) return outcome elif isinstance(base_tokens[0], Keyword) and base_tokens[0].value in PREPROCESSORS: # remaining preprocessors are ignored return outcome elif len(base_tokens) == 2 and base_tokens[0] == Keyword('private'): # the rhs may be a variable, so we cannot get the value rhs = self.execute_token(base_tokens[1]) if isinstance(rhs, String): self.add_privates([rhs]) elif isinstance(rhs, Array): value = self.value(rhs) if value.is_undefined: self.exception( SQFWarning( base_tokens[0].position, 'Obfuscated statement. Consider explicitly set what is private.' )) else: self.add_privates(value) elif isinstance(rhs, Variable): var = String('"' + rhs.name + '"') var.position = rhs.position self.add_privates([var]) outcome = PrivateType(rhs) outcome.position = rhs.position self.privates.add(outcome) else: self.exception( SQFParserError(base_tokens[0].position, '`private` used incorrectly')) return outcome # assignment operator elif len(base_tokens) == 3 and base_tokens[1] == Keyword('='): lhs = self.execute_token(base_tokens[0]) if isinstance(lhs, PrivateType): self.privates.remove(lhs) lhs = lhs.variable else: lhs = self.get_variable(base_tokens[0]) if not isinstance(lhs, Variable): self.exception( SQFParserError( base_tokens[0].position, 'lhs of assignment operator must be a variable')) else: # if the rhs_v is code and calls `lhs` (recursion) it will assume lhs is anything (and not Nothing) scope = self.get_scope(lhs.name) if lhs.name not in scope or isinstance(scope[lhs.name], Nothing): scope[lhs.name] = Anything() rhs_v = self.value(base_tokens[2]) self.assign(lhs, rhs_v) if not statement.ending: outcome = rhs_v return outcome # A variable can only be evaluated if we need its value, so we will not call its value until the very end. elif len(base_tokens) == 1 and type( base_tokens[0]) in (Variable, Array): return self.execute_token(base_tokens[0]) # heuristic for defines (that are thus syntactically correct): # - is keyword but upper cased # - first token string starts uppercased elif len(base_tokens) == 1 and type(base_tokens[0]) == Keyword and str( base_tokens[0])[0].isupper(): outcome = Variable(str(base_tokens[0])) outcome.position = base_tokens[0].position return outcome elif is_undefined_define(base_tokens): # get all arguments and compute their value to analyze them if isinstance(base_tokens[1].base_tokens[0], Statement): sub_tokens = base_tokens[1].base_tokens[0].base_tokens else: sub_tokens = base_tokens[0] for sub_token in sub_tokens: self.value(sub_token) # finally, build the outcome outcome = Anything() outcome.position = base_tokens[0].position return outcome # evaluate all the base_tokens, trying to obtain their values values = [] tokens = [] for token in base_tokens: t = self.execute_token(token) v = self.value(t) tokens.append(t) values.append(v) # try to find a match for any expression, both typed and un-typed case_found = None possible_expressions = values_to_expressions(values, EXPRESSIONS_MAP, EXPRESSIONS) for case in possible_expressions: if case.is_signature_match(values): # match first occurrence case_found = case break
def parse_block(all_tokens, analyze_tokens, start=0, initial_lvls=None, stop_statement='both', defines=None): if not initial_lvls: initial_lvls = _LEVELS if defines is None: defines = defaultdict(dict) lvls = initial_lvls.copy() statements = [] tokens = [] i = start if not all_tokens: return Statement([]), 0 while i < len(all_tokens): token = all_tokens[i] # begin #ifdef controls if lvls['ifdef'] and token in OPEN_PARENTHESIS: lvls['ifdef_open_close'] += 1 stop = False if token in (Preprocessor('#ifdef'), Preprocessor('#ifndef')): stop = True lvls['ifdef'] += 1 expression, size = parse_block(all_tokens, _analyze_simple, i + 1, lvls, stop_statement, defines=defines) lvls['ifdef'] -= 1 if lvls['ifdef'] == 0: assert (isinstance(expression, IfDefStatement)) replacing_expression = parse_ifdef_block( expression, defines, get_coord(all_tokens[:i - 1])) new_all_tokens = sqf.base_type.get_all_tokens( tokens + replacing_expression) result, _ = parse_block(new_all_tokens, analyze_tokens, 0, None, stop_statement, defines=defines) expression.prepend(tokens) expression = IfDefResult(expression, result.tokens) statements.append(expression) len_expression = len(expression.get_all_tokens()) i += len_expression - len(tokens) - 1 tokens = [] else: tokens.append(expression) i += size + 1 # finish ifdef elif is_finish_ifdef_condition(tokens, lvls) and ( is_end_statement(token, stop_statement) or is_finish_ifdef_parenthesis(token, lvls) ) or lvls['ifdef'] > 1 and token == Preprocessor('#endif'): if token != EndOfFile() and token not in CLOSE_PARENTHESIS: tokens.append(token) if_def = finish_ifdef(tokens, all_tokens, start, statements) return if_def, i - start # parse during ifdef elif lvls['ifdef'] != 0: stop = True tokens.append(token) # end ifdef controls if lvls['ifdef'] and token in (STOP_KEYWORDS['single'] + CLOSE_PARENTHESIS): lvls['ifdef_open_close'] -= 1 if lvls['ifdef_open_close'] < 0: lvls['ifdef_open_close'] = 0 if stop: pass # try to match a #defined and get the arguments elif str(token) in defines: # is a define stop, define_statement, arg_indexes = find_match_if_def( all_tokens, i, defines, token) if stop: arg_number = len(define_statement.args) extra_tokens_to_move = 1 + 2 * ( arg_number != 0) + 2 * arg_number - 1 * (arg_number != 0) replaced_expression = all_tokens[i:i + extra_tokens_to_move] # the `all_tokens` after replacement replacing_expression = replace_in_expression( define_statement.expression, define_statement.args, arg_indexes, all_tokens) new_all_tokens = all_tokens[:i - len( tokens)] + tokens + replacing_expression + all_tokens[ i + extra_tokens_to_move:] new_start = i - len(tokens) expression, size = parse_block(new_all_tokens, analyze_tokens, new_start, lvls, stop_statement, defines=defines) # the all_tokens of the statement before replacement original_tokens_taken = len(replaced_expression) - len( replacing_expression) + size original_tokens = all_tokens[i - len(tokens):i - len(tokens) + original_tokens_taken] if isinstance(expression, Statement): expression = expression.content[0] if type(original_tokens[-1]) in (EndOfLine, Comment, EndOfFile): del original_tokens[-1] original_tokens_taken -= 1 expression = DefineResult(original_tokens, define_statement, expression) statements.append(expression) i += original_tokens_taken - len(tokens) - 1 tokens = [] if stop: pass elif token == ParserKeyword('['): lvls['[]'] += 1 expression, size = parse_block(all_tokens, analyze_tokens, i + 1, lvls, stop_statement='single', defines=defines) lvls['[]'] -= 1 tokens.append(expression) i += size + 1 elif token == ParserKeyword('('): lvls['()'] += 1 expression, size = parse_block(all_tokens, analyze_tokens, i + 1, lvls, stop_statement, defines=defines) lvls['()'] -= 1 tokens.append(expression) i += size + 1 elif token == ParserKeyword('{'): lvls['{}'] += 1 expression, size = parse_block(all_tokens, analyze_tokens, i + 1, lvls, stop_statement, defines=defines) lvls['{}'] -= 1 tokens.append(expression) i += size + 1 elif token == ParserKeyword(']'): if lvls['[]'] == 0: raise SQFParenthesisError( get_coord(all_tokens[:i]), 'Trying to close right parenthesis without them opened.') if statements: if isinstance(statements[0], DefineResult): statements[0]._tokens = [ Array( _analyze_array(statements[0]._tokens, analyze_tokens, all_tokens[:i])) ] return statements[0], i - start else: raise SQFParserError( get_coord(all_tokens[:i]), 'A statement %s cannot be in an array' % Statement(statements)) return Array(_analyze_array(tokens, analyze_tokens, all_tokens[:i])), i - start elif token == ParserKeyword(')'): if lvls['()'] == 0: raise SQFParenthesisError( get_coord(all_tokens[:i]), 'Trying to close parenthesis without opened parenthesis.') if tokens: statements.append(analyze_tokens(tokens)) return Statement(statements, parenthesis=True), i - start elif token == ParserKeyword('}'): if lvls['{}'] == 0: raise SQFParenthesisError( get_coord(all_tokens[:i]), 'Trying to close brackets without opened brackets.') if tokens: statements.append(analyze_tokens(tokens)) return Code(statements), i - start # end of statement when not in preprocessor states elif all(lvls[lvl_type] == 0 for lvl_type in ('#define', '#include')) and is_end_statement( token, stop_statement): if type(token) != EndOfFile: tokens.append(token) if tokens: statements.append(analyze_tokens(tokens)) tokens = [] elif token in (Preprocessor('#define'), Preprocessor('#include')): # notice that `token` is ignored here. It will be picked up in the end if tokens: # a pre-processor starts a new statement statements.append(analyze_tokens(tokens)) tokens = [] lvls[token.value] += 1 expression, size = parse_block(all_tokens, analyze_tokens, i + 1, lvls, stop_statement, defines=defines) lvls[token.value] -= 1 statements.append(expression) i += size elif token == Keyword('#') and lvls['#define'] != 0: # The # sqf command is superseded by the preprocessor directive's stringification command tokens.append(Preprocessor('#')) elif type(token) in (EndOfLine, Comment, EndOfFile) and any( lvls[x] != 0 for x in {'#define', '#include'}): tokens.insert( 0, all_tokens[start - 1]) # pick the token that triggered the statement if tokens[0] == Preprocessor('#define'): define_statement = _analyze_define(tokens) defines[define_statement.variable_name][len( define_statement.args)] = define_statement statements.append(define_statement) else: statements.append(analyze_tokens(tokens)) return Statement(statements), i - start elif type(token) != EndOfFile: tokens.append(token) i += 1 if is_finish_ifdef_condition(tokens, lvls): return finish_ifdef(tokens, all_tokens, start, statements), i - start for lvl_type in ('[]', '()', '{}', 'ifdef'): if lvls[lvl_type] != 0: message = 'Parenthesis "%s" not closed' % lvl_type[0] if lvl_type == 'ifdef': message = '#ifdef statement not closed' raise SQFParenthesisError(get_coord(all_tokens[:start - 1]), message) if tokens: statements.append(analyze_tokens(tokens)) return Statement(statements), i - start
def parse_strings_and_comments(all_tokens): """ Function that parses the strings of a script, transforming them into `String`. """ string = '' # the buffer for the activated mode tokens = [] # the final result in_double = False mode = None # [None, "string_single", "string_double", "comment_line", "comment_bulk"] for i, token in enumerate(all_tokens): if mode == "string_double": string += token if token == '"': if in_double: in_double = False elif not in_double and i != len(all_tokens) - 1 and all_tokens[ i + 1] == '"': in_double = True else: tokens.append(String(string)) mode = None in_double = False elif mode == "string_single": string += token if token == "'": if in_double: in_double = False elif not in_double and i != len(all_tokens) - 1 and all_tokens[ i + 1] == "'": in_double = True else: tokens.append(String(string)) mode = None in_double = False elif mode == "comment_bulk": string += token if token == '*/': mode = None tokens.append(Comment(string)) string = '' elif mode == "comment_line": string += token if token in ('\n', '\r\n'): mode = None tokens.append(Comment(string)) string = '' else: # mode is None if token == '"': string = token mode = "string_double" elif token == "'": string = token mode = "string_single" elif token == '/*': string = token mode = "comment_bulk" elif token == '//': string = token mode = "comment_line" else: tokens.append(token) if mode in ("comment_line", "comment_bulk"): tokens.append(Comment(string)) elif mode is not None: raise SQFParserError(get_coord(tokens), 'String is not closed') return tokens