diff options
author | Ian Johnson <ianj@wgrids.com> | 2013-10-16 20:26:03 +0400 |
---|---|---|
committer | Ian Johnson <ianj@wgrids.com> | 2013-10-16 20:26:03 +0400 |
commit | 6451c37835ef255ec01cbe182094a11db889d6e0 (patch) | |
tree | f9c297f899d888deb096b259420e322c9bb5afd6 | |
parent | ad4a1c1065086f4902b15e46e8b3a9013c00d419 (diff) |
More resolution code
-rw-r--r-- | src/pclc/parser/expressions.py | 5 | ||||
-rw-r--r-- | src/pclc/parser/pcl_parser.py | 2 | ||||
-rw-r--r-- | src/pclc/visitors/first_pass_resolver_visitor.py | 143 | ||||
-rw-r--r-- | src/pclc/visitors/second_pass_resolver_visitor.py | 17 | ||||
-rw-r--r-- | src/pclc/visitors/third_pass_resolver_visitor.py | 28 | ||||
-rw-r--r-- | src/runtime/pcl/os/path.py | 3 | ||||
-rw-r--r-- | src/runtime/pcl/util/string.py | 3 |
7 files changed, 148 insertions, 53 deletions
diff --git a/src/pclc/parser/expressions.py b/src/pclc/parser/expressions.py index b12411d..e2b0d73 100644 --- a/src/pclc/parser/expressions.py +++ b/src/pclc/parser/expressions.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU General Public License # along with Pipeline Creation Language (PCL). If not, see <http://www.gnu.org/licenses/>. # -from entity import Entity +import types +from entity import Entity from mappings import TopMapping, BottomMapping, LiteralMapping from pypeline.core.types.just import Just @@ -28,7 +29,7 @@ class Literal(Entity): self.value = value def __str__(self): - return str(self.value) + return '"' + str(self.value) + '"' if type(self.value) in types.StringTypes else str(self.value) def __repr__(self): return "<Literal: value = [%s], entity = %s>" % \ diff --git a/src/pclc/parser/pcl_parser.py b/src/pclc/parser/pcl_parser.py index 86a398f..08f8428 100644 --- a/src/pclc/parser/pcl_parser.py +++ b/src/pclc/parser/pcl_parser.py @@ -395,7 +395,7 @@ def p_function(p): def p_opt_function_args(p): '''opt_function_args : function_arg_list | ''' - if len(p) > 0: + if len(p) > 1: p[0] = p[1] else: p[0] = list() diff --git a/src/pclc/visitors/first_pass_resolver_visitor.py b/src/pclc/visitors/first_pass_resolver_visitor.py index 93e9609..37a47e9 100644 --- a/src/pclc/visitors/first_pass_resolver_visitor.py +++ b/src/pclc/visitors/first_pass_resolver_visitor.py @@ -125,7 +125,6 @@ class FirstPassResolverVisitor(ResolverVisitor): python_module_interface = [] if declarations: - print "\n".join(map(lambda d: str(d), declarations)) for decl in declarations: # Count occurrences of declaration identifiers try: @@ -250,10 +249,21 @@ class FirstPassResolverVisitor(ResolverVisitor): module_spec = {} if imported_module: funcs_and_specs = [(o[0], inspect.getargspec(o[1])) \ - for o in inspect.getmembers(imported_module) \ - if inspect.isfunction(o[1])] + for o in inspect.getmembers(imported_module, \ + lambda t: inspect.isfunction(t))] for k, v in funcs_and_specs: - module_spec[k] = v + if v.keywords: + self._add_warnings("WARNING: %(filename)s at line %(lineno)d, " \ + "dropping function %(func_name)s imported " \ + "from module %(module_name)s since arguments " \ + "are unsupported.", + [k], + lambda n: {'filename' : an_import.filename, + 'lineno' : an_import.lineno, + 'func_name' : k, + 'module_name' : an_import.module_name}) + else: + module_spec[k] = v return module_spec @@ -782,17 +792,25 @@ class FirstPassResolverVisitor(ResolverVisitor): @multimethod(Function) def visit(self, function): + # Get the package alias and function name package_alias, name = function.name.split(".") + # The imports import_symbol_dict = self._module.resolution_symbols['imports'] - if not import_symbol_dict.has_key(package_alias): - self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function package alias %(alias)s", - [function], - lambda f: {'filename' : f.filename, - 'lineno' : f.lineno, - 'alias' : package_alias}) - else: - functions = import_symbol_dict[package_alias] + + # This error is a little pointless. + #if not import_symbol_dict.has_key(package_alias): + # self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function package alias %(alias)s", + # [function], + # lambda f: {'filename' : f.filename, + # 'lineno' : f.lineno, + # 'alias' : package_alias}) + + # The key for the imports is an Identifier, so create an Identifier + # from the string package alias derived from the function call. + alias_identifier = Identifier(None, -1, package_alias) + if import_symbol_dict.has_key(alias_identifier): + functions = import_symbol_dict[alias_identifier] if not functions.has_key(name): self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function %(alias)s.%(name)s", [function], @@ -803,41 +821,72 @@ class FirstPassResolverVisitor(ResolverVisitor): else: # The argument spec from the imported module function_arg_spec = functions[name] - - if len(function_arg_spec.args) != len(function.arguments): - self._add_errors("ERROR: %(filename)s at line %(lineno)d, function %(name)s called with " \ - "%(given)d arguments, expected %(required)d", - [function], - lambda f: {'filename' : f.filename, - 'lineno' : f.lineno, - 'name' : f.name, - 'given' : len(f.arguments), - 'required' : len(function_arg_spec.args)}) - - # Check that the arguments are either inputs, configuration or assignment - for argument in function.arguments: - if isinstance(argument, Identifier) and \ - argument not in self._module.definition.inputs and \ - argument not in self._module.resolution_symbols['assignment_table']: - self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function argument %(arg_name)s", + no_defaults = len(function_arg_spec.defaults) if function_arg_spec.defaults else 0 + min_no_args = len(function_arg_spec.args) - no_defaults + + # If there are no argument default values and no var-args, then if the + # the number of arguments does *not* match the function's definition + # then that is an error. + if no_defaults < 1 and not function_arg_spec.varargs: + # The number of expected arguments *is* the length of the ArgSpec.args + if len(function_arg_spec.args) != len(function.arguments): + self._add_errors("ERROR: %(filename)s at line %(lineno)d, function %(name)s called with " \ + "%(given)d arguments, expected %(required)d", [function], lambda f: {'filename' : f.filename, 'lineno' : f.lineno, - 'arg_name' : argument}) - elif isinstance(argument, StateIdentifier) and \ - argument not in self._module.resolution_symbols['configuration']: - self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function argument %(arg_name)s", + 'name' : f.name, + 'given' : len(f.arguments), + 'required' : len(function_arg_spec.args)}) + elif no_defaults > 0 or function_arg_spec.varargs: + # If we have at least one default argument value or at least one var-args then + # if the minimum number of arguments expected for this function is less than + # the number of arguments in the function call then this is an error. + if len(function.arguments) < min_no_args: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, function %(name)s called with " \ + "%(given)d arguments, expected at least %(required)d", [function], lambda f: {'filename' : f.filename, 'lineno' : f.lineno, - 'arg_name' : argument}) + 'name' : f.name, + 'given' : len(f.arguments), + 'required' : min_no_args}) - # Mark the import as used - self._module.resolution_symbols['used_imports'][package_alias] = (self._module.resolution_symbols['used_imports'][package_alias][0], - True) + # Mark the import as used + self._module.resolution_symbols['used_imports'][alias_identifier] = (self._module.resolution_symbols['used_imports'][alias_identifier][0], + True) + + # Check that the arguments are either inputs, configuration or assignment + for argument in function.arguments: + if isinstance(argument, StateIdentifier): + if argument not in self._module.resolution_symbols['configuration']: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function argument %(arg_name)s", + [function], + lambda f: {'filename' : f.filename, + 'lineno' : f.lineno, + 'arg_name' : argument}) + else: + # Mark the configuration as used + try: + self._module.resolution_symbols['unused_configuration'].remove(argument) + except KeyError: + # We may well have removed this state identifier already + pass + elif isinstance(argument, Identifier): + if argument not in self._module.definition.inputs and \ + argument not in self._module.resolution_symbols['assignment_table']: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function argument %(arg_name)s TITZ", + [function], + lambda f: {'filename' : f.filename, + 'lineno' : f.lineno, + 'arg_name' : argument}) @multimethod(Command) def visit(self, command): + # If no assignment then we don't need to do anything. + if not command.identifier: + return + # Check the assigned variable is *not* an input. Inputs are immutable. if command.identifier in self._module.definition.inputs: self._add_errors("ERROR: %(filename)s at line %(lineno)d, attempt to write read-only input %(input)s", @@ -858,16 +907,19 @@ class FirstPassResolverVisitor(ResolverVisitor): # generate all code to provide the previous use! if command.identifier in self._module.resolution_symbols['assignment_table']: self._add_warnings("WARNING: %(filename)s at line %(lineno)d, duplicate assignment variable %(var_name)s", - [command], + [command.identifier], lambda c: {'filename' : c.filename, 'lineno' : c.lineno, - 'var_name' : c.identifier}) - - # Record the assignment and the command - self._module.resolution_symbols['assignment_table'][command.identifier] = command + 'var_name' : c}) + else: + # Record the assignment and the command + self._module.resolution_symbols['assignment_table'][command.identifier] = command @multimethod(Return) def visit(self, ret): + if not ret.mappings: + return + # Duplicate 'to' maps duplicate_to = dict() missing_froms = list() @@ -898,6 +950,13 @@ class FirstPassResolverVisitor(ResolverVisitor): 'lineno' : t.lineno, 'missing' : str(t)}) + # Unknown 'to' maps + self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown output in return %(unknown)s", + frozenset([mapping.to for mapping in ret.mappings if mapping.to not in self._module.definition.outputs]), + lambda m: {'filename' : m.filename, + 'lineno' : m.lineno, + 'unknown' : m}) + # Missing 'from' maps self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown variable in return %(unknown)s", missing_froms, diff --git a/src/pclc/visitors/second_pass_resolver_visitor.py b/src/pclc/visitors/second_pass_resolver_visitor.py index 00bde81..3923191 100644 --- a/src/pclc/visitors/second_pass_resolver_visitor.py +++ b/src/pclc/visitors/second_pass_resolver_visitor.py @@ -18,6 +18,7 @@ # from multimethod import multimethod, multimethodclass from parser.import_spec import Import +from parser.command import Function, Command, Return, IfCommand from parser.component import Component from parser.conditional_expressions import ConditionalExpression, \ UnaryConditionalExpression, \ @@ -125,3 +126,19 @@ class SecondPassResolverVisitor(FirstPassResolverVisitor): @multimethod(TerminalConditionalExpression) def visit(self, term_cond_expr): pass + + @multimethod(Function) + def visit(self, function): + pass + + @multimethod(Command) + def visit(self, command): + pass + + @multimethod(Return) + def visit(self, ret): + pass + + @multimethod(IfCommand) + def visit(self, if_command): + pass diff --git a/src/pclc/visitors/third_pass_resolver_visitor.py b/src/pclc/visitors/third_pass_resolver_visitor.py index 21e4f9c..f04442a 100644 --- a/src/pclc/visitors/third_pass_resolver_visitor.py +++ b/src/pclc/visitors/third_pass_resolver_visitor.py @@ -49,6 +49,9 @@ class ThirdPassResolverVisitor(SecondPassResolverVisitor): @multimethod(object) def visit(self, nowt): + if self._module.definition.is_leaf: + return + # Root expression, so get inputs and outpus # from module defined inputs and outputs clauses expr = self._module.definition.definition @@ -157,11 +160,20 @@ class ThirdPassResolverVisitor(SecondPassResolverVisitor): 'filename' : e.filename, 'lineno' : e.lineno}) elif isinstance(terminal, Identifier): - self._current_if_expr.resolution_symbols['inputs'] >= (lambda s: self._add_errors("ERROR: %(filename)s at line %(lineno)d, " \ - "identifier %(entity)s not defined in " \ - "if inputs", - [terminal], - lambda e: {'entity' : e, - 'filename' : e.filename, - 'lineno' : e.lineno}) \ - if terminal not in s else None) + if self._module.definition.is_leaf: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown identifier %(identifier)s", + [terminal] if terminal not in self._module.definition.inputs and \ + terminal not in self._module.resolution_symbols['assignment_table'] \ + else [], + lambda t: {'filename' : t.filename, + 'lineno' : t.lineno, + 'identifier' : t}) + else: + self._current_if_expr.resolution_symbols['inputs'] >= (lambda s: self._add_errors("ERROR: %(filename)s at line %(lineno)d, " \ + "identifier %(entity)s not defined in " \ + "if inputs", + [terminal], + lambda e: {'entity' : e, + 'filename' : e.filename, + 'lineno' : e.lineno}) \ + if terminal not in s else None) diff --git a/src/runtime/pcl/os/path.py b/src/runtime/pcl/os/path.py index b5a9349..2dd1092 100644 --- a/src/runtime/pcl/os/path.py +++ b/src/runtime/pcl/os/path.py @@ -26,3 +26,6 @@ join = lambda *args: os.path.join(*args) # mkdir :: String -> () mkdir = lambda p: os.mkdir(p) + +# basename :: String -> String +basename = lambda s: os.path.basename(s) diff --git a/src/runtime/pcl/util/string.py b/src/runtime/pcl/util/string.py index 120c9c6..7ba1859 100644 --- a/src/runtime/pcl/util/string.py +++ b/src/runtime/pcl/util/string.py @@ -19,3 +19,6 @@ # split :: String -> String -> String split = lambda s, ss: s.__str__().split(ss) + +# join :: [String] -> String -> String +join = lambda l, s: s.join(l) |