def make_node(cls, lineno: int, s, lower, upper): """ Creates a node for a string slice. S is the string expression Tree. Lower and upper are the bounds, if lower & upper are constants, and s is also constant, then a string constant is returned. If lower > upper, an empty string is returned. """ if lower is None or upper is None or s is None: return None if not check.check_type(lineno, Type.string, s): return None lo = up = None base = NUMBER(config.OPTIONS.string_base, lineno=lineno) lower = TYPECAST.make_node( gl.SYMBOL_TABLE.basic_types[gl.STR_INDEX_TYPE], BINARY.make_node('MINUS', lower, base, lineno=lineno, func=lambda x, y: x - y), lineno) upper = TYPECAST.make_node( gl.SYMBOL_TABLE.basic_types[gl.STR_INDEX_TYPE], BINARY.make_node('MINUS', upper, base, lineno=lineno, func=lambda x, y: x - y), lineno) if lower is None or upper is None: return None if check.is_number(lower): lo = lower.value if lo < gl.MIN_STRSLICE_IDX: lower.value = lo = gl.MIN_STRSLICE_IDX if check.is_number(upper): up = upper.value if up > gl.MAX_STRSLICE_IDX: upper.value = up = gl.MAX_STRSLICE_IDX if check.is_number(lower, upper): if lo > up: return STRING('', lineno) if s.token == 'STRING': # A constant string? Recalculate it now up += 1 st = s.value.ljust(up) # Procrustean filled (right) return STRING(st[lo:up], lineno) # a$(0 TO INF.) = a$ if lo == gl.MIN_STRSLICE_IDX and up == gl.MAX_STRSLICE_IDX: return s return cls(s, lower, upper, lineno)
def make_node(cls, lineno, operator, operand, func=None, type_=None): """ Creates a node for a unary operation. E.g. -x or LEN(a$) Parameters: -func: lambda function used on constant folding when possible -type_: the resulting type (by default, the same as the argument). For example, for LEN (str$), result type is 'u16' and arg type is 'string' """ assert type_ is None or isinstance(type_, SymbolTYPE) if func is not None: # Try constant-folding if check.is_number(operand): # e.g. ABS(-5) return SymbolNUMBER(func(operand.value), lineno=lineno) elif check.is_string(operand): # e.g. LEN("a") return SymbolSTRING(func(operand.text), lineno=lineno) if type_ is None: type_ = operand.type_ if operator == 'MINUS': if not type_.is_signed: type_ = type_.to_signed() operand = SymbolTYPECAST.make_node(type_, operand, lineno) elif operator == 'NOT': type_ = TYPE.ubyte return cls(operator, operand, lineno, type_)
def offset(self): """ If this is a constant access (e.g. A(1)) return the offset in bytes from the beginning of the variable in memory. Otherwise, if it's not constant (e.g. A(i)) returns None """ if self.scope == SCOPE.parameter: return None offset = 0 # Now we must typecast each argument to a u16 (POINTER) type # i is the dimension ith index, b is the bound for i, b in zip(self.arglist, self.entry.bounds): tmp = i.children[0] if check.is_number(tmp) or check.is_const(tmp): if offset is not None: offset = offset * b.count + tmp.value else: offset = None break if offset is not None: offset *= self.type_.size return offset
def visit_IF(self, node): expr_ = (yield ToVisit(node.children[0])) then_ = (yield ToVisit(node.children[1])) else_ = (yield ToVisit(node.children[2])) if len(node.children) == 3 else self.NOP if self.O_LEVEL >= 1: if chk.is_null(then_, else_): src.api.errmsg.warning_empty_if(node.lineno) yield self.NOP return block_accessed = chk.is_block_accessed(then_) or chk.is_block_accessed(else_) if not block_accessed and chk.is_number(expr_): # constant condition if expr_.value: # always true (then_) yield then_ else: # always false (else_) yield else_ return if chk.is_null(else_) and len(node.children) == 3: node.children.pop() # remove empty else yield node return for i in range(len(node.children)): node.children[i] = (expr_, then_, else_)[i] yield node
def declare_variable(self, id_, lineno, type_, default_value=None): """ Like the above, but checks that entry.declared is False. Otherwise raises an error. Parameter default_value specifies an initialized variable, if set. """ assert isinstance(type_, symbols.TYPEREF) if not self.check_is_undeclared(id_, lineno, scope=self.current_scope, show_error=False): entry = self.get_entry(id_) if entry.scope == SCOPE.parameter: syntax_error(lineno, "Variable '%s' already declared as a parameter " "at %s:%i" % (id_, entry.filename, entry.lineno)) else: syntax_error(lineno, "Variable '%s' already declared at " "%s:%i" % (id_, entry.filename, entry.lineno)) return None if not self.check_class(id_, CLASS.var, lineno, scope=self.current_scope): return None entry = (self.get_entry(id_, scope=self.current_scope) or self.declare(id_, lineno, symbols.VAR(id_, lineno, class_=CLASS.var))) __DEBUG__("Entry %s declared with class %s at scope %i" % (entry.name, CLASS.to_string(entry.class_), self.current_scope)) if entry.type_ is None or entry.type_ == self.basic_types[TYPE.unknown]: entry.type_ = type_ if entry.type_ != type_: if not type_.implicit and entry.type_ is not None: syntax_error(lineno, "'%s' suffix is for type '%s' but it was " "declared as '%s'" % (id_, entry.type_, type_)) return None entry.scope = SCOPE.global_ if self.current_scope == self.global_scope else SCOPE.local entry.callable = False entry.class_ = CLASS.var # HINT: class_ attribute could be erased if access_id was used. entry.declared = True # marks it as declared if entry.type_.implicit and entry.type_ != self.basic_types[TYPE.unknown]: warning_implicit_type(lineno, id_, entry.type_.name) if default_value is not None and entry.type_ != default_value.type_: if check.is_number(default_value): default_value = symbols.TYPECAST.make_node(entry.type_, default_value, lineno) if default_value is None: return None else: syntax_error(lineno, "Variable '%s' declared as '%s' but initialized " "with a '%s' value" % (id_, entry.type_, default_value.type_)) return None entry.default_value = default_value return entry
def visit_WHILE(self, node): expr_ = (yield node.children[0]) body_ = (yield node.children[1]) if self.O_LEVEL >= 1: if chk.is_number(expr_) and not expr_.value and not chk.is_block_accessed(body_): yield self.NOP return for i, child in enumerate((expr_, body_)): node.children[i] = child yield node
def visit_FOR(self, node): from_ = (yield node.children[1]) to_ = (yield node.children[2]) step_ = (yield node.children[3]) body_ = (yield node.children[4]) if self.O_LEVEL > 0 and chk.is_number(from_, to_, step_) and not chk.is_block_accessed(body_): if from_ > to_ and step_ > 0: yield self.NOP return if from_ < to_ and step_ < 0: yield self.NOP return for i, child in enumerate((from_, to_, step_, body_), start=1): node.children[i] = child yield node
def make_node(cls, id_: str, arglist: SymbolARGLIST, lineno: int, filename: str) -> Optional['SymbolARRAYACCESS']: """ Creates an array access. A(x1, x2, ..., xn) """ assert isinstance(arglist, SymbolARGLIST) variable = gl.SYMBOL_TABLE.access_array(id_, lineno) if variable is None: return None if variable.scope != SCOPE.parameter: if len(variable.bounds) != len(arglist): errmsg.error(lineno, "Array '%s' has %i dimensions, not %i" % (variable.name, len(variable.bounds), len(arglist))) return None # Checks for array subscript range if the subscript is constant # e.g. A(1) is a constant subscript access btype = gl.SYMBOL_TABLE.basic_types[gl.BOUND_TYPE] for i, b in zip(arglist, variable.bounds): lower_bound = NUMBER(b.lower, type_=btype, lineno=lineno) if check.is_number(i.value) or check.is_const(i.value): val = i.value.value if val < b.lower or val > b.upper: errmsg.warning(lineno, "Array '%s' subscript out of range" % id_) i.value = BINARY.make_node('MINUS', TYPECAST.make_node(btype, i.value, lineno), lower_bound, lineno, func=lambda x, y: x - y, type_=btype) else: btype = gl.SYMBOL_TABLE.basic_types[gl.BOUND_TYPE] for arg in arglist: arg.value = TYPECAST.make_node(btype, arg.value, arg.value.lineno) # Returns the variable entry and the node return cls(variable, arglist, lineno, filename)
def make_node(cls, new_type, node, lineno): """ Creates a node containing the type cast of the given one. If new_type == node.type, then nothing is done, and the same node is returned. Returns None on failure (and calls syntax_error) """ assert isinstance(new_type, SymbolTYPE) # None (null) means the given AST node is empty (usually an error) if node is None: return None # Do nothing. Return None assert isinstance(node, Symbol), '<%s> is not a Symbol' % node # The source and dest types are the same if new_type == node.type_: return node # Do nothing. Return as is # TODO: Create a base scalar type if isinstance(node, SymbolVARARRAY): if new_type.size == node.type_.size and TYPE.string not in (new_type, node.type_): return node error(lineno, "Array {} type does not match parameter type".format(node.name)) return None STRTYPE = TYPE.string # Typecasting, at the moment, only for number if node.type_ == STRTYPE: error(lineno, 'Cannot convert string to a value. Use VAL() function') return None # Converting from string to number is done by STR if new_type == STRTYPE: error(lineno, 'Cannot convert value to string. Use STR() function') return None # If the given operand is a constant, perform a static typecast if check.is_CONST(node): node.expr = cls(new_type, node.expr, lineno) return node if not check.is_number(node) and not check.is_const(node): return cls(new_type, node, lineno) # It's a number. So let's convert it directly if check.is_const(node): node = SymbolNUMBER(node.value, node.lineno, node.type_) if new_type.is_basic and not TYPE.is_integral(new_type): # not an integer node.value = float(node.value) else: # It's an integer new_val = (int(node.value) & ((1 << (8 * new_type.size)) - 1)) # Mask it if node.value >= 0 and node.value != new_val: errmsg.warning_conversion_lose_digits(node.lineno) node.value = new_val elif node.value < 0 and (1 << (new_type.size * 8)) + \ node.value != new_val: # Test for positive to negative coercion errmsg.warning_conversion_lose_digits(node.lineno) node.value = new_val - (1 << (new_type.size * 8)) node.type_ = new_type return node
def visit_CONST(self, node): if chk.is_number(node.expr) or chk.is_const(node.expr): yield node.expr else: yield node
def make_node(cls, operator, left, right, lineno, func=None, type_=None): """ Creates a binary node for a binary operation, e.g. A + 6 => '+' (A, 6) in prefix notation. Parameters: -operator: the binary operation token. e.g. 'PLUS' for A + 6 -left: left operand -right: right operand -func: is a lambda function used when constant folding is applied -type_: resulting type (to enforce it). If no type_ is specified the resulting one will be guessed. """ if left is None or right is None: return None a, b = left, right # short form names # Check for constant non-numeric operations c_type = check.common_type(a, b) # Resulting operation type or None if c_type: # there must be a common type for a and b if check.is_numeric(a, b) and (check.is_const(a) or check.is_number(a)) and \ (check.is_const(b) or check.is_number(b)): if func is not None: a = SymbolTYPECAST.make_node(c_type, a, lineno) # ensure type b = SymbolTYPECAST.make_node(c_type, b, lineno) # ensure type return SymbolNUMBER(func(a.value, b.value), type_=type_, lineno=lineno) if check.is_static(a, b): a = SymbolTYPECAST.make_node(c_type, a, lineno) # ensure type b = SymbolTYPECAST.make_node(c_type, b, lineno) # ensure type return SymbolCONST(cls(operator, a, b, lineno, type_=type_, func=func), lineno=lineno) if operator in {'BNOT', 'BAND', 'BOR', 'BXOR', 'NOT', 'AND', 'OR', 'XOR', 'MINUS', 'MULT', 'DIV', 'SHL', 'SHR'} and \ not check.is_numeric(a, b): errmsg.error(lineno, 'Operator %s cannot be used with STRINGS' % operator) return None if check.is_string(a, b) and func is not None: # Are they STRING Constants? if operator == 'PLUS': return SymbolSTRING(func(a.value, b.value), lineno) return SymbolNUMBER(int(func(a.text, b.text)), type_=TYPE.ubyte, lineno=lineno) # Convert to u8 (boolean) if operator in ('BNOT', 'BAND', 'BOR', 'BXOR'): if TYPE.is_decimal(c_type): c_type = TYPE.long_ if a.type_ != b.type_ and TYPE.string in (a.type_, b.type_): c_type = a.type_ # Will give an error based on the fist operand if operator not in ('SHR', 'SHL'): a = SymbolTYPECAST.make_node(c_type, a, lineno) b = SymbolTYPECAST.make_node(c_type, b, lineno) if a is None or b is None: return None if type_ is None: if operator in ('LT', 'GT', 'EQ', 'LE', 'GE', 'NE', 'AND', 'OR', 'XOR', 'NOT'): type_ = TYPE.ubyte # Boolean type else: type_ = c_type return cls(operator, a, b, type_=type_, lineno=lineno)