[Reproducible-commits] [reprotest] 03/04: First version of the mini shell AST library
Ceridwen
ceridwen-guest at moszumanska.debian.org
Wed Jul 13 21:26:39 UTC 2016
This is an automated email from the git hooks/post-receive script.
ceridwen-guest pushed a commit to branch virtualization
in repository reprotest.
commit b8c30d0a456459e50a16f9e0f5ae0957e3fdf610
Author: Ceridwen <ceridwenv at gmail.com>
Date: Tue Jul 12 15:28:07 2016 -0400
First version of the mini shell AST library
---
reprotest/_shell_ast.py | 386 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 386 insertions(+)
diff --git a/reprotest/_shell_ast.py b/reprotest/_shell_ast.py
new file mode 100644
index 0000000..197b160
--- /dev/null
+++ b/reprotest/_shell_ast.py
@@ -0,0 +1,386 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/gpl-3.0.en.html
+# For details: reprotest/debian/copyright
+'''This is partial implementation of an abstract syntax tree (AST) for
+the POSIX shell command language from the grammar at
+http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_10
+. It's used exclusively for generating shell scripts from ASTs, which
+means that it doesn't include a parser, and only implements the
+functionality needed for reprotest's shell scripts, which means that
+it doesn't include any superfluous features or alternatives that
+construct the same semantics.
+
+The nodes are classes. Nodes that correspond directly to rules in
+POSIX standard's grammar are named using camel-case conversions of the
+names in the grammar (e.g. simple_command becomes SimpleCommand).
+Each docstring should contain the right-hand side of the grammar
+definition for the corresponding node, if any. Otherwise, the
+docstring should contain describe which grammar rules the node
+represents and what other types of nodes it's allowed to contain. All
+nodes should contain only other nodes and strings. Empty fields are
+denoted by the empty string. All nodes must have __slots__ set to the
+empty tuple. Each class has only one overloaded method, __str__,
+which should transform the AST into valid shell code.
+'''
+
+import collections
+import shlex
+
+
+class BaseNode:
+ '''Abstract base class for all nodes. This class should never be
+ instantiated.'''
+ __slots__ = ()
+
+ def __str__(self):
+ '''A generic implementation of __str__ that returns the node's fields
+ separated by spaces.'''
+ return ' '.join(str(field) for field in self)
+
+
+class Command(BaseNode):
+ '''Abstract base class for command nodes. This class exists to define
+ a type that other classes can refer to to show where any command
+ is allowed, and should never be instantiated.
+
+ Grammar rules:
+
+ command: simple_command | compound_command | compound_command
+ redirect_list | function_definition;
+
+ compound_command: brace_group | subshell | for_clause | case_clause
+ | if_clause | while_clause | until_clause;
+
+ '''
+ __slots__ = ()
+
+
+class List(BaseNode, tuple):
+ '''The recursion in this rule is flatted into a sequence.
+ separator_op is a & or ;.
+
+ Grammar rules:
+
+ list: list separator_op and_or | and_or;
+
+ compound_list: term | newline_list term | term separator |
+ newline_list term separator;
+
+ newline_list: NEWLINE | newline_list NEWLINE;
+
+ separator: separator_op linebreak | newline_list;
+
+ linebreak: newline_list | /* empty */
+
+ Attributes:
+ *args (Sequence[Term]): A sequence of commands terminated by & or ;.
+
+ '''
+ __slots__ = ()
+
+
+class Term(BaseNode, collections.namedtuple('_Term', 'list separator')):
+ '''This rule is recursive in the grammar, but its direct recursion is
+ handled in List in this AST.
+
+ Grammar rule:
+
+ term separator and_or | and_or
+
+ Attributes:
+ list (AndList, OrList, Command): A command or sequence of commands.
+ separator (str): & or ;.
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return str(self.list) + ' ' + self.separator + '\n'
+
+
+class AndList(BaseNode, tuple):
+ '''While the && and || operators are not associative with each other,
+ each is associative with itself, so this recursion can also be
+ flattened into a sequence.
+
+ Grammar rules:
+
+ list: list separator_op and_or | and_or;
+
+ and_or: pipeline | and_or AND_IF linebreak pipeline
+ | and_or OR_IF linebreak pipeline;
+
+ Attributes:
+ *args (Sequence[Pipelines, Commands]): A sequence of commands and/or
+ pipelines.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return ' && '.join(str(field) for field in self)
+
+
+class OrList(BaseNode, tuple):
+ '''While the && and || operators are not associative with each other,
+ each is associative with itself, so this recursion can also be
+ flattened into a sequence.
+
+ Grammar rules:
+
+ list: list separator_op and_or | and_or;
+
+ and_or: pipeline | and_or AND_IF linebreak pipeline
+ | and_or OR_IF linebreak pipeline;
+
+ Attributes:
+ *args (Sequence[Pipelines, Commands]): A sequence of commands and/or
+ pipelines.
+
+ '''
+ def __str__(self):
+ return ' || '.join(str(field) for field in self)
+
+
+class Pipeline(BaseNode, tuple):
+ '''The recursion in this rule is flatted into a sequence. The option
+ to prepend the bang (!) to a pipeline is deliberately omitted. It
+ would require another class because the __str__ method would be
+ different.
+
+ Grammar rules:
+
+ pipeline: pipe_sequence | Bang pipe_sequence;
+
+ pipe_sequence: command | pipe_sequence '|' linebreak command;
+
+ Attributes:
+ *args (Sequence[Command]): commands to be piped together.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return ' | '.join(str(field) for field in self)
+
+
+class SimpleCommand(Command,
+ collections.namedtuple('_SimpleCommand',
+ 'cmd_prefix cmd_name cmd_suffix')):
+ '''The rule distinguishes between command names prefixed with
+ redirection or environment variables by using cmd_word instead of
+ cmd_name, but this difference is immaterial when generating shell
+ code from an AST. cmd_name and cmd_word are just WORDs.
+
+ Grammar rule:
+
+ cmd_prefix cmd_word cmd_suffix | cmd_prefix cmd_word | cmd_prefix |
+ cmd_name cmd_suffix | cmd_name;
+
+ Attributes:
+ cmd_prefix (CmdPrefix, ''): Environment variables and IO redirection.
+ cmd_name (str): A valid shell command name.
+ cmd_suffix (CmdSuffix, ''): Command arguments and IO redirection.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return ((str(self.cmd_prefix) + ' ' if self.cmd_prefix else '') +
+ str(self.cmd_name) +
+ (' ' + str(self.cmd_suffix) if self.cmd_suffix else ''))
+
+class CmdPrefix(BaseNode, tuple):
+ '''The recursion in this rule is flatted into a sequence.
+
+ Grammar rule:
+
+ io_redirect | cmd_prefix io_redirect | ASSIGNMENT_WORD | cmd_prefix
+ ASSIGNMENT_WORD;
+
+ Attributes:
+ *args (AssignmentWord, IORedirect): IO redirection and
+ environment variables.
+ '''
+ __slots__ = ()
+
+
+class AssignmentWord(BaseNode,
+ collections.namedtuple('_AssignmentWord', 'target value')):
+ '''Corresponds to environment variable assignments of the form
+ target=value.
+
+ Attributes:
+ target (str): Environment variable name.
+ value (str): Environment variable value.
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return str(self.target) + '=' + str(self.value)
+
+
+class IORedirect(BaseNode,
+ collections.namedtuple('_IORedirect',
+ 'io_number operator filename')):
+ '''This represents a redirection and combines three rules. here_end
+ is just a WORD.
+
+ Grammar rules:
+
+ io_redirect: io_file | IO_NUMBER io_file | io_here | IO_NUMBER io_here;
+
+ io_file: '<' filename | LESSAND filename | '>' filename | GREATAND filename
+ | DGREAT filename | LESSGREAT filename | CLOBBER filename;
+
+ io_here: DLESS here_end | DLESSDASH here_end;
+
+ Attributes:
+ io_number (int, ''): A file descriptor. This should hold the empty
+ string if omitted.
+ operator (str): One of >, <, <<, >>, <&, >&, <>, <<-, or >|.
+ filename (str): Valid file name to redirect to.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return (str(self.io_number) + str(self.operator) + ' ' +
+ str(self.filename))
+
+
+class CmdSuffix(BaseNode, tuple):
+ ''''The recursion in this rule is flatted into a sequence. This node
+ represents the arguments passed to a simple command.
+
+ Grammar rule:
+
+ io_redirect | cmd_suffix io_redirect | WORD | cmd_suffix WORD
+
+ Attributes:
+ *args (str, IORedirect): command arguments and IO redirection.
+
+ '''
+ __slots__ = ()
+
+
+class IfClause(Command,
+ collections.namedtuple('_IfClause', 'condition then else_part')):
+ '''The start of an if-then conditional clause.
+
+ Grammar rule:
+
+ If compound_list Then compound_list else_part Fi
+
+ Attributes:
+ condition (List, Command): The command whose exit status determines
+ which branch is taken.
+ then (List, Command): The command to execute if condition exits with
+ zero.
+ else_part (ElsePart, ''): The optional command to execute if
+ condition exits with a nonzero value.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return ('if ' + str(self.condition) + '\nthen ' + str(self.then) +
+ '\n' + str(self.else_part) + '\nfi')
+
+
+class ElsePart(BaseNode, collections.namedtuple('_IfClause', 'elifs then')):
+ '''The elif and else parts of an if-then conditional clause. The rule
+ corresponding to this node is recursive, but the recursion is
+ handled in Elifs.
+
+ Grammar rule:
+
+ Elif compound_list Then compound_list | Elif compound_list Then
+ compound_list else_part | Else compound_list
+
+ Attributes:
+ elifs (Elifs, ''): The optional conditions and commands to execute if
+ the preceding command exits with nonzero status.
+ then (List, Command, ''): The optional command to execute if
+ all other conditionals exit with nonzero statuses.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return (str(self.elifs) +
+ ('\nelse ' + str(self.then)) if self.then else '')
+
+class Elifs(BaseNode, tuple):
+ '''This node doesn't directly correspond to a grammar rule. It
+ flattens the recursion for elif statements in else_part.
+
+ Grammar rule:
+
+ Elif compound_list Then compound_list | Elif compound_list Then
+ compound_list else_part | Else compound_list
+
+ Attributes:
+ *args (Sequence[Elif]): A sequence of elif statements.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return '\n'.join(str(field) for field in self)
+
+
+class Elif(BaseNode, collections.namedtuple('_Elif', 'condition then')):
+ '''This node also doesn't directly correspond to a grammar rule
+
+ Grammar rule:
+
+ Elif compound_list Then compound_list | Elif compound_list Then
+ compound_list else_part | Else compound_list
+
+ Attributes:
+ condition (List, Command): The command whose exit status determines
+ which branch is taken.
+ then (List, Command): The command to execute if condition exits with
+ zero.
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return 'elif ' + str(self.condition) + '\nthen ' + str(self.then)
+
+
+class BraceGroup(Command, collections.namedtuple('_BraceGroup', 'list')):
+ '''Grammar rule:
+
+ Lbrace compound_list Rbrace'''
+ __slots__ = ()
+
+ def __str__(self):
+ return '{ ' + str(self.list) + ' }'
+
+
+class Subshell(Command, collections.namedtuple('_BraceGroup', 'list')):
+ '''Grammar rule:
+
+ '(' compound_list ')'
+
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return '( ' + str(self.list) + ' )'
+
+
+class Quote(BaseNode, collections.namedtuple('_Quote', 'command')):
+ '''This is a special node that allows nesting of commands using shell
+ quoting. For example, to pass a script to a specific shell:
+
+ SimpleCommand('', 'bash', CmdSuffix([Quote(<script>)]))
+
+ Attributes:
+ command (List, Command): AST to quote.
+ '''
+ __slots__ = ()
+
+ def __str__(self):
+ return shlex.quote(str(self.command))
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/reprotest.git
More information about the Reproducible-commits
mailing list