diff options
author | Ian Johnson <ian.johnson@appliedlanguage.com> | 2013-10-14 19:58:06 +0400 |
---|---|---|
committer | Ian Johnson <ian.johnson@appliedlanguage.com> | 2013-10-14 19:58:06 +0400 |
commit | ad4a1c1065086f4902b15e46e8b3a9013c00d419 (patch) | |
tree | 5121dcb0db0398c63090f4362b3cf962bb4b6261 | |
parent | 259b829b0b89966fa3613096f5015cd997f9e9f1 (diff) |
Further resolution code
-rw-r--r-- | src/pclc/parser/pcl_parser.py | 4 | ||||
-rw-r--r-- | src/pclc/visitors/first_pass_resolver_visitor.py | 334 | ||||
-rw-r--r-- | src/pclc/visitors/third_pass_resolver_visitor.py | 2 | ||||
-rw-r--r-- | src/runtime/pcl/io/__init__.py | 0 | ||||
-rw-r--r-- | src/runtime/pcl/io/file.py | 25 | ||||
-rw-r--r-- | src/runtime/pcl/os/__init__.py | 0 | ||||
-rw-r--r-- | src/runtime/pcl/os/path.py | 28 | ||||
-rw-r--r-- | src/runtime/pcl/util/list.py | 2 |
8 files changed, 307 insertions, 88 deletions
diff --git a/src/pclc/parser/pcl_parser.py b/src/pclc/parser/pcl_parser.py index f4d186e..86a398f 100644 --- a/src/pclc/parser/pcl_parser.py +++ b/src/pclc/parser/pcl_parser.py @@ -389,8 +389,8 @@ def p_do_command(p): p[0] = Command(p.parser.filename, p[1].lineno, None, p[1]) def p_function(p): - '''function : identifier_or_qual_identifier '(' opt_function_args ')' ''' - p[0] = Function(p.parser.filename, p[1].lineno, p[1], p[3]) + '''function : QUALIFIED_IDENTIFIER '(' opt_function_args ')' ''' + p[0] = Function(p.parser.filename, p.lineno(1), p[1], p[3]) def p_opt_function_args(p): '''opt_function_args : function_arg_list diff --git a/src/pclc/visitors/first_pass_resolver_visitor.py b/src/pclc/visitors/first_pass_resolver_visitor.py index fc6ae99..93e9609 100644 --- a/src/pclc/visitors/first_pass_resolver_visitor.py +++ b/src/pclc/visitors/first_pass_resolver_visitor.py @@ -18,11 +18,14 @@ # import collections import glob +import inspect import os import sys +import types 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 AndConditionalExpression, \ OrConditionalExpression, \ @@ -202,12 +205,18 @@ class FirstPassResolverVisitor(ResolverVisitor): 'used_components' : dict(), 'used_imports' : dict(), 'configuration' : dict(), - 'unused_configuration' : list()} + 'unused_configuration' : list(), + 'command_table' : list(), + 'assignment_table' : dict()} + if not self.__dict__.has_key("__resolve_import"): + self.__resolve_import = self.__resolve_runtime_import if self._module.definition.is_leaf \ + else self.__resolve_pcl_import @multimethod(Import) def visit(self, an_import): # Hu rah, we're about to import something. Very exciting import_symbol_dict = an_import.module.resolution_symbols['imports'] + # Add, only uniquely aliased, Python modules to symbol table if import_symbol_dict.has_key(an_import.alias): self._add_errors("ERROR: %(filename)s at line %(lineno)d, duplicate import alias found %(alias)s", @@ -216,89 +225,8 @@ class FirstPassResolverVisitor(ResolverVisitor): 'lineno' : i.lineno, 'alias' : i.alias}) else: - # Import the Python module - module_spec = {'module_name_id' : an_import.module_name} - imported_module = None - try: - imported_module = __import__(str(an_import.module_name), - fromlist = ['get_inputs', - 'get_outputs', - 'get_configuration', - 'configure', - 'initialise']) - except Exception as ie: - self._add_errors("ERROR: %(filename)s at line %(lineno)d, error importing " \ - "module %(module_name)s: %(exception)s", - [an_import], - lambda i: {'filename' : i.filename, - 'lineno' : i.lineno, - 'module_name' : i.module_name, - 'exception' : str(ie)}) - - # Default, or dummy, functions - get_inputs_fn = lambda : [] - get_outputs_fn = lambda : [] - get_configuration_fn = lambda : [] - - # Was the module imported? - if imported_module: - # Yes! - try: - get_inputs_fn = getattr(imported_module, 'get_inputs') - except AttributeError: - self._add_errors("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ - "does not define get_inputs function", - [an_import], - lambda i: {'filename' : i.filename, - 'lineno' : i.lineno, - 'module' : i.module_name}) - try: - get_outputs_fn = getattr(imported_module, 'get_outputs') - except AttributeError: - self._add_errors("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ - "does not define get_outputs function", - [an_import], - lambda i: {'filename' : i.filename, - 'lineno' : i.lineno, - 'module' : i.module_name}) - try: - get_configuration_fn = getattr(imported_module, 'get_configuration') - except AttributeError: - self._add_errors("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ - "does not define get_configuration function", - [an_import], - lambda i: {'filename' : i.filename, - 'lineno' : i.lineno, - 'module' : i.module_name}) - try: - configure_fn = getattr(imported_module, 'configure') - except AttributeError: - self._errors.append("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ - "does not define configure function", - [an_import], - lambda i: {'filename' : i.filename, - 'lineno' : i.lineno, - 'module' : i.module_name}) - try: - initialise_fn = getattr(imported_module, 'initialise') - except AttributeError: - self._errors.append("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ - "does not define initialise function", - [an_import], - lambda i: {'filename' : i.filename, - 'lineno' : i.lineno, - 'module' : i.module_name}) - - # Record stuff from the imported Python module - module_spec.update({'module' : imported_module, - 'get_inputs_fn' : get_inputs_fn, - 'get_outputs_fn' : get_outputs_fn, - 'get_configuration_fn' : get_configuration_fn}) - else: - # Record a dummy module - module_spec.update({'get_inputs_fn' : get_inputs_fn, - 'get_outputs_fn' : get_outputs_fn, - 'get_configuration_fn' : get_configuration_fn}) + # Resolve the import + module_spec = self.__resolve_import(an_import) # Always add the module alias as a key to the import dictionary import_symbol_dict[an_import.alias] = module_spec @@ -306,6 +234,116 @@ class FirstPassResolverVisitor(ResolverVisitor): # Mark the import as not used for now ;) self._module.resolution_symbols['used_imports'][an_import.alias] = (an_import, False) + def __resolve_runtime_import(self, an_import): + imported_module = None + try: + imported_module = __import__(str(an_import.module_name), globals(), locals(), ['*'], -1) + except Exception as ie: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, error importing " \ + "module %(module_name)s: %(exception)s", + [an_import], + lambda i: {'filename' : i.filename, + 'lineno' : i.lineno, + 'module_name' : i.module_name, + 'exception' : str(ie)}) + + 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 k, v in funcs_and_specs: + module_spec[k] = v + + return module_spec + + def __resolve_pcl_import(self, an_import): + # Import the Python module + module_spec = {'module_name_id' : an_import.module_name} + imported_module = None + try: + imported_module = __import__(str(an_import.module_name), + fromlist = ['get_inputs', + 'get_outputs', + 'get_configuration', + 'configure', + 'initialise']) + except Exception as ie: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, error importing " \ + "module %(module_name)s: %(exception)s", + [an_import], + lambda i: {'filename' : i.filename, + 'lineno' : i.lineno, + 'module_name' : i.module_name, + 'exception' : str(ie)}) + + # Default, or dummy, functions + get_inputs_fn = lambda : [] + get_outputs_fn = lambda : [] + get_configuration_fn = lambda : [] + + # Was the module imported? + if imported_module: + # Yes! + try: + get_inputs_fn = getattr(imported_module, 'get_inputs') + except AttributeError: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ + "does not define get_inputs function", + [an_import], + lambda i: {'filename' : i.filename, + 'lineno' : i.lineno, + 'module' : i.module_name}) + try: + get_outputs_fn = getattr(imported_module, 'get_outputs') + except AttributeError: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ + "does not define get_outputs function", + [an_import], + lambda i: {'filename' : i.filename, + 'lineno' : i.lineno, + 'module' : i.module_name}) + try: + get_configuration_fn = getattr(imported_module, 'get_configuration') + except AttributeError: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ + "does not define get_configuration function", + [an_import], + lambda i: {'filename' : i.filename, + 'lineno' : i.lineno, + 'module' : i.module_name}) + try: + configure_fn = getattr(imported_module, 'configure') + except AttributeError: + self._errors.append("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ + "does not define configure function", + [an_import], + lambda i: {'filename' : i.filename, + 'lineno' : i.lineno, + 'module' : i.module_name}) + try: + initialise_fn = getattr(imported_module, 'initialise') + except AttributeError: + self._errors.append("ERROR: %(filename)s at line %(lineno)d, imported Python module %(module)s " \ + "does not define initialise function", + [an_import], + lambda i: {'filename' : i.filename, + 'lineno' : i.lineno, + 'module' : i.module_name}) + + # Record stuff from the imported Python module + module_spec.update({'module' : imported_module, + 'get_inputs_fn' : get_inputs_fn, + 'get_outputs_fn' : get_outputs_fn, + 'get_configuration_fn' : get_configuration_fn}) + else: + # Record a dummy module + module_spec.update({'get_inputs_fn' : get_inputs_fn, + 'get_outputs_fn' : get_outputs_fn, + 'get_configuration_fn' : get_configuration_fn}) + + return module_spec + @multimethod(Component) def visit(self, component): # Component name *must* be the same as the file name @@ -742,3 +780,131 @@ class FirstPassResolverVisitor(ResolverVisitor): iden_expr.resolution_symbols['inputs'] = transform_fn(inputs) iden_expr.resolution_symbols['outputs'] = transform_fn(outputs) + @multimethod(Function) + def visit(self, function): + package_alias, name = function.name.split(".") + + 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] + if not functions.has_key(name): + self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown function %(alias)s.%(name)s", + [function], + lambda f: {'filename' : f.filename, + 'lineno' : f.lineno, + 'alias' : package_alias, + 'name' : name}) + 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", + [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", + [function], + lambda f: {'filename' : f.filename, + 'lineno' : f.lineno, + 'arg_name' : argument}) + + # Mark the import as used + self._module.resolution_symbols['used_imports'][package_alias] = (self._module.resolution_symbols['used_imports'][package_alias][0], + True) + + @multimethod(Command) + def visit(self, command): + # 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", + [command], + lambda c: {'filename' : c.filename, + 'lineno' : c.lineno, + 'input' : c.identifier}) + + # Check the assigned variable is *not* an output. + if command.identifier in self._module.definition.outputs: + self._add_errors("ERROR: %(filename)s at line %(lineno)d, attempt to write output %(output)s outside a return", + [command], + lambda c: {'filename' : c.filename, + 'lineno' : c.lineno, + 'output' : c.identifier}) + + # Check if assignment variable has been used already. Code generation may not + # 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], + 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 + + @multimethod(Return) + def visit(self, ret): + # Duplicate 'to' maps + duplicate_to = dict() + missing_froms = list() + + # Check the mapping contains all the outputs and that assignments exist + for mapping in ret.mappings: + # Duplicate to? + if mapping.to in duplicate_to: + duplicate_to[mapping.to] += 1 + else: + duplicate_to[mapping.to] = 1 + + # Does the 'from' exist? + if mapping.from_ not in self._module.resolution_symbols['assignment_table']: + missing_froms.append(mapping.from_) + + # Record duplicate 'to' maps + self._add_errors("ERROR: %(filename)s at line %(lineno)d, duplicate output in return %(duplicate)s", + [duplicate_to[key] for key in filter(lambda k: duplicate_to[k] > 1, duplicate_to.keys())], + lambda t: {'filename' : t.filename, + 'lineno' : t.lineno, + 'duplicate' : str(t)}) + + # Missing 'to' maps + self._add_errors("ERROR: %(filename)s at line %(lineno)d, missing output in return %(missing)s", + frozenset(self._module.definition.outputs) - frozenset(duplicate_to.keys()), + lambda t: {'filename' : t.filename, + 'lineno' : t.lineno, + 'missing' : str(t)}) + + # Missing 'from' maps + self._add_errors("ERROR: %(filename)s at line %(lineno)d, unknown variable in return %(unknown)s", + missing_froms, + lambda m: {'filename' : m.filename, + 'lineno' : m.lineno, + 'unknown' : str(m)}) + + @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 e186d58..21e4f9c 100644 --- a/src/pclc/visitors/third_pass_resolver_visitor.py +++ b/src/pclc/visitors/third_pass_resolver_visitor.py @@ -52,9 +52,9 @@ class ThirdPassResolverVisitor(SecondPassResolverVisitor): # Root expression, so get inputs and outpus # from module defined inputs and outputs clauses expr = self._module.definition.definition + # Place the input/output specifications in a Maybe monad expected_fn = lambda e: Just((frozenset(e[0]), frozenset(e[1]))) \ if isinstance(e, tuple) else Just(frozenset(e)) - self._module.resolution_symbols['inputs'] = expected_fn(self._module.definition.inputs) >= \ (lambda expected_inputs: expr.resolution_symbols['inputs'] >= \ (lambda actual_inputs: self._add_errors("ERROR: %(filename)s at line %(lineno)d, component " \ diff --git a/src/runtime/pcl/io/__init__.py b/src/runtime/pcl/io/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/runtime/pcl/io/__init__.py diff --git a/src/runtime/pcl/io/file.py b/src/runtime/pcl/io/file.py new file mode 100644 index 0000000..eb56f98 --- /dev/null +++ b/src/runtime/pcl/io/file.py @@ -0,0 +1,25 @@ +# +# Copyright Capita Translation and Interpreting 2013 +# +# This file is part of Pipeline Creation Language (PCL). +# +# Pipeline Creation Language (PCL) 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 Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pipeline Creation Language (PCL) is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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/>. +# + + +# openForRead :: String -> file +openFile = lambda fn, flags: open(fn, flags) + +# closeFile :: file -> () +closeFile = lambda f: f.close() diff --git a/src/runtime/pcl/os/__init__.py b/src/runtime/pcl/os/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/runtime/pcl/os/__init__.py diff --git a/src/runtime/pcl/os/path.py b/src/runtime/pcl/os/path.py new file mode 100644 index 0000000..b5a9349 --- /dev/null +++ b/src/runtime/pcl/os/path.py @@ -0,0 +1,28 @@ +# +# Copyright Capita Translation and Interpreting 2013 +# +# This file is part of Pipeline Creation Language (PCL). +# +# Pipeline Creation Language (PCL) 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 Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pipeline Creation Language (PCL) is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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/>. +# +import os + +# exists :: String -> Bool +exists = lambda p: os.path.exists(p) + +# join :: String -> String -> String +join = lambda *args: os.path.join(*args) + +# mkdir :: String -> () +mkdir = lambda p: os.mkdir(p) diff --git a/src/runtime/pcl/util/list.py b/src/runtime/pcl/util/list.py index 242439e..baaa9c1 100644 --- a/src/runtime/pcl/util/list.py +++ b/src/runtime/pcl/util/list.py @@ -21,7 +21,7 @@ cons = lambda : list() # concat :: [*] -> [*] -> [*] -concat :: lambda l, m: l + m +concat = lambda l, m: l + m # append :: [a] -> a -> a append = lambda l, x: l.append(x) or x |