# HG changeset patch # User Paul Boddie # Date 1487939264 -3600 # Node ID 41c666f36a6ed9fde22c0401cb26b7e09c5d3738 # Parent 8325575e2d240b528ef4c4380c7c53665f407f5b# Parent 7f284b0b42b93ad8e81e2fa661183863cf8b57e2 Merged the attr-strvalue-without-size branch into the default branch. diff -r 8325575e2d24 -r 41c666f36a6e branching.py --- a/branching.py Mon Feb 20 18:48:39 2017 +0100 +++ b/branching.py Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e common.py --- a/common.py Mon Feb 20 18:48:39 2017 +0100 +++ b/common.py Fri Feb 24 13:27:44 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." diff -r 8325575e2d24 -r 41c666f36a6e deducer.py --- a/deducer.py Mon Feb 20 18:48:39 2017 +0100 +++ b/deducer.py Fri Feb 24 13:27:44 2017 +0100 @@ -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 8325575e2d24 -r 41c666f36a6e docs/lplc.1 --- a/docs/lplc.1 Mon Feb 20 18:48:39 2017 +0100 +++ b/docs/lplc.1 Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e encoders.py --- a/encoders.py Mon Feb 20 18:48:39 2017 +0100 +++ b/encoders.py Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e generator.py --- a/generator.py Mon Feb 20 18:48:39 2017 +0100 +++ b/generator.py Fri Feb 24 13:27:44 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")] @@ -500,6 +519,11 @@ else: attrs["__key__"] = None + # Initialise the size, if a string. + + if attrs.has_key("__size__"): + attrs["__size__"] = len(data) + # Define Unicode constant encoding details. if cls == self.unicode_type: @@ -512,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. @@ -533,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("") @@ -571,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): @@ -632,25 +660,45 @@ # Write the corresponding definition. - print >>f_defs, "const __table %s = {\n %s,\n {\n %s\n }\n };\n" % ( - table_name, structure_size, - ",\n ".join(table)) + print >>f_defs, """\ +const __table %s = { + %s, + { + %s + } +}; +""" % (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("{.code=%s, .pos=%s}" % t) + print >>f_decls, "extern const __ptable %s;\n" % table_name # Write the corresponding definition. - print >>f_defs, "const __ptable %s = {\n %s,\n {\n %s\n }\n };\n" % ( - table_name, structure_size, - ",\n ".join([("{%s, %s}" % t) for t in table])) + print >>f_defs, """\ +const __ptable %s = { + .min=%s, + .max=%s, + .size=%s, + { + %s + } +}; +""" % (table_name, min_parameters, max_parameters, structure_size, + ",\n ".join(members)) def write_instance_structure(self, f_decls, path, structure_size): @@ -860,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. @@ -878,23 +920,28 @@ 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. elif attrname == "__data__": - structure.append("{{.size=%d, .%s=%s}}" % ( - encode_literal_constant_size(attr), + structure.append("{.%s=%s}" % ( encode_literal_constant_member(attr), encode_literal_constant_value(attr))) continue + # Special internal size member. + + elif attrname == "__size__": + structure.append("{.intvalue=%d}" % attr) + continue + # 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. @@ -926,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 @@ -937,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. @@ -968,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. @@ -982,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): @@ -1116,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 8325575e2d24 -r 41c666f36a6e inspector.py --- a/inspector.py Mon Feb 20 18:48:39 2017 +0100 +++ b/inspector.py Fri Feb 24 13:27:44 2017 +0100 @@ -518,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() diff -r 8325575e2d24 -r 41c666f36a6e internal_tests/branches.py --- a/internal_tests/branches.py Mon Feb 20 18:48:39 2017 +0100 +++ b/internal_tests/branches.py Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e lib/__builtins__/core.py --- a/lib/__builtins__/core.py Mon Feb 20 18:48:39 2017 +0100 +++ b/lib/__builtins__/core.py Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e lib/__builtins__/str.py --- a/lib/__builtins__/str.py Mon Feb 20 18:48:39 2017 +0100 +++ b/lib/__builtins__/str.py Fri Feb 24 13:27:44 2017 +0100 @@ -22,8 +22,8 @@ from __builtins__.operator import _negate from __builtins__.sequence import hashable, itemaccess from __builtins__.types import check_int -from native import str_add, str_lt, str_gt, str_eq, str_len, str_ord, \ - str_nonempty, str_substr +from native import int_new, str_add, str_lt, str_gt, str_eq, str_ord, \ + str_substr WHITESPACE = (" ", "\f", "\n", "\r", "\t") @@ -39,26 +39,25 @@ # literals or converted using routines defined for other types, no form # of actual initialisation is performed here. + # Note the __key__ member. This is also initialised statically. Where + # a string is the same as an attribute name, the __key__ member contains + # attribute position and code details. + # NOTE: Cannot perform "other and other.__data__ or None" since the # NOTE: __data__ attribute is not a normal attribute. if other: self.__data__ = other.__data__ + self.__key__ = other.__key__ + self.__size__ = other.__size__ else: self.__data__ = None - - # Note the __key__ member. This is also initialised statically. Where - # a string is the same as an attribute name, the __key__ member contains - # attribute position and code details. - - if other: - self.__key__ = other.__key__ - else: self.__key__ = None + self.__size__ = None # Internal methods. - def _binary_op(self, op, other): + def _binary_op(self, op, other, sizes=False): "Perform 'op' on this object and 'other' if appropriate." @@ -69,10 +68,12 @@ # Otherwise, perform the operation on the operands' data. + elif sizes: + return op(self.__data__, other.__data__, self.__size__, other.__size__) else: return op(self.__data__, other.__data__) - def _binary_op_rev(self, op, other): + def _binary_op_rev(self, op, other, sizes=False): "Perform 'op' on 'other' and this object if appropriate." @@ -83,6 +84,8 @@ # Otherwise, perform the operation on the operands' data. + elif sizes: + return op(other.__data__, self.__data__, other.__size__, self.__size__) else: return op(other.__data__, self.__data__) @@ -154,7 +157,7 @@ "Return the number of bytes in this string." - return str_len(self.__data__) + return int_new(self.__size__) # General type methods. @@ -162,7 +165,7 @@ "Return whether the string provides any data." - return str_nonempty(self.__data__) + return int_new(self.__size__).__bool__() def __contains__(self, value): @@ -196,7 +199,7 @@ "Return a string combining this string with 'other'." - return self._binary_op(str_add, other) + return self._binary_op(str_add, other, True) __add__ = __iadd__ @@ -204,7 +207,7 @@ "Return a string combining this string with 'other'." - return self._binary_op_rev(str_add, other) + return self._binary_op_rev(str_add, other, True) def __mod__(self, other): pass def __rmod__(self, other): pass diff -r 8325575e2d24 -r 41c666f36a6e lib/__builtins__/unicode.py --- a/lib/__builtins__/unicode.py Mon Feb 20 18:48:39 2017 +0100 +++ b/lib/__builtins__/unicode.py Fri Feb 24 13:27:44 2017 +0100 @@ -40,7 +40,7 @@ self.encoding = encoding self.length = None - def _binary_op(self, op, other): + def _binary_op(self, op, other, sizes=False): "Perform 'op' on this object and 'other' if appropriate." @@ -51,16 +51,17 @@ # Combining text with bytes. - elif not _isinstance(other, utf8string): + if not _isinstance(other, utf8string): s = self.encode() + else: + s = self + + if sizes: + return op(s.__data__, other.__data__, s.__size__, other.__size__) + else: return op(s.__data__, other.__data__) - # Otherwise, perform the operation on the operands' data. - - else: - return op(self.__data__, other.__data__) - - def _binary_op_rev(self, op, other): + def _binary_op_rev(self, op, other, sizes=False): "Perform 'op' on 'other' and this object if appropriate." @@ -71,14 +72,15 @@ # Combining text with bytes. - elif not _isinstance(other, utf8string): + if not _isinstance(other, utf8string): s = self.encode() - return op(other.__data__, s.__data__) + else: + s = self - # Otherwise, perform the operation on the operands' data. - + if sizes: + return op(other.__data__, s.__data__, other.__size__, s.__size__) else: - return op(other.__data__, self.__data__) + return op(other.__data__, s.__data__) def _convert(self, result, other): @@ -118,7 +120,7 @@ "Return a string combining this string with 'other'." - return self._convert(self._binary_op(str_add, other), other) + return self._convert(self._binary_op(str_add, other, True), other) __add__ = __iadd__ @@ -126,14 +128,14 @@ "Return a string combining this string with 'other'." - return self._convert(self._binary_op_rev(str_add, other), other) + return self._convert(self._binary_op_rev(str_add, other, True), other) def __len__(self): "Return the length of this string in characters." if self.length is None: - self.length = unicode_len(self.__data__) + self.length = unicode_len(self.__data__, self.__size__) return self.length @@ -142,7 +144,7 @@ "Return the value of the string, if only a single character." if self.__len__() == 1: - return unicode_ord(self.__data__) + return unicode_ord(self.__data__, self.__size__) else: raise ValueError, self @@ -204,7 +206,7 @@ "Return the item at the normalised (positive) 'index'." self._check_index(index) - return utf8string(unicode_substr(self.__data__, index, index + 1, 1), self.encoding) + return utf8string(unicode_substr(self.__data__, self.__size__, index, index + 1, 1), self.encoding) def __get_multiple_items__(self, start, end, step): diff -r 8325575e2d24 -r 41c666f36a6e lib/native/__init__.py --- a/lib/native/__init__.py Mon Feb 20 18:48:39 2017 +0100 +++ b/lib/native/__init__.py Fri Feb 24 13:27:44 2017 +0100 @@ -23,7 +23,8 @@ from native.identity import is_, is_not -from native.int import int_add, int_div, int_mod, int_mul, int_neg, int_pow, \ +from native.int import int_new, \ + int_add, int_div, int_mod, int_mul, int_neg, int_pow, \ int_sub, int_and, int_not, int_or, int_xor, int_lt, \ int_gt, int_eq, int_ne, int_str @@ -42,8 +43,8 @@ from native.program import get_using -from native.str import str_add, str_chr, str_eq, str_gt, str_lt, str_len, \ - str_nonempty, str_ord, str_substr +from native.str import str_add, str_chr, str_eq, str_gt, str_lt, \ + str_ord, str_substr from native.system import exit, get_argv, get_path diff -r 8325575e2d24 -r 41c666f36a6e lib/native/int.py --- a/lib/native/int.py Mon Feb 20 18:48:39 2017 +0100 +++ b/lib/native/int.py Fri Feb 24 13:27:44 2017 +0100 @@ -8,7 +8,7 @@ non-core exceptions used by the native functions because they need to be identified as being needed by the program. -Copyright (C) 2011, 2015, 2016 Paul Boddie +Copyright (C) 2011, 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 @@ -24,6 +24,8 @@ this program. If not, see . """ +def int_new(data): pass + def int_add(self, other): pass def int_div(self, other): pass def int_mod(self, other): pass diff -r 8325575e2d24 -r 41c666f36a6e lib/native/str.py --- a/lib/native/str.py Mon Feb 20 18:48:39 2017 +0100 +++ b/lib/native/str.py Fri Feb 24 13:27:44 2017 +0100 @@ -26,13 +26,11 @@ # String operations. -def str_add(data, other_data): pass +def str_add(data, other_data, size, other_size): pass def str_chr(data): pass def str_eq(data, other_data): pass def str_gt(data, other_data): pass def str_lt(data, other_data): pass -def str_len(data): pass -def str_nonempty(data): pass def str_ord(data): pass def str_substr(data, start, end, step): pass diff -r 8325575e2d24 -r 41c666f36a6e lib/native/unicode.py --- a/lib/native/unicode.py Mon Feb 20 18:48:39 2017 +0100 +++ b/lib/native/unicode.py Fri Feb 24 13:27:44 2017 +0100 @@ -26,9 +26,9 @@ # Unicode string operations. -def unicode_len(data): pass -def unicode_ord(data): pass -def unicode_substr(data, start, end, step): pass +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 8325575e2d24 -r 41c666f36a6e lplc --- a/lplc Mon Feb 20 18:48:39 2017 +0100 +++ b/lplc Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e optimiser.py --- a/optimiser.py Mon Feb 20 18:48:39 2017 +0100 +++ b/optimiser.py Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e templates/gc_sections.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/gc_sections.mk Fri Feb 24 13:27:44 2017 +0100 @@ -0,0 +1,2 @@ +CFLAGS += -fdata-sections -ffunction-sections +LDFLAGS += -Wl,--gc-sections diff -r 8325575e2d24 -r 41c666f36a6e templates/native/buffer.c --- a/templates/native/buffer.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/buffer.c Fri Feb 24 13:27:44 2017 +0100 @@ -1,6 +1,6 @@ /* Native functions for buffer 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 @@ -37,7 +37,7 @@ /* Calculate the size of the string. */ for (i = 0; i < data->size; i++) - size += __load_via_object(data->attrs[i].value, __pos___data__).size; + size += __load_via_object(data->attrs[i].value, __pos___size__).intvalue; /* Reserve space for a new string. */ s = (char *) __ALLOCATE(size + 1, sizeof(char)); @@ -46,7 +46,7 @@ for (i = 0, j = 0; i < data->size; i++) { o = __load_via_object(data->attrs[i].value, __pos___data__); - n = o.size; + n = __load_via_object(data->attrs[i].value, __pos___size__).intvalue; memcpy(s + j, o.strvalue, n); /* does not null terminate but final byte should be zero */ j += n; } diff -r 8325575e2d24 -r 41c666f36a6e templates/native/common.c --- a/templates/native/common.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/common.c Fri Feb 24 13:27:44 2017 +0100 @@ -34,12 +34,12 @@ return attr; } -__attr __new_str(char *s, size_t size) +__attr __new_str(char *s, int size) { - /* Create a new string and mutate the __data__ and __key__ attributes. */ + /* Create a new string and mutate the __data__, __size__ and __key__ attributes. */ __attr attr = __NEWINSTANCE(__builtins___str_string); - attr.value->attrs[__pos___data__].size = size; attr.value->attrs[__pos___data__].strvalue = s; + attr.value->attrs[__pos___size__].intvalue = size; attr.value->attrs[__pos___key__] = __NULL; return attr; } diff -r 8325575e2d24 -r 41c666f36a6e templates/native/common.h --- a/templates/native/common.h Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/common.h Fri Feb 24 13:27:44 2017 +0100 @@ -1,6 +1,6 @@ /* Common operations for native functions. -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 @@ -24,7 +24,7 @@ /* Utility functions. */ __attr __new_int(int i); -__attr __new_str(char *s, size_t size); +__attr __new_str(char *s, int size); __attr __new_list(__fragment *f); __fragment *__fragment_append(__fragment *data, __attr * const value); diff -r 8325575e2d24 -r 41c666f36a6e templates/native/iconv.c --- a/templates/native/iconv.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/iconv.c Fri Feb 24 13:27:44 2017 +0100 @@ -1,6 +1,6 @@ /* Native functions for character set conversion. -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 @@ -156,7 +156,6 @@ /* Return the descriptor as an opaque value. */ - attr.context = 0; attr.datavalue = (void *) result; return attr; } diff -r 8325575e2d24 -r 41c666f36a6e templates/native/int.c --- a/templates/native/int.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/int.c Fri Feb 24 13:27:44 2017 +0100 @@ -1,6 +1,6 @@ /* Native functions for integer 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 @@ -18,8 +18,9 @@ #include /* INT_MAX, INT_MIN */ #include /* ceil, log10, pow */ -#include /* fdopen, snprintf, strlen */ +#include /* fdopen, snprintf */ #include /* errno */ +#include /* strlen */ #include "native/common.h" #include "types.h" #include "exceptions.h" @@ -31,6 +32,13 @@ /* Integer operations. */ +__attr __fn_native_int_int_new(__attr __args[]) +{ + __attr * const _data = &__args[1]; + + return __new_int(_data->intvalue); +} + __attr __fn_native_int_int_add(__attr __args[]) { __attr * const _data = &__args[1]; diff -r 8325575e2d24 -r 41c666f36a6e templates/native/int.h --- a/templates/native/int.h Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/int.h Fri Feb 24 13:27:44 2017 +0100 @@ -1,6 +1,6 @@ /* Native functions for integer 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 @@ -23,6 +23,8 @@ /* Integer operations. */ +__attr __fn_native_int_int_new(__attr __args[]); + __attr __fn_native_int_int_add(__attr __args[]); __attr __fn_native_int_int_div(__attr __args[]); __attr __fn_native_int_int_mod(__attr __args[]); diff -r 8325575e2d24 -r 41c666f36a6e templates/native/io.c --- a/templates/native/io.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/io.c Fri Feb 24 13:27:44 2017 +0100 @@ -1,6 +1,6 @@ /* Native functions for input/output. -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 @@ -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; } @@ -160,9 +158,9 @@ /* fp interpreted as FILE reference */ FILE *f = (FILE *) fp->datavalue; /* str.__data__ interpreted as string */ - __attr sa = __load_via_object(str->value, __pos___data__); - char *s = sa.strvalue; - size_t to_write = sa.size; + char *s = __load_via_object(str->value, __pos___data__).strvalue; + /* str.__size__ interpreted as int */ + int to_write = __load_via_object(str->value, __pos___size__).intvalue; size_t have_written = fwrite(s, sizeof(char), to_write, f); int error; @@ -222,12 +220,13 @@ /* fd.__data__ interpreted as int */ int i = __load_via_object(fd->value, __pos___data__).intvalue; /* str.__data__ interpreted as string */ - __attr sa = __load_via_object(str->value, __pos___data__); - char *s = sa.strvalue; + char *s = __load_via_object(str->value, __pos___data__).strvalue; + /* str.__size__ interpreted as int */ + int size = __load_via_object(str->value, __pos___size__).intvalue; ssize_t have_written; errno = 0; - have_written = write(i, s, sizeof(char) * sa.size); + have_written = write(i, s, sizeof(char) * size); if (have_written == -1) __raise_io_error(__new_int(errno)); diff -r 8325575e2d24 -r 41c666f36a6e templates/native/list.c --- a/templates/native/list.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/list.c Fri Feb 24 13:27:44 2017 +0100 @@ -32,7 +32,7 @@ __attr * const size = &__args[1]; /* size.__data__ interpreted as int */ unsigned int n = __load_via_object(size->value, __pos___data__).intvalue; - __attr attr = {{.size=0, .seqvalue=__new_fragment(n)}}; + __attr attr = {.seqvalue=__new_fragment(n)}; /* Return the __data__ attribute. */ return attr; @@ -61,7 +61,7 @@ /* Replace the __data__ attribute if appropriate. */ if (newdata != data) - __store_via_object(self->value, __pos___data__, ((__attr) {{.size=0, .seqvalue=newdata}})); + __store_via_object(self->value, __pos___data__, ((__attr) {.seqvalue=newdata})); return __builtins___none_None; } @@ -92,7 +92,7 @@ /* Replace the __data__ attribute if appropriate. */ if (newdata != data) - __store_via_object(self->value, __pos___data__, ((__attr) {{.size=0, .seqvalue=newdata}})); + __store_via_object(self->value, __pos___data__, ((__attr) {.seqvalue=newdata})); return __builtins___none_None; } diff -r 8325575e2d24 -r 41c666f36a6e templates/native/locale.c diff -r 8325575e2d24 -r 41c666f36a6e templates/native/str.c --- a/templates/native/str.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/str.c Fri Feb 24 13:27:44 2017 +0100 @@ -32,14 +32,17 @@ { __attr * const _data = &__args[1]; __attr * const other = &__args[2]; + __attr * const _size = &__args[3]; + __attr * const othersize = &__args[4]; /* _data, other interpreted as string */ char *s = _data->strvalue; char *o = other->strvalue; - int n = _data->size + other->size; + int ss = _size->intvalue, os = othersize->intvalue; + int n = ss + os; char *r = (char *) __ALLOCATE(n + 1, sizeof(char)); - memcpy(r, s, _data->size); - memcpy(r + _data->size, o, other->size); + memcpy(r, s, ss); + memcpy(r + ss, o, os); /* Return a new string. */ return __new_str(r, n); @@ -92,21 +95,6 @@ return strcmp(s, o) == 0 ? __builtins___boolean_True : __builtins___boolean_False; } -__attr __fn_native_str_str_len(__attr __args[]) -{ - __attr * const _data = &__args[1]; - - /* Return the new integer. */ - return __new_int(_data->size); -} - -__attr __fn_native_str_str_nonempty(__attr __args[]) -{ - __attr * const _data = &__args[1]; - - return _data->size ? __builtins___boolean_True : __builtins___boolean_False; -} - __attr __fn_native_str_str_ord(__attr __args[]) { __attr * const _data = &__args[1]; diff -r 8325575e2d24 -r 41c666f36a6e templates/native/str.h --- a/templates/native/str.h Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/str.h Fri Feb 24 13:27:44 2017 +0100 @@ -26,8 +26,6 @@ __attr __fn_native_str_str_lt(__attr __args[]); __attr __fn_native_str_str_gt(__attr __args[]); __attr __fn_native_str_str_eq(__attr __args[]); -__attr __fn_native_str_str_len(__attr __args[]); -__attr __fn_native_str_str_nonempty(__attr __args[]); __attr __fn_native_str_str_ord(__attr __args[]); __attr __fn_native_str_str_substr(__attr __args[]); diff -r 8325575e2d24 -r 41c666f36a6e templates/native/unicode.c --- a/templates/native/unicode.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/native/unicode.c Fri Feb 24 13:27:44 2017 +0100 @@ -72,11 +72,14 @@ __attr __fn_native_unicode_unicode_len(__attr __args[]) { __attr * const _data = &__args[1]; + __attr * const _size = &__args[2]; /* _data interpreted as string */ char *s = _data->strvalue; + /* _size interpreted as int */ + int size = _size->intvalue; unsigned int i, c = 0; - for (i = 0; i < _data->size; i++) + for (i = 0; i < size; i++) if (boundary(s[i])) c++; @@ -87,11 +90,14 @@ __attr __fn_native_unicode_unicode_ord(__attr __args[]) { __attr * const _data = &__args[1]; + __attr * const _size = &__args[2]; /* _data interpreted as string */ char *s = _data->strvalue; + /* _size interpreted as int */ + int size = _size->intvalue; unsigned int i, c = 0, v; - for (i = 0; i < _data->size; i++) + for (i = 0; i < size; i++) { /* Evaluate the current character as a boundary. */ @@ -120,11 +126,14 @@ __attr __fn_native_unicode_unicode_substr(__attr __args[]) { __attr * const _data = &__args[1]; - __attr * const start = &__args[2]; - __attr * const end = &__args[3]; - __attr * const step = &__args[4]; + __attr * const _size = &__args[2]; + __attr * const start = &__args[3]; + __attr * const end = &__args[4]; + __attr * const step = &__args[5]; /* _data interpreted as string */ char *s = _data->strvalue, *sub; + /* _size interpreted as int */ + int ss = _size->intvalue; /* start.__data__ interpreted as int */ int istart = __load_via_object(start->value, __pos___data__).intvalue; /* end.__data__ interpreted as int */ @@ -137,14 +146,14 @@ unsigned int indexes[nchar]; unsigned int c, d, i, to, from, lastbyte = 0; - size_t resultsize = 0; + int resultsize = 0; /* Find the indexes of the characters. */ if (istep > 0) { /* Get the first byte position. */ for (c = 0; c < istart; c++) - lastbyte = nextpos(s, _data->size, lastbyte); + lastbyte = nextpos(s, ss, lastbyte); /* Get each subsequent byte position. */ for (c = istart, i = 0; i < nchar; c += istep, i++) @@ -152,17 +161,17 @@ indexes[i] = lastbyte; /* Add the character size to the result size. */ - resultsize += nextpos(s, _data->size, lastbyte) - lastbyte; + resultsize += nextpos(s, ss, lastbyte) - lastbyte; for (d = c; d < c + istep; d++) - lastbyte = nextpos(s, _data->size, lastbyte); + lastbyte = nextpos(s, ss, lastbyte); } } else { /* Get the first byte position. */ for (c = 0; c < istart; c++) - lastbyte = nextpos(s, _data->size, lastbyte); + lastbyte = nextpos(s, ss, lastbyte); /* Get each subsequent byte position. */ for (c = istart, i = 0; i < nchar; c += istep, i++) @@ -170,7 +179,7 @@ indexes[i] = lastbyte; /* Add the character size to the result size. */ - resultsize += nextpos(s, _data->size, lastbyte) - lastbyte; + resultsize += nextpos(s, ss, lastbyte) - lastbyte; for (d = c; d > c + istep; d--) lastbyte = prevpos(s, lastbyte); diff -r 8325575e2d24 -r 41c666f36a6e templates/ops.c --- a/templates/ops.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/ops.c Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e templates/ops.h --- a/templates/ops.h Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/ops.h Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e templates/progops.c --- a/templates/progops.c Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/progops.c Fri Feb 24 13:27:44 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, __pos___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. */ @@ -57,7 +60,7 @@ /* Calculate the size of the fragment. */ __fragment *data = __new_fragment(number); - __attr attr = {{.size=0, .seqvalue=data}}; + __attr attr = {.seqvalue=data}; unsigned int i, j; /* Copy the given number of values, starting from the second element. */ @@ -69,7 +72,7 @@ /* Store a reference to the data in the object's __data__ attribute. */ - __store_via_object(args[0].value, __pos___data__, attr); + __store_via_object(args[0].value, __ATTRPOS(__data__), attr); } #ifdef __HAVE___builtins___dict_dict @@ -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, __pos___args__, __code___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 8325575e2d24 -r 41c666f36a6e templates/progops.h --- a/templates/progops.h Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/progops.h Fri Feb 24 13:27:44 2017 +0100 @@ -1,6 +1,6 @@ /* Operations depending on program specifics. -Copyright (C) 2015, 2016 Paul Boddie +Copyright (C) 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 @@ -19,11 +19,13 @@ #ifndef __PROGOPS_H__ #define __PROGOPS_H__ +#include /* size_t */ #include "types.h" /* 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 8325575e2d24 -r 41c666f36a6e templates/types.h --- a/templates/types.h Mon Feb 20 18:48:39 2017 +0100 +++ b/templates/types.h Fri Feb 24 13:27:44 2017 +0100 @@ -19,15 +19,15 @@ #ifndef __TYPES_H__ #define __TYPES_H__ -#include /* size_t */ - /* 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. */ @@ -49,51 +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 */ - }; - struct { - size_t size; /* size of value */ - 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 @@ -103,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. */ @@ -114,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 @@ -124,7 +107,7 @@ /* Special null values. */ -#define __NULL ((__attr) {{.context=0, .value=0}}) +#define __NULL ((__attr) {.value=0}) /* Function pointer type. */ diff -r 8325575e2d24 -r 41c666f36a6e test_all.sh --- a/test_all.sh Mon Feb 20 18:48:39 2017 +0100 +++ b/test_all.sh Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e tests/methods_rebound.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/methods_rebound.py Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e tests/nested_calls.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/nested_calls.py Fri Feb 24 13:27:44 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 8325575e2d24 -r 41c666f36a6e translator.py --- a/translator.py Mon Feb 20 18:48:39 2017 +0100 +++ b/translator.py Fri Feb 24 13:27:44 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. @@ -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) @@ -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))) @@ -1914,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"):