# HG changeset patch # User Paul Boddie # Date 1487939227 -3600 # Node ID 7f284b0b42b93ad8e81e2fa661183863cf8b57e2 # Parent 65cadf3264bc5c4a7e58adee53ff9be36300a33e# Parent 5dfbfa8e22880f14f24e2a1ee88c23a758b5adc7 Merged the method-wrapper-for-context branch into attr-strvalue-without-size. diff -r 65cadf3264bc -r 7f284b0b42b9 branching.py --- a/branching.py Mon Feb 13 21:40:34 2017 +0100 +++ b/branching.py Fri Feb 24 13:27:07 2017 +0100 @@ -4,7 +4,7 @@ Track attribute usage for names. Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, - 2014, 2015, 2016 Paul Boddie + 2014, 2015, 2016, 2017 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -75,7 +75,11 @@ if name in self.assignments: return [self] else: - return [b for b in self.get_all_suppliers(name) if name in b.assignments] + sources = [] + for b in self.get_all_suppliers(name): + if name in b.assignments: + sources.append(b) + return sources def set_usage(self, name, attrname, invocation=False, assignment=False): @@ -597,7 +601,10 @@ d = {} for name, branches in self.assignments.items(): - d[name] = [branch.values.get(name) for branch in branches] + l = [] + for branch in branches: + l.append(branch.values.get(name)) + d[name] = l return d # Special objects. diff -r 65cadf3264bc -r 7f284b0b42b9 common.py --- a/common.py Mon Feb 13 21:40:34 2017 +0100 +++ b/common.py Fri Feb 24 13:27:07 2017 +0100 @@ -23,7 +23,7 @@ from compiler.transformer import Transformer from errors import InspectError from os import listdir, makedirs, remove -from os.path import exists, isdir, join, split +from os.path import exists, getmtime, isdir, join, split from results import ConstantValueRef, LiteralSequenceRef, NameRef import compiler.ast @@ -76,6 +76,36 @@ else: remove(path) +def copy(source, target, only_if_newer=True): + + "Copy a text file from 'source' to 'target'." + + if isdir(target): + target = join(target, split(source)[-1]) + + if only_if_newer and not is_newer(source, target): + return + + infile = open(source) + outfile = open(target, "w") + + try: + outfile.write(infile.read()) + finally: + outfile.close() + infile.close() + +def is_newer(source, target): + + "Return whether 'source' is newer than 'target'." + + if exists(target): + target_mtime = getmtime(target) + source_mtime = getmtime(source) + return source_mtime > target_mtime + + return True + class CommonModule: "A common module representation." @@ -741,14 +771,16 @@ return isinstance(node.expr, compiler.ast.Getattr) - def get_name_for_tracking(self, name, path=None): + def get_name_for_tracking(self, name, name_ref=None): """ Return the name to be used for attribute usage observations involving - the given 'name' in the current namespace. If 'path' is indicated and - the name is being used outside a function, return the path value; - otherwise, return a path computed using the current namespace and the - given name. + the given 'name' in the current namespace. + + If the name is being used outside a function, and if 'name_ref' is + given, a path featuring the name in the global namespace is returned + where 'name_ref' indicates a global. Otherwise, a path computed using + the current namespace and the given name is returned. The intention of this method is to provide a suitably-qualified name that can be tracked across namespaces. Where globals are being @@ -766,10 +798,10 @@ if self.in_function: return name - # For static namespaces, use the given qualified name. + # For global names outside functions, use a global name. - elif path: - return path + elif name_ref and name_ref.is_global_name(): + return self.get_global_path(name) # Otherwise, establish a name in the current namespace. diff -r 65cadf3264bc -r 7f284b0b42b9 deducer.py --- a/deducer.py Mon Feb 13 21:40:34 2017 +0100 +++ b/deducer.py Fri Feb 24 13:27:07 2017 +0100 @@ -917,9 +917,9 @@ # For each version of the name, obtain the access location. - for version, (original_name, attrnames, access_number) in all_aliases.items(): + for version, (original_path, original_name, attrnames, access_number) in all_aliases.items(): accessor_location = (path, name, None, version) - access_location = (path, original_name, attrnames, access_number) + access_location = (original_path, original_name, attrnames, access_number) init_item(self.alias_index, accessor_location, list) self.alias_index[accessor_location].append(access_location) @@ -2088,7 +2088,9 @@ # All other methods of access involve traversal. else: - final_method = is_assignment and "assign" or "access" + final_method = is_assignment and "assign" or \ + is_invocation and "access-invoke" or \ + "access" origin = None # First attribute accessed at a known position via the accessor. diff -r 65cadf3264bc -r 7f284b0b42b9 docs/lplc.1 --- a/docs/lplc.1 Mon Feb 13 21:40:34 2017 +0100 +++ b/docs/lplc.1 Fri Feb 24 13:27:07 2017 +0100 @@ -24,31 +24,39 @@ The following options may be specified: .PP .TP -.B \-c -Only partially compile the program; do not attempt to build or link it +.BR \-c ", " \-\-compile +Only partially compile the program; do not build or link it .TP -.B \-E +.BR \-E ", " \-\-no\-env Ignore environment variables affecting the module search path .TP -.B \-g +.BR \-g ", " \-\-debug Generate debugging information for the built executable .TP -.B \-P +.BR \-G ", " \-\-gc\-sections +Remove superfluous sections of the built executable by applying the +.B \-\-gc\-sections +linker option and associated compiler options +.TP +.BR \-P ", " \-\-show\-path Show the module search path .TP -.B \-q +.BR \-q ", " \-\-quiet Silence messages produced when building an executable .TP -.B \-r -Reset (discard) cached program information; inspect the whole program again +.BR \-r ", " \-\-reset +Reset (discard) cached information; inspect the whole program again .TP -.B \-t +.BR \-R ", " \-\-reset\-all +Reset (discard) all program details including translated code +.TP +.BR \-t ", " \-\-no\-timing Silence timing messages .TP -.B \-tb +.BR \-tb ", " \-\-traceback Provide a traceback for any internal errors (development only) .TP -.B \-v +.BR \-v ", " \-\-verbose Report compiler activities in a verbose fashion (development only) .PP Some options may be followed by values, either immediately after the option @@ -79,7 +87,35 @@ .TP .BR \-V ", " \-\-version Show version information for this tool -.SH ENVIRONMENT VARIABLES +.SH EXAMPLES +Compile the main program in +.BR hello.py , +including all source files that the program requires: +.IP +lplc -o hello hello.py +.PP +This produces an output executable called +.B hello +in the current directory, assuming that +.B hello.py +can be compiled without errors. +.SH FILES +.B lplc +produces an output executable file called +.B _main +unless the +.B \-o +option is given with a different name. Working data is stored in a directory +whose name is derived from the output executable name. Therefore, the working +data directory will be called +.B _main.lplc +unless otherwise specified. For example, an output executable called +.B hello +will have a working data directory called +.BR hello.lplc . +This is intended to allow work to proceed efficiently on multiple programs in +the same directory, although it can also create lots of unwanted directories. +.SH ENVIRONMENT .TP ARCH Indicates a prefix to be used with tool names when building an executable. This @@ -93,7 +129,7 @@ A collection of directories that are searched before those in the collection comprising the default "module search path". This collection, if already defined in the environment, may be excluded by specifying the -.B \-E +.BR \-E " (or " \-\-no\-env ) option. .SH AUTHOR Paul Boddie diff -r 65cadf3264bc -r 7f284b0b42b9 encoders.py --- a/encoders.py Mon Feb 13 21:40:34 2017 +0100 +++ b/encoders.py Fri Feb 24 13:27:07 2017 +0100 @@ -21,6 +21,21 @@ from common import first, InstructionSequence + + +# Value digest computation. + +from base64 import b64encode +from hashlib import sha1 + +def digest(values): + m = sha1() + for value in values: + m.update(str(value)) + return b64encode(m.digest()).replace("+", "__").replace("/", "_").rstrip("=") + + + # Output encoding and decoding for the summary files. def encode_attrnames(attrnames): @@ -207,13 +222,25 @@ ) static_ops = ( - "__load_static", + "__load_static_ignore", "__load_static_replace", "__load_static_test", "", + ) + +context_values = ( + "", + ) + +context_ops = ( + "", "", "", "", + ) + +context_op_functions = ( + "", "", ) reference_acting_ops = attribute_ops + checked_ops + typename_ops attribute_producing_ops = attribute_loading_ops + checked_loading_ops -def encode_access_instruction(instruction, subs): +def encode_access_instruction(instruction, subs, context_index): """ Encode the 'instruction' - a sequence starting with an operation and @@ -223,6 +250,9 @@ The 'subs' parameter defines a mapping of substitutions for special values used in instructions. + The 'context_index' parameter defines the position in local context storage + for the referenced context or affected by a context operation. + Return both the encoded instruction and a collection of substituted names. """ @@ -230,54 +260,65 @@ args = instruction[1:] substituted = set() - if not args: - argstr = "" + # Encode the arguments. - else: - # Encode the arguments. - - a = [] + a = [] + if args: converting_op = op for arg in args: - s, _substituted = encode_access_instruction_arg(arg, subs, converting_op) + s, _substituted = encode_access_instruction_arg(arg, subs, converting_op, context_index) substituted.update(_substituted) a.append(s) converting_op = None - # Modify certain arguments. + # Modify certain arguments. - # Convert attribute name arguments to position symbols. + # Convert attribute name arguments to position symbols. - if op in attribute_ops: - arg = a[1] - a[1] = encode_symbol("pos", arg) + if op in attribute_ops: + arg = a[1] + a[1] = encode_symbol("pos", arg) + + # Convert attribute name arguments to position and code symbols. - # Convert attribute name arguments to position and code symbols. + elif op in checked_ops: + arg = a[1] + a[1] = encode_symbol("pos", arg) + a.insert(2, encode_symbol("code", arg)) + + # Convert type name arguments to position and code symbols. - elif op in checked_ops: - arg = a[1] - a[1] = encode_symbol("pos", arg) - a.insert(2, encode_symbol("code", arg)) + elif op in typename_ops: + arg = encode_type_attribute(args[1]) + a[1] = encode_symbol("pos", arg) + a.insert(2, encode_symbol("code", arg)) - # Convert type name arguments to position and code symbols. + # Obtain addresses of type arguments. - elif op in typename_ops: - arg = encode_type_attribute(args[1]) - a[1] = encode_symbol("pos", arg) - a.insert(2, encode_symbol("code", arg)) + elif op in type_ops: + a[1] = "&%s" % a[1] + + # Obtain addresses of static objects. - # Obtain addresses of type arguments. + elif op in static_ops: + a[-1] = "&%s" % a[-1] + + # Add context storage information to certain operations. - elif op in type_ops: - a[1] = "&%s" % a[1] + if op in context_ops: + a.insert(0, context_index) - # Obtain addresses of static objects. + # Add the local context array to certain operations. - elif op in static_ops: - a[0] = "&%s" % a[0] - a[1] = "&%s" % a[1] + if op in context_op_functions: + a.append("__tmp_contexts") + + # Define any argument string. + if a: argstr = "(%s)" % ", ".join(map(str, a)) + else: + argstr = "" # Substitute the first element of the instruction, which may not be an # operation at all. @@ -301,16 +342,19 @@ return "%s%s" % (op, argstr), substituted -def encode_access_instruction_arg(arg, subs, op): +def encode_access_instruction_arg(arg, subs, op, context_index): """ - Encode 'arg' using 'subs' to define substitutions, returning a tuple - containing the encoded form of 'arg' along with a collection of any - substituted values. + Encode 'arg' using 'subs' to define substitutions, 'op' to indicate the + operation to which the argument belongs, and 'context_index' to indicate any + affected context storage. + + Return a tuple containing the encoded form of 'arg' along with a collection + of any substituted values. """ if isinstance(arg, tuple): - encoded, substituted = encode_access_instruction(arg, subs) + encoded, substituted = encode_access_instruction(arg, subs, context_index) # Convert attribute results to references where required. @@ -322,7 +366,13 @@ # Special values only need replacing, not encoding. elif subs.has_key(arg): - return subs.get(arg), set([arg]) + + # Handle values modified by storage details. + + if arg in context_values: + return "%s(%s)" % (subs.get(arg), context_index), set([arg]) + else: + return subs.get(arg), set([arg]) # Convert static references to the appropriate type. @@ -359,7 +409,7 @@ "Encode a name for the literal constant with the number 'n'." - return "__const%d" % n + return "__const%s" % n def encode_literal_constant_size(value): @@ -419,7 +469,7 @@ "Encode a reference to a literal constant with the number 'n'." - return "__constvalue%d" % n + return "__constvalue%s" % n diff -r 65cadf3264bc -r 7f284b0b42b9 generator.py --- a/generator.py Mon Feb 13 21:40:34 2017 +0100 +++ b/generator.py Fri Feb 24 13:27:07 2017 +0100 @@ -19,7 +19,7 @@ this program. If not, see . """ -from common import CommonOutput +from common import CommonOutput, copy from encoders import encode_function_pointer, \ encode_instantiator_pointer, \ encode_literal_constant, encode_literal_constant_member, \ @@ -31,24 +31,10 @@ encode_symbol, encode_tablename, \ encode_type_attribute, decode_type_attribute, \ is_type_attribute -from os import listdir, mkdir -from os.path import exists, isdir, join, split +from os import listdir, mkdir, remove +from os.path import exists, isdir, join, split, splitext from referencing import Reference -def copy(source, target): - - "Copy a text file from 'source' to 'target'." - - if isdir(target): - target = join(target, split(source)[-1]) - infile = open(source) - outfile = open(target, "w") - try: - outfile.write(infile.read()) - finally: - outfile.close() - infile.close() - class Generator(CommonOutput): "A code generator." @@ -92,13 +78,13 @@ self.optimiser = optimiser self.output = output - def to_output(self, debug=False): + def to_output(self, debug=False, gc_sections=False): "Write the generated code." self.check_output() self.write_structures() - self.write_scripts(debug) + self.write_scripts(debug, gc_sections) self.copy_templates() def copy_templates(self): @@ -124,9 +110,34 @@ if not exists(target): mkdir(target) - for filename in listdir(pathname): + existing = listdir(target) + needed = listdir(pathname) + + # Determine which files are superfluous by comparing their + # basenames (without extensions) to those of the needed + # filenames. This should preserve object files for needed source + # files, only discarding completely superfluous files from the + # target directory. + + needed_basenames = set() + for filename in needed: + needed_basenames.add(splitext(filename)[0]) + + superfluous = [] + for filename in existing: + if splitext(filename)[0] not in needed_basenames: + superfluous.append(filename) + + # Copy needed files. + + for filename in needed: copy(join(pathname, filename), target) + # Remove superfluous files. + + for filename in superfluous: + remove(join(target, filename)) + def write_structures(self): "Write structures used by the program." @@ -217,7 +228,7 @@ # Define special attributes. attrs["__fn__"] = path - attrs["__args__"] = encode_size("pmin", path) + attrs["__args__"] = path self.populate_structure(Reference(kind, path), attrs, kind, structure) @@ -270,7 +281,7 @@ # Set a special callable attribute on the instance. function_instance_attrs["__fn__"] = path - function_instance_attrs["__args__"] = encode_size("pmin", path) + function_instance_attrs["__args__"] = path structure = self.populate_function(path, function_instance_attrs) self.write_structure(f_decls, f_defs, path, table_name, structure) @@ -286,22 +297,48 @@ print >>f_signatures, "__attr %s(__attr args[]);" % encode_function_pointer(path) + # Generate parameter table size data. + + min_parameters = {} + max_parameters = {} + size_parameters = {} + # Consolidate parameter tables for instantiators and functions. parameter_tables = set() for path, function_path in self.callables.items(): + argmin, argmax = self.get_argument_limits(function_path) + + # Obtain the parameter table members. + parameters = self.optimiser.parameters[function_path] if not parameters: parameters = () else: parameters = tuple(parameters) - parameter_tables.add(parameters) + + # Define each table in terms of the members and the minimum + # number of arguments. + + parameter_tables.add((argmin, parameters)) + signature = self.get_parameter_signature(argmin, parameters) + + # Record the minimum number of arguments, the maximum number, + # and the size of the table. + + min_parameters[signature] = argmin + max_parameters[signature] = argmax + size_parameters[signature] = len(parameters) + + self.write_size_constants(f_consts, "pmin", min_parameters, 0) + self.write_size_constants(f_consts, "pmax", max_parameters, 0) + self.write_size_constants(f_consts, "psize", size_parameters, 0) # Generate parameter tables for distinct function signatures. - for parameters in parameter_tables: - self.make_parameter_table(f_decls, f_defs, parameters) + for argmin, parameters in parameter_tables: + self.make_parameter_table(f_decls, f_defs, argmin, parameters) # Generate predefined constants. @@ -340,27 +377,6 @@ for kind, sizes in size_tables: self.write_size_constants(f_consts, kind, sizes, 0) - # Generate parameter table size data. - - min_sizes = {} - max_sizes = {} - - # Determine the minimum number of parameters for each - - for path in self.optimiser.parameters.keys(): - argmin, argmax = self.get_argument_limits(path) - min_sizes[path] = argmin - - # Use the parameter table details to define the maximum number. - # The context is already present in the collection. - - for parameters in parameter_tables: - signature = self.get_parameter_signature(parameters) - max_sizes[signature] = len(parameters) - - self.write_size_constants(f_consts, "pmin", min_sizes, 0) - self.write_size_constants(f_consts, "pmax", max_sizes, 0) - # Generate parameter codes. self.write_code_constants(f_consts, self.optimiser.all_paramnames, self.optimiser.arg_locations, "pcode", "ppos") @@ -402,7 +418,7 @@ f_signatures.close() f_code.close() - def write_scripts(self, debug): + def write_scripts(self, debug, gc_sections): "Write scripts used to build the program." @@ -413,6 +429,9 @@ if debug: print >>f_options, "CFLAGS = -g" + if gc_sections: + print >>f_options, "include gc_sections.mk" + # Identify modules used by the program. native_modules = [join("native", "common.c")] @@ -517,7 +536,7 @@ # Employ a special alias that will be tested specifically in # encode_member. - encoding_ref = Reference("", self.string_type, "$c%d" % n) + encoding_ref = Reference("", self.string_type, "$c%s" % n) # Use None where no encoding was indicated. @@ -538,31 +557,34 @@ # Define a macro for the constant. attr_name = encode_path(const_path) - print >>f_decls, "#define %s ((__attr) {{.context=&%s, .value=&%s}})" % (attr_name, structure_name, structure_name) + print >>f_decls, "#define %s ((__attr) {.value=&%s})" % (attr_name, structure_name) - def make_parameter_table(self, f_decls, f_defs, parameters): + def make_parameter_table(self, f_decls, f_defs, argmin, parameters): """ Write parameter table details to 'f_decls' (to declare a table) and to - 'f_defs' (to define the contents) for the given 'parameters'. + 'f_defs' (to define the contents) for the given 'argmin' and + 'parameters'. """ # Use a signature for the table name instead of a separate name for each # function. - signature = self.get_parameter_signature(parameters) + signature = self.get_parameter_signature(argmin, parameters) table_name = encode_tablename("", signature) - structure_size = encode_size("pmax", signature) + min_parameters = encode_size("pmin", signature) + max_parameters = encode_size("pmax", signature) + structure_size = encode_size("psize", signature) table = [] self.populate_parameter_table(parameters, table) - self.write_parameter_table(f_decls, f_defs, table_name, structure_size, table) + self.write_parameter_table(f_decls, f_defs, table_name, min_parameters, max_parameters, structure_size, table) - def get_parameter_signature(self, parameters): + def get_parameter_signature(self, argmin, parameters): - "Return a signature for the given 'parameters'." + "Return a signature for the given 'argmin' and 'parameters'." - l = [] + l = [str(argmin)] for parameter in parameters: if parameter is None: l.append("") @@ -576,8 +598,9 @@ "Return the signature for the callable with the given 'path'." function_path = self.callables[path] + argmin, argmax = self.get_argument_limits(function_path) parameters = self.optimiser.parameters[function_path] - return self.get_parameter_signature(parameters) + return self.get_parameter_signature(argmin, parameters) def write_size_constants(self, f_consts, size_prefix, sizes, padding): @@ -644,19 +667,22 @@ %s } }; -""" % (table_name, structure_size, ",\n ".join(table)) +""" % (table_name, structure_size, + ",\n ".join(table)) - def write_parameter_table(self, f_decls, f_defs, table_name, structure_size, table): + def write_parameter_table(self, f_decls, f_defs, table_name, min_parameters, + max_parameters, structure_size, table): """ Write the declarations to 'f_decls' and definitions to 'f_defs' for - the object having the given 'table_name' and the given 'structure_size', - with 'table' details used to populate the definition. + the object having the given 'table_name' and the given 'min_parameters', + 'max_parameters' and 'structure_size', with 'table' details used to + populate the definition. """ members = [] for t in table: - members.append("{%s, %s}" % t) + members.append("{.code=%s, .pos=%s}" % t) print >>f_decls, "extern const __ptable %s;\n" % table_name @@ -664,12 +690,15 @@ print >>f_defs, """\ const __ptable %s = { - %s, + .min=%s, + .max=%s, + .size=%s, { %s } }; -""" % (table_name, structure_size, ",\n ".join(members)) +""" % (table_name, min_parameters, max_parameters, structure_size, + ",\n ".join(members)) def write_instance_structure(self, f_decls, path, structure_size): @@ -879,16 +908,10 @@ if kind == "": attr = encode_instantiator_pointer(attr) - unbound_attr = attr - - # Provide bound method and unbound function pointers if - # populating methods in a class. - else: attr = encode_function_pointer(attr) - unbound_attr = "__unbound_method" - structure.append("{{.inv=%s, .fn=%s}}" % (unbound_attr, attr)) + structure.append("{.fn=%s}" % attr) continue # Special argument specification member. @@ -897,7 +920,7 @@ signature = self.get_signature_for_callable(ref.get_origin()) ptable = encode_tablename("", signature) - structure.append("{{.min=%s, .ptable=&%s}}" % (attr, ptable)) + structure.append("{.ptable=&%s}" % ptable) continue # Special internal data member. @@ -917,8 +940,8 @@ # Special internal key member. elif attrname == "__key__": - structure.append("{{.code=%s, .pos=%s}}" % (attr and encode_symbol("code", attr) or "0", - attr and encode_symbol("pos", attr) or "0")) + structure.append("{.code=%s, .pos=%s}" % (attr and encode_symbol("code", attr) or "0", + attr and encode_symbol("pos", attr) or "0")) continue # Special cases. @@ -950,7 +973,7 @@ constant_name = "$c%d" % local_number attr_path = "%s.%s" % (path, constant_name) constant_number = self.optimiser.constant_numbers[attr_path] - constant_value = "__const%d" % constant_number + constant_value = "__const%s" % constant_number structure.append("%s /* %s */" % (constant_value, attrname)) continue @@ -961,13 +984,33 @@ # object paths. value = path.rsplit(".", 1)[0] - structure.append("{{.context=0, .value=&%s}}" % encode_path(value)) + structure.append("{.value=&%s}" % encode_path(value)) + continue + + # Special context member. + # Set the context depending on the kind of attribute. + # For methods: + # For other attributes: __NULL + + elif attrname == "__context__": + path = ref.get_origin() + + # Contexts of methods are derived from their object paths. + + context = "0" + + if ref.get_kind() == "": + parent = path.rsplit(".", 1)[0] + if self.importer.classes.has_key(parent): + context = "&%s" % encode_path(parent) + + structure.append("{.value=%s}" % context) continue # Special class relationship attributes. elif is_type_attribute(attrname): - structure.append("{{.context=0, .value=&%s}}" % encode_path(decode_type_attribute(attrname))) + structure.append("{.value=&%s}" % encode_path(decode_type_attribute(attrname))) continue # All other kinds of members. @@ -992,7 +1035,7 @@ # Use the alias directly if appropriate. if alias.startswith("$c"): - constant_value = encode_literal_constant(int(alias[2:])) + constant_value = encode_literal_constant(alias[2:]) return "%s /* %s */" % (constant_value, name) # Obtain a constant value directly assigned to the attribute. @@ -1006,33 +1049,22 @@ if kind == "" and origin == self.none_type: attr_path = encode_predefined_reference(self.none_value) - return "{{.context=&%s, .value=&%s}} /* %s */" % (attr_path, attr_path, name) + return "{.value=&%s} /* %s */" % (attr_path, name) # Predefined constant members. if (path, name) in self.predefined_constant_members: attr_path = encode_predefined_reference("%s.%s" % (path, name)) - return "{{.context=&%s, .value=&%s}} /* %s */" % (attr_path, attr_path, name) + return "{.value=&%s} /* %s */" % (attr_path, name) # General undetermined members. if kind in ("", ""): attr_path = encode_predefined_reference(self.none_value) - return "{{.context=&%s, .value=&%s}} /* %s */" % (attr_path, attr_path, name) - - # Set the context depending on the kind of attribute. - # For methods: {&, &} - # For other attributes: {&, &} + return "{.value=&%s} /* %s */" % (attr_path, name) else: - if kind == "" and structure_type == "": - parent = origin.rsplit(".", 1)[0] - context = "&%s" % encode_path(parent) - elif kind == "": - context = "&%s" % encode_path(origin) - else: - context = "0" - return "{{.context=%s, .value=&%s}}" % (context, encode_path(origin)) + return "{.value=&%s}" % encode_path(origin) def append_defaults(self, path, structure): @@ -1140,7 +1172,7 @@ } __Catch(__tmp_exc) { - if (__ISINSTANCE(__tmp_exc.arg, ((__attr) {{.context=0, .value=&__builtins___exception_system_SystemExit}}))) + if (__ISINSTANCE(__tmp_exc.arg, ((__attr) {.value=&__builtins___exception_system_SystemExit}))) return __load_via_object( __load_via_object(__tmp_exc.arg.value, %s).value, %s).intvalue; diff -r 65cadf3264bc -r 7f284b0b42b9 inspector.py --- a/inspector.py Mon Feb 13 21:40:34 2017 +0100 +++ b/inspector.py Fri Feb 24 13:27:07 2017 +0100 @@ -412,7 +412,7 @@ # if assigned in the namespace, or using an external name # (presently just globals within classes). - name = self.get_name_for_tracking(name_ref.name, name_ref.final()) + name = self.get_name_for_tracking(name_ref.name, name_ref) tracker = self.trackers[-1] immediate_access = len(self.attrs) == 1 @@ -420,10 +420,7 @@ # Record global-based chains for subsequent resolution. - is_global = self.in_function and not self.function_locals[path].has_key(name) or \ - not self.in_function - - if is_global: + if name_ref.is_global_name(): self.record_global_access_details(name, attrnames) # Make sure the name is being tracked: global names will not @@ -521,14 +518,17 @@ # class. Function instances provide these attributes. if class_name != "__builtins__.core.function": + self.set_name("__fn__") # special instantiator attribute self.set_name("__args__") # special instantiator attribute - # Provide leafname and parent attributes. + # Provide leafname, parent and context attributes. parent, leafname = class_name.rsplit(".", 1) self.set_name("__name__", self.get_constant("string", leafname).reference()) - self.set_name("__parent__") + + if class_name != "__builtins__.core.function": + self.set_name("__parent__") self.process_structure_node(n.code) self.exit_namespace() @@ -873,12 +873,12 @@ ref = self.find_name(n.name) if ref: - return ResolvedNameRef(n.name, ref) + return ResolvedNameRef(n.name, ref, is_global=True) # Explicitly-declared global names. elif self.in_function and n.name in self.scope_globals[path]: - return NameRef(n.name) + return NameRef(n.name, is_global=True) # Examine other names. @@ -899,7 +899,7 @@ # Possible global or built-in name. else: - return NameRef(n.name) + return NameRef(n.name, is_global=True) def process_operator_chain(self, nodes, fn): diff -r 65cadf3264bc -r 7f284b0b42b9 internal_tests/branches.py --- a/internal_tests/branches.py Mon Feb 13 21:40:34 2017 +0100 +++ b/internal_tests/branches.py Fri Feb 24 13:27:07 2017 +0100 @@ -2,10 +2,73 @@ import branching +def simple_usage(assignment): + d = {} + for name, usages in assignment.get_usage().items(): + l = [] + for usage in usages: + if not usage: + l.append(usage) + else: + l2 = [] + for t in usage: + attrname, invocation, assignment = t + l2.append(attrname) + l.append(tuple(l2)) + d[name] = set(l) + return d + names = [] # Equivalent to... # +# y = ... +# while cond0: +# if cond1: +# y.a1 +# elif cond2: +# y = ... +# y.a2 +# else: +# y.a3 + +bt = branching.BranchTracker() +y1 = bt.assign_names(["y"]) +bt.new_branchpoint(True) # begin +bt.new_branch(True) # while ... +bt.new_branchpoint() # begin +bt.new_branch() # if ... +ya1 = bt.use_attribute("y", "a1") +bt.shelve_branch() +bt.new_branch() # elif ... +y2 = bt.assign_names(["y"]) +ya2 = bt.use_attribute("y", "a2") +bt.shelve_branch() +bt.new_branch() # else +ya1 = bt.use_attribute("y", "a3") +bt.shelve_branch() +bt.merge_branches() # end +bt.resume_continuing_branches() +bt.shelve_branch(True) +bt.new_branch() # (null) +bt.shelve_branch() +bt.merge_branches() # end +bt.resume_broken_branches() + +print simple_usage(y1) == \ + {'y' : set([(), ('a1',), ('a3',)])}, \ + simple_usage(y1) +print simple_usage(y2) == \ + {'y' : set([('a2',)])}, \ + simple_usage(y2) +print bt.get_assignment_positions_for_branches("y", ya1) == [0, 1], \ + bt.get_assignment_positions_for_branches("y", ya1) +print bt.get_assignment_positions_for_branches("y", ya2) == [1], \ + bt.get_assignment_positions_for_branches("y", ya2) +names.append(bt.assignments["y"]) + +# Equivalent to... +# # a = ... # a.p # if ...: @@ -28,12 +91,12 @@ bt.merge_branches() # end aq = bt.use_attribute("a", "q") -print a1.get_usage() == \ +print simple_usage(a1) == \ {'a' : set([('p',), ('p', 'q')])}, \ - a1.get_usage() -print a2.get_usage() == \ + simple_usage(a1) +print simple_usage(a2) == \ {'a' : set([('q', 'x')])}, \ - a2.get_usage() + simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ax) == [1], \ bt.get_assignment_positions_for_branches("a", ax) print bt.get_assignment_positions_for_branches("a", aq) == [0, 1], \ @@ -68,9 +131,9 @@ bt.merge_branches() # end bt.use_attribute("a", "q") -print a.get_usage() == \ +print simple_usage(a) == \ {'a' : set([('p', 'q'), ('p', 'q', 'x'), ('p', 'q', 'y', 'z')])}, \ - a.get_usage() + simple_usage(a) print bt.get_assignment_positions_for_branches("a", ax) == [0], \ bt.get_assignment_positions_for_branches("a", ax) print bt.get_assignment_positions_for_branches("a", ay) == [0], \ @@ -101,8 +164,8 @@ bt.resume_broken_branches() bt.use_attribute("a", "q") -print a.get_usage() == \ - {'a' : set([('p', 'q'), ('p', 'q', 'x')])}, a.get_usage() +print simple_usage(a) == \ + {'a' : set([('p', 'q'), ('p', 'q', 'x')])}, simple_usage(a) print bt.get_assignment_positions_for_branches("a", ax) == [0], \ bt.get_assignment_positions_for_branches("a", ax) names.append(bt.assignments["a"]) @@ -139,9 +202,9 @@ bt.resume_broken_branches() bt.use_attribute("a", "q") -print a.get_usage() == \ +print simple_usage(a) == \ {'a' : set([('p', 'q'), ('p', 'q', 'x'), ('p', 'q', 'y')])}, \ - a.get_usage() + simple_usage(a) print bt.get_assignment_positions_for_branches("a", ax) == [0], \ bt.get_assignment_positions_for_branches("a", ax) print bt.get_assignment_positions_for_branches("a", ay) == [0], \ @@ -182,10 +245,10 @@ bt.resume_broken_branches() bt.use_attribute("a", "q") -print a1.get_usage() == \ - {'a' : set([('p', 'q'), ('p', 'q', 'y'), ('p',)])}, a1.get_usage() -print a2.get_usage() == \ - {'a' : set([('q', 'x')])}, a2.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p', 'q'), ('p', 'q', 'y'), ('p',)])}, simple_usage(a1) +print simple_usage(a2) == \ + {'a' : set([('q', 'x')])}, simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ax) == [1], \ bt.get_assignment_positions_for_branches("a", ax) print bt.get_assignment_positions_for_branches("a", ay) == [0, 1], \ @@ -226,10 +289,10 @@ bt.resume_broken_branches() bt.use_attribute("a", "q") -print a1.get_usage() == \ - {'a' : set([('p', 'q'), ('p', 'q', 'y'), ('p',)])}, a1.get_usage() -print a2.get_usage() == \ - {'a' : set([('q', 'x')])}, a2.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p', 'q'), ('p', 'q', 'y'), ('p',)])}, simple_usage(a1) +print simple_usage(a2) == \ + {'a' : set([('q', 'x')])}, simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ax) == [1], \ bt.get_assignment_positions_for_branches("a", ax) print bt.get_assignment_positions_for_branches("a", ay) == [0, 1], \ @@ -260,10 +323,10 @@ bt.resume_broken_branches() aq = bt.use_attribute("a", "q") -print a1.get_usage() == \ - {'a' : set([('p', 'q'), ('p',)])}, a1.get_usage() -print a2.get_usage() == \ - {'a' : set([('q', 'x')])}, a2.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p', 'q'), ('p',)])}, simple_usage(a1) +print simple_usage(a2) == \ + {'a' : set([('q', 'x')])}, simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ax) == [1], \ bt.get_assignment_positions_for_branches("a", ax) print bt.get_assignment_positions_for_branches("a", ap) == [0], \ @@ -301,8 +364,8 @@ bt.resume_broken_branches() bt.use_attribute("a", "r") -print a1.get_usage() == \ - {'a' : set([('p', 'q', 'r'), ('p', 'r')])}, a1.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p', 'q', 'r'), ('p', 'r')])}, simple_usage(a1) names.append(bt.assignments["a"]) # Equivalent to... @@ -332,8 +395,8 @@ bt.shelve_branch() bt.merge_branches() # end -print a1.get_usage() == \ - {'a' : set([('p', 'q', 'r'), ('p', 'q'), ('p',)])}, a1.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p', 'q', 'r'), ('p', 'q'), ('p',)])}, simple_usage(a1) names.append(bt.assignments["a"]) # Equivalent to... @@ -356,8 +419,8 @@ bt.merge_branches() # end bt.use_attribute("a", "q") -print a1.get_usage() == \ - {'a' : set([('p',), ('q',)])}, a1.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p',), ('q',)])}, simple_usage(a1) names.append(bt.assignments["a"]) # Equivalent to... @@ -388,8 +451,8 @@ bt.shelve_branch() bt.merge_branches() # end -print a1.get_usage() == \ - {'a' : set([('p',), ('q', 'r')])}, a1.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p',), ('q', 'r')])}, simple_usage(a1) names.append(bt.assignments["a"]) # Equivalent to... @@ -421,10 +484,10 @@ bt.shelve_branch() bt.merge_branches() # end -print a1.get_usage() == \ - {'a' : set([('p',), ()])}, a1.get_usage() -print a2.get_usage() == \ - {'a' : set([('q',), ()])}, a2.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p',), ()])}, simple_usage(a1) +print simple_usage(a2) == \ + {'a' : set([('q',), ()])}, simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ap) == [0], \ bt.get_assignment_positions_for_branches("a", ap) print bt.get_assignment_positions_for_branches("a", aq) == [1], \ @@ -455,12 +518,12 @@ bt.merge_branches() # end aq = bt.use_attribute("a", "q") -print a1.get_usage() == \ +print simple_usage(a1) == \ {'a' : set([('p',), ('p', 'q')])}, \ - a1.get_usage() -print a2.get_usage() == \ + simple_usage(a1) +print simple_usage(a2) == \ {'a' : set([('q', 'x')])}, \ - a2.get_usage() + simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ap) == [0], \ bt.get_assignment_positions_for_branches("a", ap) print bt.get_assignment_positions_for_branches("a", ax) == [1], \ @@ -489,9 +552,9 @@ bt.merge_branches() # end aq = bt.use_attribute("a", "q") -print a1.get_usage() == \ +print simple_usage(a1) == \ {'a' : set([('q', 'x')])}, \ - a1.get_usage() + simple_usage(a1) print bt.get_assignment_positions_for_branches("a", aq) == [None, 0], \ bt.get_assignment_positions_for_branches("a", aq) names.append(bt.assignments["a"]) @@ -514,8 +577,8 @@ bt.merge_branches() # end aq = bt.use_attribute("a", "q") -print a1.get_usage() == \ - {'a' : set([()])}, a1.get_usage() +print simple_usage(a1) == \ + {'a' : set([()])}, simple_usage(a1) print bt.get_assignment_positions_for_branches("a", aq) == [None], \ bt.get_assignment_positions_for_branches("a", aq) names.append(bt.assignments["a"]) @@ -546,8 +609,8 @@ bt.use_attribute("a", "r") bt.restore_active_branches(branches) -print a1.get_usage() == \ - {'a' : set([('p', 'r'), ('q', 'r')])}, a1.get_usage() +print simple_usage(a1) == \ + {'a' : set([('p', 'r'), ('q', 'r')])}, simple_usage(a1) names.append(bt.assignments["a"]) # Equivalent to... @@ -578,10 +641,10 @@ ar = bt.use_attribute("a", "r") bt.restore_active_branches(branches) -print a1.get_usage() == \ - {'a' : set([(), ('q', 'r')])}, a1.get_usage() -print a2.get_usage() == \ - {'a' : set([('p', 'r')])}, a2.get_usage() +print simple_usage(a1) == \ + {'a' : set([(), ('q', 'r')])}, simple_usage(a1) +print simple_usage(a2) == \ + {'a' : set([('p', 'r')])}, simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ar) == [0, 1], \ bt.get_assignment_positions_for_branches("a", ar) names.append(bt.assignments["a"]) @@ -616,10 +679,10 @@ bt.shelve_branch() bt.merge_branches() # end -print a1.get_usage() == \ - {'a' : set([(), ('q', 'r')])}, a1.get_usage() -print a2.get_usage() == \ - {'a' : set([('p',)])}, a2.get_usage() +print simple_usage(a1) == \ + {'a' : set([(), ('q', 'r')])}, simple_usage(a1) +print simple_usage(a2) == \ + {'a' : set([('p',)])}, simple_usage(a2) print bt.get_assignment_positions_for_branches("a", ar) == [0, 1], \ bt.get_assignment_positions_for_branches("a", ar) names.append(bt.assignments["a"]) diff -r 65cadf3264bc -r 7f284b0b42b9 lib/__builtins__/character.py --- a/lib/__builtins__/character.py Mon Feb 13 21:40:34 2017 +0100 +++ b/lib/__builtins__/character.py Fri Feb 24 13:27:07 2017 +0100 @@ -20,7 +20,7 @@ """ from __builtins__.types import check_int, check_string -from native import str_chr +from native import str_chr, unicode_unichr def chr(i): @@ -87,6 +87,15 @@ return c.__ord__() -def unichr(i): pass +def unichr(i): + + "Return the given character value 'i' encoded as a character." + + check_int(i) + + if 0 <= i <= 2097151: + return utf8string(unicode_unichr(i.__data__)) + else: + raise ValueError, i # vim: tabstop=4 expandtab shiftwidth=4 diff -r 65cadf3264bc -r 7f284b0b42b9 lib/__builtins__/core.py --- a/lib/__builtins__/core.py Mon Feb 13 21:40:34 2017 +0100 +++ b/lib/__builtins__/core.py Fri Feb 24 13:27:07 2017 +0100 @@ -86,6 +86,7 @@ self.__args__ = None self.__name__ = None self.__parent__ = None + self.__context__ = None def __bool__(self): @@ -122,6 +123,25 @@ __repr__ = __str__ +class wrapper: + + "A special method wrapper." + + def __init__(self, context, value): + + "Initialise a wrapper with the given 'context' and wrapped 'value'." + + self.__context__ = context + self.__value__ = value + + def __str__(self): + + "Return a string representation, referring to the wrapped object." + + return self.__value__.__str__() + + __repr__ = __str__ + class Exception: "The root of all exception types." diff -r 65cadf3264bc -r 7f284b0b42b9 lib/native/__init__.py --- a/lib/native/__init__.py Mon Feb 13 21:40:34 2017 +0100 +++ b/lib/native/__init__.py Fri Feb 24 13:27:07 2017 +0100 @@ -48,6 +48,6 @@ from native.system import exit, get_argv, get_path -from native.unicode import unicode_len, unicode_ord, unicode_substr +from native.unicode import unicode_len, unicode_ord, unicode_substr, unicode_unichr # vim: tabstop=4 expandtab shiftwidth=4 diff -r 65cadf3264bc -r 7f284b0b42b9 lib/native/unicode.py --- a/lib/native/unicode.py Mon Feb 13 21:40:34 2017 +0100 +++ b/lib/native/unicode.py Fri Feb 24 13:27:07 2017 +0100 @@ -29,5 +29,6 @@ def unicode_len(data, size): pass def unicode_ord(data, size): pass def unicode_substr(data, size, start, end, step): pass +def unicode_unichr(value): pass # vim: tabstop=4 expandtab shiftwidth=4 diff -r 65cadf3264bc -r 7f284b0b42b9 lplc --- a/lplc Mon Feb 13 21:40:34 2017 +0100 +++ b/lplc Fri Feb 24 13:27:07 2017 +0100 @@ -22,8 +22,8 @@ VERSION = "0.1" from errors import * -from os import environ, rename -from os.path import abspath, exists, isfile, join, split +from os import environ, listdir, remove, rename +from os.path import abspath, exists, extsep, isdir, isfile, join, split from pyparser import error from subprocess import Popen, PIPE from time import time @@ -82,6 +82,17 @@ else: return l, needed +def remove_all(dirname): + + "Remove 'dirname' and its contents." + + for filename in listdir(dirname): + pathname = join(dirname, filename) + if isdir(pathname): + remove_all(pathname) + else: + remove(pathname) + # Main program. if __name__ == "__main__": @@ -98,21 +109,34 @@ Compile the program whose principal file is given in place of . The following options may be specified: --c Only partially compile the program; do not attempt to build or link it --E Ignore environment variables affecting the module search path --g Generate debugging information for the built executable --P Show the module search path --q Silence messages produced when building an executable --r Reset (discard) cached program information; inspect the whole program again --t Silence timing messages --tb Provide a traceback for any internal errors (development only) --v Report compiler activities in a verbose fashion (development only) +-c Only partially compile the program; do not build or link it +--compile Equivalent to -c +-E Ignore environment variables affecting the module search path +--no-env Equivalent to -E +-g Generate debugging information for the built executable +--debug Equivalent to -g +-G Remove superfluous sections of the built executable +--gc-sections Equivalent to -G +-P Show the module search path +--show-path Equivalent to -P +-q Silence messages produced when building an executable +--quiet Equivalent to -q +-r Reset (discard) cached information; inspect the whole program again +--reset Equivalent to -r +-R Reset (discard) all program details including translated code +--reset-all Equivalent to -R +-t Silence timing messages +--no-timing Equivalent to -t +-tb Provide a traceback for any internal errors (development only) +--traceback Equivalent to -tb +-v Report compiler activities in a verbose fashion (development only) +--verbose Equivalent to -v Some options may be followed by values, either immediately after the option (without any space between) or in the arguments that follow them: --o Indicate the output executable name --W Show warnings on the topics indicated +-o Indicate the output executable name +-W Show warnings on the topics indicated Currently, the following warnings are supported: @@ -148,10 +172,12 @@ # Determine the options and arguments. debug = False + gc_sections = False ignore_env = False make = True make_verbose = True reset = False + reset_all = False timings = True traceback = False verbose = False @@ -166,15 +192,17 @@ needed = None for arg in args: - if arg == "-c": make = False - elif arg == "-E": ignore_env = True - elif arg == "-g": debug = True - elif arg == "-q": make_verbose = False - elif arg == "-r": reset = True - elif arg == "-t": timings = False - elif arg == "-tb": traceback = True + if arg in ("-c", "--compile"): make = False + elif arg in ("-E", "--no-env"): ignore_env = True + elif arg in ("-g", "--debug"): debug = True + elif arg in ("-G", "--gc-sections"): gc_sections = True + elif arg in ("-q", "--quiet"): make_verbose = False + elif arg in ("-r", "--reset"): reset = True + elif arg in ("-R", "--reset-all"): reset_all = True + elif arg in ("-t", "--no-timing"): timings = False + elif arg in ("-tb", "--traceback"): traceback = True elif arg.startswith("-o"): l, needed = start_arg_list(outputs, arg, "-o", 1) - elif arg == "-v": verbose = True + elif arg == ("-v", "--verbose"): verbose = True elif arg.startswith("-W"): l, needed = start_arg_list(warnings, arg, "-W", 1) else: l.append(arg) @@ -193,7 +221,7 @@ # Show the module search path if requested. - if "-P" in args: + if "-P" in args or "--show-path" in args: for libdir in libdirs: print libdir sys.exit(0) @@ -221,12 +249,17 @@ # Define the output data directories. - datadir = "_lplc" + datadir = "%s%s%s" % (output, extsep, "lplc") # _main.lplc by default cache_dir = join(datadir, "_cache") deduced_dir = join(datadir, "_deduced") output_dir = join(datadir, "_output") generated_dir = join(datadir, "_generated") + # Perform any full reset of the working data. + + if reset_all: + remove_all(datadir) + # Load the program. try: @@ -255,7 +288,7 @@ if timings: now = stopwatch("Optimisation", now) g = generator.Generator(i, o, generated_dir) - g.to_output(debug) + g.to_output(debug, gc_sections) if timings: now = stopwatch("Generation", now) @@ -270,9 +303,7 @@ make_clean_cmd = ["make", "-C", generated_dir, "clean"] make_cmd = make_clean_cmd[:-1] - retval = call(make_clean_cmd, make_verbose) - if not retval: - retval = call(make_cmd, make_verbose) + retval = call(make_cmd, make_verbose) if not retval: if timings: stopwatch("Compilation", now) diff -r 65cadf3264bc -r 7f284b0b42b9 modules.py --- a/modules.py Mon Feb 13 21:40:34 2017 +0100 +++ b/modules.py Fri Feb 24 13:27:07 2017 +0100 @@ -495,11 +495,11 @@ f.readline() # "aliased names:" line = f.readline().rstrip() while line: - name, version, original_name, attrnames, number = self._get_fields(line, 5) + name, version, path, original_name, attrnames, number = self._get_fields(line, 6) init_item(self.aliased_names, name, dict) if number == "{}": number = None else: number = int(number) - self.aliased_names[name][int(version)] = (original_name, attrnames != "{}" and attrnames or None, number) + self.aliased_names[name][int(version)] = (path, original_name, attrnames != "{}" and attrnames or None, number) line = f.readline().rstrip() def _get_function_parameters(self, f): @@ -785,8 +785,8 @@ versions = aliases.items() versions.sort() for version, alias in versions: - original_name, attrnames, number = alias - print >>f, name, version, original_name, attrnames or "{}", number is None and "{}" or number + path, original_name, attrnames, number = alias + print >>f, name, version, path, original_name, attrnames or "{}", number is None and "{}" or number print >>f print >>f, "function parameters:" diff -r 65cadf3264bc -r 7f284b0b42b9 optimiser.py --- a/optimiser.py Mon Feb 13 21:40:34 2017 +0100 +++ b/optimiser.py Fri Feb 24 13:27:07 2017 +0100 @@ -21,7 +21,7 @@ from common import add_counter_item, get_attrname_from_location, init_item, \ sorted_output -from encoders import encode_access_location, encode_instruction, get_kinds +from encoders import digest, encode_access_location, encode_instruction, get_kinds from os.path import exists, join from os import makedirs from referencing import Reference @@ -351,9 +351,13 @@ first_method, final_method, \ origin, accessor_kinds = access_plan + # Emit instructions by appending them to a list. + instructions = [] emit = instructions.append + # Identify any static original accessor. + if base: original_accessor = base else: @@ -371,7 +375,7 @@ # Perform the first access explicitly if at least one operation # requires it. - access_first_attribute = final_method in ("access", "assign") or traversed or attrnames + access_first_attribute = final_method in ("access", "access-invoke", "assign") or traversed or attrnames # Determine whether the first access involves assignment. @@ -388,8 +392,12 @@ # Prevent re-evaluation of any dynamic expression by storing it. if original_accessor == "": - emit((set_accessor, original_accessor)) - accessor = context_var = (stored_accessor,) + if final_method in ("access-invoke", "static-invoke"): + emit(("", original_accessor)) + accessor = context_var = ("",) + else: + emit((set_accessor, original_accessor)) + accessor = context_var = (stored_accessor,) else: accessor = context_var = (original_accessor,) @@ -462,12 +470,24 @@ # Set the context, if appropriate. if remaining == 1 and final_method != "assign" and context == "final-accessor": - emit(("__set_context", accessor)) - accessor = context_var = "" + + # Invoked attributes employ a separate context accessed + # during invocation. + + if final_method in ("access-invoke", "static-invoke"): + emit(("", accessor)) + accessor = context_var = "" + + # A private context within the access is otherwise + # retained. + + else: + emit(("", accessor)) + accessor = context_var = "" # Perform the access only if not achieved directly. - if remaining > 1 or final_method in ("access", "assign"): + if remaining > 1 or final_method in ("access", "access-invoke", "assign"): if traversal_mode == "class": if assigning: @@ -489,12 +509,24 @@ # Set the context, if appropriate. if remaining == 1 and final_method != "assign" and context == "final-accessor": - emit(("__set_context", accessor)) - accessor = context_var = "" + + # Invoked attributes employ a separate context accessed + # during invocation. + + if final_method in ("access-invoke", "static-invoke"): + emit(("", accessor)) + accessor = context_var = "" + + # A private context within the access is otherwise + # retained. + + else: + emit(("", accessor)) + accessor = context_var = "" # Perform the access only if not achieved directly. - if remaining > 1 or final_method in ("access", "assign"): + if remaining > 1 or final_method in ("access", "access-invoke", "assign"): if assigning: emit(("__check_and_store_via_any", accessor, attrname, "")) @@ -505,23 +537,72 @@ # Define or emit the means of accessing the actual target. + # Assignments to known attributes. + if final_method == "static-assign": parent, attrname = origin.rsplit(".", 1) emit(("__store_via_object", parent, attrname, "")) + # Invoked attributes employ a separate context. + elif final_method in ("static", "static-invoke"): - parent, attrname = origin.rsplit(".", 1) - accessor = ("__load_static", parent, origin) + accessor = ("__load_static_ignore", origin) # Wrap accesses in context operations. if context_test == "test": - emit(("__test_context", context_var, accessor)) + + # Test and combine the context with static attribute details. + + if final_method == "static": + emit(("__load_static_test", context_var, origin)) + + # Test the context, storing it separately if required for the + # immediately invoked static attribute. + + elif final_method == "static-invoke": + emit(("", context_var, origin)) + + # Test the context, storing it separately if required for an + # immediately invoked attribute. + + elif final_method == "access-invoke": + emit(("", context_var, accessor)) + + # Test the context and update the attribute details if + # appropriate. + + else: + emit(("__test_context", context_var, accessor)) elif context_test == "replace": - emit(("__update_context", context_var, accessor)) + + # Produce an object with updated context. + + if final_method == "static": + emit(("__load_static_replace", context_var, origin)) + + # Omit the context update operation where the target is static + # and the context is recorded separately. + + elif final_method == "static-invoke": + pass - elif final_method not in ("assign", "static-assign"): + # If a separate context is used for an immediate invocation, + # produce the attribute details unchanged. + + elif final_method == "access-invoke": + emit(accessor) + + # Update the context in the attribute details. + + else: + emit(("__update_context", context_var, accessor)) + + # Omit the accessor for assignments and for invocations of static + # targets. + + elif final_method not in ("assign", "static-assign", "static-invoke"): emit(accessor) self.access_instructions[access_location] = instructions @@ -642,7 +723,7 @@ # Each constant is actually (value, value_type, encoding). for constant, n in constants.items(): - add_counter_item(self.constants, constant) + self.constants[constant] = digest(constant) self.constant_numbers = {} diff -r 65cadf3264bc -r 7f284b0b42b9 resolving.py --- a/resolving.py Mon Feb 13 21:40:34 2017 +0100 +++ b/resolving.py Fri Feb 24 13:27:07 2017 +0100 @@ -3,7 +3,7 @@ """ Name resolution. -Copyright (C) 2016 Paul Boddie +Copyright (C) 2016, 2017 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -289,7 +289,13 @@ if not ref: if not invocation: - aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number + + # Record the path used for tracking purposes + # alongside original name, attribute and access + # number details. + + aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number + continue # Attempt to resolve a plain name reference. @@ -303,7 +309,13 @@ if not origin: if not invocation: - aliased_names[i] = name_ref.name, None, name_ref.number + + # Record the path used for tracking purposes + # alongside original name, attribute and access + # number details. + + aliased_names[i] = path, name_ref.name, None, name_ref.number + continue ref = self.get_resolved_object(origin) diff -r 65cadf3264bc -r 7f284b0b42b9 results.py --- a/results.py Mon Feb 13 21:40:34 2017 +0100 +++ b/results.py Fri Feb 24 13:27:07 2017 +0100 @@ -30,6 +30,9 @@ def is_name(self): return False + def is_global_name(self): + return False + def reference(self): return None @@ -90,25 +93,29 @@ "A reference to a name." - def __init__(self, name, expr=None): + def __init__(self, name, expr=None, is_global=False): self.name = name self.expr = expr + self.is_global = is_global def is_name(self): return True + def is_global_name(self): + return self.is_global + def final(self): return None def __repr__(self): - return "NameRef(%r, %r)" % (self.name, self.expr) + return "NameRef(%r, %r, %r)" % (self.name, self.expr, self.is_global) class LocalNameRef(NameRef): "A reference to a local name." def __init__(self, name, number): - NameRef.__init__(self, name) + NameRef.__init__(self, name, is_global=False) self.number = number def __repr__(self): @@ -149,12 +156,12 @@ "A resolved name-based reference." - def __init__(self, name, ref, expr=None): - NameRef.__init__(self, name, expr) + def __init__(self, name, ref, expr=None, is_global=False): + NameRef.__init__(self, name, expr, is_global) ResolvedRef.__init__(self, ref) def __repr__(self): - return "ResolvedNameRef(%r, %r, %r)" % (self.name, self.ref, self.expr) + return "ResolvedNameRef(%r, %r, %r, %r)" % (self.name, self.ref, self.expr, self.is_global) class ConstantValueRef(ResolvedNameRef): diff -r 65cadf3264bc -r 7f284b0b42b9 templates/gc_sections.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/gc_sections.mk Fri Feb 24 13:27:07 2017 +0100 @@ -0,0 +1,2 @@ +CFLAGS += -fdata-sections -ffunction-sections +LDFLAGS += -Wl,--gc-sections diff -r 65cadf3264bc -r 7f284b0b42b9 templates/native/iconv.c --- a/templates/native/iconv.c Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/native/iconv.c Fri Feb 24 13:27:07 2017 +0100 @@ -156,7 +156,6 @@ /* Return the descriptor as an opaque value. */ - attr.context = 0; attr.datavalue = (void *) result; return attr; } diff -r 65cadf3264bc -r 7f284b0b42b9 templates/native/io.c --- a/templates/native/io.c Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/native/io.c Fri Feb 24 13:27:07 2017 +0100 @@ -80,7 +80,6 @@ else { - attr.context = 0; attr.datavalue = (void *) f; return attr; } @@ -113,7 +112,6 @@ else { - attr.context = 0; attr.datavalue = (void *) f; return attr; } diff -r 65cadf3264bc -r 7f284b0b42b9 templates/native/unicode.c --- a/templates/native/unicode.c Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/native/unicode.c Fri Feb 24 13:27:07 2017 +0100 @@ -1,6 +1,6 @@ /* Native functions for Unicode operations. -Copyright (C) 2016 Paul Boddie +Copyright (C) 2016, 2017 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -202,6 +202,48 @@ return __new_str(sub, resultsize); } +__attr __fn_native_unicode_unicode_unichr(__attr __args[]) +{ + __attr * const value = &__args[1]; + /* value interpreted as int */ + int i = value->intvalue; + unsigned int resultsize; + char *s; + + if (i < 128) resultsize = 1; + else if (i < 2048) resultsize = 2; + else if (i < 65536) resultsize = 3; + else resultsize = 4; + + /* Reserve space for a new string. */ + + s = (char *) __ALLOCATE(resultsize + 1, sizeof(char)); + + /* Populate the string. */ + + if (i < 128) s[0] = (char) i; + else if (i < 2048) + { + s[0] = 0b11000000 | (i >> 6); + s[1] = 0b10000000 | (i & 0b00111111); + } + else if (i < 65536) + { + s[0] = 0b11100000 | (i >> 12); + s[1] = 0b10000000 | ((i >> 6) & 0b00111111); + s[2] = 0b10000000 | (i & 0b00111111); + } + else + { + s[0] = 0b11110000 | (i >> 18); + s[1] = 0b10000000 | ((i >> 12) & 0b00111111); + s[2] = 0b10000000 | ((i >> 6) & 0b00111111); + s[3] = 0b10000000 | (i & 0b00111111); + } + + return __new_str(s, resultsize); +} + /* Module initialisation. */ void __main_native_unicode() diff -r 65cadf3264bc -r 7f284b0b42b9 templates/native/unicode.h --- a/templates/native/unicode.h Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/native/unicode.h Fri Feb 24 13:27:07 2017 +0100 @@ -24,6 +24,7 @@ __attr __fn_native_unicode_unicode_len(__attr __args[]); __attr __fn_native_unicode_unicode_ord(__attr __args[]); __attr __fn_native_unicode_unicode_substr(__attr __args[]); +__attr __fn_native_unicode_unicode_unichr(__attr __args[]); /* Module initialisation. */ diff -r 65cadf3264bc -r 7f284b0b42b9 templates/ops.c --- a/templates/ops.c Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/ops.c Fri Feb 24 13:27:07 2017 +0100 @@ -21,14 +21,22 @@ #include "progops.h" /* for raising errors */ #include "progconsts.h" #include "progtypes.h" -#include /* Direct access and manipulation of static objects. */ -__attr __load_static(__ref parent, __ref obj) +__attr __load_static_ignore(__ref obj) +{ + return (__attr) {.value=obj}; +} + +__attr __load_static_replace(__ref context, __ref obj) { - __attr out = {.context=parent, .value=obj}; - return out; + return __update_context(context, (__attr) {.value=obj}); +} + +__attr __load_static_test(__ref context, __ref obj) +{ + return __test_context(context, (__attr) {.value=obj}); } /* Direct retrieval operations, returning and setting attributes. */ @@ -190,77 +198,122 @@ /* Context-related operations. */ -__attr __test_context(__ref context, __attr attr) +int __test_context_update(__ref context, __attr attr) { + /* Return whether the context should be updated for the attribute. */ + + __ref attrcontext = __CONTEXT_AS_VALUE(attr).value; + /* Preserve any existing null or instance context. */ - if ((attr.context == 0) || __is_instance(attr.context)) - return attr; + if ((attrcontext == 0) || __is_instance(attrcontext)) + return 0; /* Test any instance context against the context employed by the attribute. */ if (__is_instance(context)) { - if (__test_common_instance(context, __TYPEPOS(attr.context), __TYPECODE(attr.context))) - return __update_context(context, attr); + /* Obtain the special class attribute position and code identifying the + attribute context's class, inspecting the context instance for + compatibility. */ + + if (__test_common_instance(context, __TYPEPOS(attrcontext), __TYPECODE(attrcontext))) + return 1; else __raise_type_error(); } /* Test for access to a type class attribute using a type instance. */ - if (__test_specific_type(attr.context, &__TYPE_CLASS_TYPE) && __is_type_instance(context)) - return __update_context(context, attr); + if (__test_specific_type(attrcontext, &__TYPE_CLASS_TYPE) && __is_type_instance(context)) + return 1; /* Otherwise, preserve the attribute as retrieved. */ - return attr; + return 0; +} + +__attr __test_context(__ref context, __attr attr) +{ + /* Update the context or return the unchanged attribute. */ + + if (__test_context_update(context, attr)) + return __update_context(context, attr); + else + return attr; } __attr __update_context(__ref context, __attr attr) { - __attr out = {.context=context, .value=attr.value}; - return out; + return __new_wrapper(context, attr); +} + +__attr __test_context_revert(int target, __ref context, __attr attr, __ref contexts[]) +{ + /* Revert the local context to that employed by the attribute if the + supplied context is not appropriate. */ + + if (!__test_context_update(context, attr)) + contexts[target] = __CONTEXT_AS_VALUE(attr).value; + return attr; +} + +__attr __test_context_static(int target, __ref context, __ref value, __ref contexts[]) +{ + /* Set the local context to the specified context if appropriate. */ + + if (__test_context_update(context, (__attr) {.value=value})) + contexts[target] = context; + return (__attr) {.value=value}; } /* Context testing for invocations. */ -int __type_method_invocation(__attr attr) +int __type_method_invocation(__ref context, __attr target) { - __attr parent; + __ref targetcontext = __CONTEXT_AS_VALUE(target).value; /* Require instances, not classes, where methods are function instances. */ - if (!__is_instance(attr.value)) + if (!__is_instance(target.value)) return 0; - /* Access the parent of the callable and test if it is the type object. */ + /* Access the context of the callable and test if it is the type object. */ - parent = __check_and_load_via_object_null(attr.value, __ATTRPOS(__parent__), __ATTRCODE(__parent__)); - return ((parent.value != 0) && __test_specific_type(parent.value, &__TYPE_CLASS_TYPE) && __is_type_instance(attr.context)); + return ((targetcontext != 0) && __test_specific_type(targetcontext, &__TYPE_CLASS_TYPE) && __is_type_instance(context)); } -__attr (*__get_function(__attr attr))(__attr[]) +__attr __unwrap_callable(__attr callable) { + __attr value = __check_and_load_via_object_null(callable.value, __ATTRPOS(__value__), __ATTRCODE(__value__)); + return value.value ? value : callable; +} + +__attr (*__get_function(__ref context, __attr target))(__attr[]) +{ + target = __unwrap_callable(target); + /* Require null or instance contexts for functions and methods respectively, or type instance contexts for type methods. */ - if ((attr.context == 0) || __is_instance(attr.context) || __type_method_invocation(attr)) - return __load_via_object(attr.value, __ATTRPOS(__fn__)).fn; + if ((context == 0) || __is_instance(context) || __type_method_invocation(context, target)) + return __load_via_object(target.value, __ATTRPOS(__fn__)).fn; else - return __load_via_object(attr.value, __ATTRPOS(__fn__)).inv; + return __unbound_method; } -__attr (*__check_and_get_function(__attr attr))(__attr[]) +__attr (*__check_and_get_function(__ref context, __attr target))(__attr[]) { + target = __unwrap_callable(target); + /* Require null or instance contexts for functions and methods respectively, or type instance contexts for type methods. */ - if ((attr.context == 0) || __is_instance(attr.context) || __type_method_invocation(attr)) - return __check_and_load_via_object(attr.value, __ATTRPOS(__fn__), __ATTRCODE(__fn__)).fn; + if ((context == 0) || __is_instance(context) || __type_method_invocation(context, target)) + return __check_and_load_via_object(target.value, __ATTRPOS(__fn__), __ATTRCODE(__fn__)).fn; else - return __check_and_load_via_object(attr.value, __ATTRPOS(__fn__), __ATTRCODE(__fn__)).inv; + return __unbound_method; } /* Basic structure tests. */ @@ -295,10 +348,7 @@ __attr __CONTEXT_AS_VALUE(__attr attr) { - __attr out; - out.context = attr.context; - out.value = attr.context; - return out; + return __check_and_load_via_object_null(attr.value, __ATTRPOS(__context__), __ATTRCODE(__context__)); } /* Type testing. */ @@ -310,7 +360,6 @@ int __ISNULL(__attr value) { - /* (value.context == __NULL.context) is superfluous */ return (value.value == 0); /* __NULL.value */ } diff -r 65cadf3264bc -r 7f284b0b42b9 templates/ops.h --- a/templates/ops.h Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/ops.h Fri Feb 24 13:27:07 2017 +0100 @@ -24,7 +24,9 @@ /* Direct access and manipulation of static objects. */ -__attr __load_static(__ref parent, __ref obj); +__attr __load_static_ignore(__ref obj); +__attr __load_static_replace(__ref context, __ref obj); +__attr __load_static_test(__ref context, __ref obj); /* Direct retrieval operations, returning attributes. */ @@ -68,17 +70,23 @@ /* Context-related operations. */ +int __test_context_update(__ref context, __attr attr); __attr __test_context(__ref context, __attr attr); __attr __update_context(__ref context, __attr attr); +__attr __test_context_revert(int target, __ref context, __attr attr, __ref contexts[]); +__attr __test_context_static(int target, __ref context, __ref value, __ref contexts[]); -#define __set_context(__ATTR) (__tmp_context = (__ATTR).value) +#define __get_context(__TARGET) (__tmp_contexts[__TARGET]) +#define __set_context(__TARGET, __ATTR) (__tmp_contexts[__TARGET] = (__ATTR).value) +#define __set_private_context(__ATTR) (__tmp_private_context = (__ATTR).value) #define __set_accessor(__ATTR) (__tmp_value = (__ATTR).value) #define __set_target_accessor(__ATTR) (__tmp_target_value = (__ATTR).value) /* Context testing for invocations. */ -__attr (*__get_function(__attr attr))(__attr[]); -__attr (*__check_and_get_function(__attr attr))(__attr[]); +__attr __unwrap_callable(__attr callable); +__attr (*__get_function(__ref context, __attr target))(__attr[]); +__attr (*__check_and_get_function(__ref context, __attr target))(__attr[]); /* Basic structure tests. */ diff -r 65cadf3264bc -r 7f284b0b42b9 templates/progops.c --- a/templates/progops.c Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/progops.c Fri Feb 24 13:27:07 2017 +0100 @@ -30,11 +30,14 @@ __attr __new(const __table * table, __ref cls, size_t size) { __ref obj = (__ref) __ALLOCATE(1, size); - __attr self = {.context=obj, .value=obj}; - __attr tmp = {.context=0, .value=cls}; obj->table = table; - __store_via_object(obj, __ATTRPOS(__class__), tmp); - return self; + __store_via_object(obj, __ATTRPOS(__class__), (__attr) {.value=cls}); + return (__attr) {.value=obj}; +} + +__attr __new_wrapper(__ref context, __attr attr) +{ + return __new___builtins___core_wrapper((__attr[]) {__NULL, {.value=context}, attr}); } /* Generic internal data allocation. */ @@ -155,7 +158,7 @@ { /* Reserve space for the instance. */ - __attr args[1]; + __attr args[1] = {__NULL}; /* Return instances as provided. */ @@ -181,14 +184,15 @@ unsigned int nkwargs, __param kwcodes[], __attr kwargs[], unsigned int nargs, __attr args[]) { - /* Obtain the __args__ special member, referencing the parameter table. */ + /* Unwrap any wrapped function. */ - __attr minparams = __check_and_load_via_object(callable.value, __ATTRPOS(__args__), __ATTRCODE(__args__)); + __attr target = __unwrap_callable(callable); + /* Obtain the __args__ special member, referencing the parameter table. */ /* Refer to the table and minimum/maximum. */ - const __ptable *ptable = minparams.ptable; - const unsigned int min = minparams.min, max = ptable->size; + const __ptable *ptable = __check_and_load_via_object(target.value, __ATTRPOS(__args__), __ATTRCODE(__args__)).ptable; + const unsigned int min = ptable->min, max = ptable->max; /* Reserve enough space for the arguments. */ @@ -240,12 +244,12 @@ for (pos = nargs; pos < max; pos++) { if (allargs[pos].value == 0) - allargs[pos] = __GETDEFAULT(callable.value, pos - min); + allargs[pos] = __GETDEFAULT(target.value, pos - min); } /* Call with the prepared arguments. */ - return (always_callable ? __get_function(callable) : __check_and_get_function(callable))(allargs); + return (always_callable ? __get_function(allargs[0].value, target) : __check_and_get_function(allargs[0].value, target))(allargs); } /* Error routines. */ diff -r 65cadf3264bc -r 7f284b0b42b9 templates/progops.h --- a/templates/progops.h Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/progops.h Fri Feb 24 13:27:07 2017 +0100 @@ -25,6 +25,7 @@ /* Generic instantiation operations, defining common members. */ __attr __new(const __table *table, __ref cls, size_t size); +__attr __new_wrapper(__ref context, __attr attr); /* Generic internal data allocation. */ diff -r 65cadf3264bc -r 7f284b0b42b9 templates/types.h --- a/templates/types.h Mon Feb 13 21:40:34 2017 +0100 +++ b/templates/types.h Fri Feb 24 13:27:07 2017 +0100 @@ -22,10 +22,12 @@ /* Define code and position types, populated by enum values defined for each program specifically. */ -typedef unsigned short __code; -typedef unsigned short __pos; -typedef unsigned short __pcode; -typedef unsigned short __ppos; +#include + +typedef uint16_t __code; +typedef uint16_t __pos; +typedef uint16_t __pcode; +typedef uint16_t __ppos; /* Attribute tables are lists of codes confirming the presence of attributes. */ @@ -47,48 +49,34 @@ typedef struct __ptable { - const __ppos size; + const __ppos min, max, size; const __param params[]; } __ptable; -/* Attributes are context and value pairs. +/* Attributes are values referring to objects or encoding other information. Objects are collections of attributes. Object references are references to tables and collections of attributes. Attribute references are references to single attributes. */ typedef struct __obj __obj; typedef struct __fragment __fragment; +typedef union __attr __attr; +typedef __obj * __ref; -typedef struct __attr +typedef union __attr { - /* One of... */ - union - { - struct { - __obj * context; /* attribute context */ - __obj * value; /* attribute value */ - }; - struct { - __ppos min; /* minimum number of parameters */ - const __ptable * ptable; /* parameter table */ - }; - struct { - __pcode code; /* parameter table code for key */ - __ppos pos; /* parameter table position for key */ - }; - struct { - struct __attr (*inv)(); /* unbound callable details */ - struct __attr (*fn)(); /* callable details */ - }; - union - { - int intvalue; /* integer value */ - double floatvalue; /* floating point value */ - char * strvalue; /* string value */ - __fragment * seqvalue; /* sequence data */ - void * datavalue; /* object-specific data */ - }; + __ref value; /* attribute value */ + const __ptable * ptable; /* parameter table */ + struct { + __pcode code; /* parameter table code for key */ + __ppos pos; /* parameter table position for key */ }; + __attr (*fn)(); /* callable details */ + int intvalue; /* integer value */ + float floatvalue; /* floating point value */ + char * strvalue; /* string value */ + __fragment * seqvalue; /* sequence data */ + void * datavalue; /* object-specific data */ } __attr; typedef struct __obj @@ -98,7 +86,7 @@ __attr attrs[]; /* attributes */ } __obj; -typedef __obj * __ref; +#define __INSTANCE_SIZE(NUMBER) ((NUMBER) * sizeof(__attr) + sizeof(__table *) + sizeof(__ppos)) /* Fragments are simple collections of attributes employed by sequence types. They provide the basis of lists and tuples. */ @@ -109,7 +97,7 @@ __attr attrs[]; } __fragment; -#define __FRAGMENT_SIZE(NUMBER) (NUMBER * sizeof(__attr) + 2 * sizeof(unsigned int)) +#define __FRAGMENT_SIZE(NUMBER) ((NUMBER) * sizeof(__attr) + 2 * sizeof(unsigned int)) /* Special instance position value. The pos member of __obj refers to the special type attribute for classes, indicating which position holds the @@ -119,7 +107,7 @@ /* Special null values. */ -#define __NULL ((__attr) {{.context=0, .value=0}}) +#define __NULL ((__attr) {.value=0}) /* Function pointer type. */ diff -r 65cadf3264bc -r 7f284b0b42b9 test_all.sh --- a/test_all.sh Mon Feb 13 21:40:34 2017 +0100 +++ b/test_all.sh Fri Feb 24 13:27:07 2017 +0100 @@ -3,7 +3,7 @@ # This tool runs the toolchain for each of the tests, optionally building and # running the test programs. # -# Copyright (C) 2016 Paul Boddie +# Copyright (C) 2016, 2017 Paul Boddie # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software @@ -22,7 +22,7 @@ OPTION=$1 LPLC="./lplc" -DATADIR="_lplc" +DATADIR="_main.lplc" TESTINPUT="_results/testinput.txt" # Expect failure from the "bad" tests. diff -r 65cadf3264bc -r 7f284b0b42b9 tests/methods_rebound.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/methods_rebound.py Fri Feb 24 13:27:07 2017 +0100 @@ -0,0 +1,21 @@ +class C: + def f(self): + print self + return self.value() + + def value(self): + return 123 + +c = C() + +class D: + f = c.f + +d = D() + +print c.f.__name__ # f +print c.f() # <__main__.C instance> + # 123 +print d.f.__name__ # wrapper +print d.f() # <__main__.C instance> + # 123 diff -r 65cadf3264bc -r 7f284b0b42b9 tests/nested_calls.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/nested_calls.py Fri Feb 24 13:27:07 2017 +0100 @@ -0,0 +1,15 @@ +class C: + def __init__(self, x): + self.x = x + + def value(self): + return self.x + + def length(self): + return self.double(self.value()) + + def double(self, x): + return x * 2 + +c = C(3) +print c.length() # 6 diff -r 65cadf3264bc -r 7f284b0b42b9 tests/unicode.py --- a/tests/unicode.py Mon Feb 13 21:40:34 2017 +0100 +++ b/tests/unicode.py Fri Feb 24 13:27:07 2017 +0100 @@ -212,3 +212,5 @@ print repr(euro) # "\u20ac" print ord(euro) # 8364 print "\u20ac" # ¤ +print unichr(ord(euro)) # ¤ +print unichr(ord(euro)) == euro # True diff -r 65cadf3264bc -r 7f284b0b42b9 translator.py --- a/translator.py Mon Feb 13 21:40:34 2017 +0100 +++ b/translator.py Fri Feb 24 13:27:07 2017 +0100 @@ -20,7 +20,8 @@ """ from common import CommonModule, CommonOutput, InstructionSequence, \ - first, get_builtin_class, init_item, predefined_constants + first, get_builtin_class, init_item, is_newer, \ + predefined_constants from encoders import encode_access_instruction, \ encode_function_pointer, encode_literal_constant, \ encode_literal_instantiator, encode_instantiator_pointer, \ @@ -45,22 +46,33 @@ self.deducer = deducer self.optimiser = optimiser self.output = output - self.modules = {} def to_output(self): + + "Write a program to the configured output directory." + + # Make a directory for the final sources. + output = join(self.output, "src") if not exists(output): makedirs(output) + # Clean the output directory of irrelevant data. + self.check_output() for module in self.importer.modules.values(): + output_filename = join(output, "%s.c" % module.name) + + # Do not generate modules in the native package. They are provided + # by native functionality source files. + parts = module.name.split(".") - if parts[0] != "native": + + if parts[0] != "native" and is_newer(module.filename, output_filename): tm = TranslatedModule(module.name, self.importer, self.deducer, self.optimiser) - tm.translate(module.filename, join(output, "%s.c" % module.name)) - self.modules[module.name] = tm + tm.translate(module.filename, output_filename) # Classes representing intermediate translation results. @@ -91,8 +103,8 @@ "A reference to a name in the translation." - def __init__(self, name, ref, expr=None, parameter=None, location=None): - results.ResolvedNameRef.__init__(self, name, ref, expr) + def __init__(self, name, ref, expr=None, is_global=False, parameter=None, location=None): + results.ResolvedNameRef.__init__(self, name, ref, expr, is_global) self.parameter = parameter self.location = location @@ -142,7 +154,7 @@ elif static_name: parent = ref.parent() context = ref.has_kind("") and encode_path(parent) or None - return "((__attr) {{.context=%s, .value=&%s}})" % (context and "&%s" % context or "0", static_name) + return "((__attr) {.value=&%s})" % static_name # Qualified names must be converted into parent-relative accesses. @@ -331,6 +343,10 @@ self.temp_usage = {} + # Initialise some data used for attribute access generation. + + self.init_substitutions() + def __repr__(self): return "TranslatedModule(%r, %r)" % (self.name, self.importer) @@ -726,7 +742,7 @@ # the complete access. name_ref = attr_expr and attr_expr.is_name() and attr_expr - name = name_ref and self.get_name_for_tracking(name_ref.name, name_ref and name_ref.final()) or None + name = name_ref and self.get_name_for_tracking(name_ref.name, name_ref) or None location = self.get_access_location(name, self.attrs) refs = self.get_referenced_attributes(location) @@ -738,42 +754,71 @@ "" : self.in_assignment, } - temp_subs = { - "" : "__tmp_context", - "" : "__tmp_value", - "" : "__tmp_target_value", - "" : "__tmp_value", - "" : "__tmp_target_value", - } - - op_subs = { - "" : "__set_accessor", - "" : "__set_target_accessor", - } - - subs.update(temp_subs) - subs.update(op_subs) + subs.update(self.temp_subs) + subs.update(self.op_subs) output = [] substituted = set() + # The context set or retrieved will be that used by any enclosing + # invocation. + + context_index = self.function_target - 1 + # Obtain encoded versions of each instruction, accumulating temporary # variables. for instruction in self.optimiser.access_instructions[location]: - encoded, _substituted = encode_access_instruction(instruction, subs) + encoded, _substituted = encode_access_instruction(instruction, subs, context_index) output.append(encoded) substituted.update(_substituted) # Record temporary name usage. for sub in substituted: - if temp_subs.has_key(sub): - self.record_temp(temp_subs[sub]) + if self.temp_subs.has_key(sub): + self.record_temp(self.temp_subs[sub]) del self.attrs[0] return AttrResult(output, refs, location) + def init_substitutions(self): + + """ + Initialise substitutions, defining temporary variable mappings, some of + which are also used as substitutions, together with operation mappings + used as substitutions in instructions defined by the optimiser. + """ + + self.temp_subs = { + + # Substitutions used by instructions. + + "" : "__tmp_private_context", + "" : "__tmp_value", + "" : "__tmp_target_value", + + # Mappings to be replaced by those given below. + + "" : "__tmp_contexts", + "" : "__tmp_contexts", + "" : "__tmp_contexts", + "" : "__tmp_contexts", + "" : "__tmp_private_context", + "" : "__tmp_value", + "" : "__tmp_target_value", + } + + self.op_subs = { + "" : "__get_context", + "" : "__test_context_revert", + "" : "__test_context_static", + "" : "__set_context", + "" : "__set_private_context", + "" : "__set_accessor", + "" : "__set_target_accessor", + } + def get_referenced_attributes(self, location): """ @@ -875,7 +920,7 @@ if not ref.static(): self.process_assignment_for_object( - n.name, make_expression("((__attr) {{.context=0, .value=&%s}})" % + n.name, make_expression("((__attr) {.value=&%s})" % encode_path(class_name))) self.enter_namespace(n.name) @@ -1054,9 +1099,7 @@ context = self.is_method(objpath) self.process_assignment_for_object(original_name, - make_expression("((__attr) {{.context=%s, .value=&%s}})" % ( - context and "&%s" % encode_path(context) or "0", - encode_path(objpath)))) + make_expression("((__attr) {.value=&%s})" % encode_path(objpath))) def process_function_defaults(self, n, name, objpath, instance_name=None): @@ -1146,7 +1189,21 @@ "Process the given invocation node 'n'." + # Any invocations in the expression will store target details in a + # different location. + + self.function_target += 1 + + # Process the expression. + expr = self.process_structure_node(n.node) + + # Reference the current target again. + + self.function_target -= 1 + + # Obtain details of the invocation expression. + objpath = expr.get_origin() location = expr.access_location() @@ -1167,6 +1224,7 @@ # Invocation requirements. context_required = True + have_access_context = isinstance(expr, AttrResult) parameters = None # Obtain details of the callable and of its parameters. @@ -1237,8 +1295,12 @@ # set to null. if context_required: - self.record_temp("__tmp_targets") - args = ["__CONTEXT_AS_VALUE(__tmp_targets[%d])" % self.function_target] + if have_access_context: + self.record_temp("__tmp_contexts") + args = ["(__attr) {.value=__tmp_contexts[%d]}" % self.function_target] + else: + self.record_temp("__tmp_targets") + args = ["__CONTEXT_AS_VALUE(__tmp_targets[%d])" % self.function_target] else: args = ["__NULL"] @@ -1335,8 +1397,11 @@ # Without a known specific callable, the expression provides the target. if not target or context_required: - self.record_temp("__tmp_targets") - stages.append("__tmp_targets[%d] = %s" % (self.function_target, expr)) + if target: + stages.append(str(expr)) + else: + self.record_temp("__tmp_targets") + stages.append("__tmp_targets[%d] = %s" % (self.function_target, expr)) # Any specific callable is then obtained. @@ -1349,7 +1414,13 @@ self.record_temp("__tmp_targets") if context_required: - stages.append("__get_function(__tmp_targets[%d])" % self.function_target) + if have_access_context: + self.record_temp("__tmp_contexts") + stages.append("__get_function(__tmp_contexts[%d], __tmp_targets[%d])" % ( + self.function_target, self.function_target)) + else: + stages.append("__get_function(__CONTEXT_AS_VALUE(__tmp_targets[%d]).value, __tmp_targets[%d])" % ( + self.function_target, self.function_target)) else: stages.append("__load_via_object(__tmp_targets[%d].value, %s).fn" % ( self.function_target, encode_symbol("pos", "__fn__"))) @@ -1414,14 +1485,14 @@ # Without defaults, produce an attribute referring to the function. if not defaults: - return make_expression("((__attr) {{.context=0, .value=&%s}})" % encode_path(function_name)) + return make_expression("((__attr) {.value=&%s})" % encode_path(function_name)) # With defaults, copy the function structure and set the defaults on the # copy. else: self.record_temp("__tmp_value") - return make_expression("(__tmp_value = __COPY(&%s, sizeof(%s)), %s, (__attr) {{.context=0, .value=__tmp_value}})" % ( + return make_expression("(__tmp_value = __COPY(&%s, sizeof(%s)), %s, (__attr) {.value=__tmp_value})" % ( encode_path(function_name), encode_symbol("obj", function_name), ", ".join(defaults))) @@ -1490,8 +1561,23 @@ # Determine any assigned globals. globals = self.importer.get_module(self.name).scope_globals.get(path) + + # Explicitly declared globals. + if globals and n.name in globals: objpath = self.get_global_path(n.name) + is_global = True + + # Implicitly referenced globals in functions. + + elif self.in_function: + is_global = n.name not in self.importer.function_locals[path] + + # Implicitly referenced globals elsewhere. + + else: + namespace = self.importer.identify(path) + is_global = not self.importer.get_attributes(namespace, n.name) # Get the static identity of the name. @@ -1520,7 +1606,7 @@ # static namespace members. The reference should be configured to return # such names. - return TrResolvedNameRef(n.name, ref, expr=expr, parameter=parameter, location=location) + return TrResolvedNameRef(n.name, ref, expr=expr, is_global=is_global, parameter=parameter, location=location) def process_not_node(self, n): @@ -1899,14 +1985,17 @@ # Provide space for the given number of targets. + targets = self.importer.function_targets.get(name) + if self.uses_temp(name, "__tmp_targets"): - targets = self.importer.function_targets.get(name) self.writeline("__attr __tmp_targets[%d];" % targets) + if self.uses_temp(name, "__tmp_contexts"): + self.writeline("__ref __tmp_contexts[%d];" % targets) # Add temporary variable usage details. - if self.uses_temp(name, "__tmp_context"): - self.writeline("__ref __tmp_context;") + if self.uses_temp(name, "__tmp_private_context"): + self.writeline("__ref __tmp_private_context;") if self.uses_temp(name, "__tmp_value"): self.writeline("__ref __tmp_value;") if self.uses_temp(name, "__tmp_target_value"):