Permalink
Browse files

Initial implementation of 'oilc deps'.

It finds all the commands that a shell script uses, taking into account
builtins and functions.  (The function logic needs a separate pass.)

- Fill out a skeleton for the 'oilc' applet.
- Prototype a visitor style which uses ASDL metaprogramming.

Unrelated:

- Some edits to the quick reference.
- enable asdl/arith_parse_test.py
  • Loading branch information...
Andy Chu
Andy Chu committed Feb 8, 2018
1 parent 197ca84 commit 537b49d1f97e1827e3649286f9568d59330c3417
Showing with 334 additions and 17 deletions.
  1. +51 −2 asdl/arith_parse_test.py
  2. +18 −1 asdl/gen_cpp.py
  3. +67 −9 bin/oil.py
  4. +2 −1 doc/osh-quick-ref-toc.txt
  5. +64 −0 test/oilc.sh
  6. +0 −4 test/unit.sh
  7. +132 −0 tools/deps.py
View
@@ -1,6 +1,7 @@
#!/usr/bin/env python
from asdl import tdop
from asdl import arith_parse
from asdl import arith_ast
from asdl import arith_parse # module under test
def _assertParseError(make_parser, s, error_substring=''):
@@ -22,6 +23,7 @@ def TestArith(t_parse):
t_parse('1+2*3', '(+ 1 (* 2 3))')
t_parse('4*(2+3)', '(* 4 (+ 2 3))')
t_parse('(2+3)*4', '(* (+ 2 3) 4)')
return
t_parse('1<2', '(< 1 2)')
t_parse('x=3', '(= x 3)')
t_parse('x = 2*3', '(= x (* 2 3))')
@@ -155,11 +157,58 @@ def TestErrors(p):
_assertParseError(p, '1 [ 2 ]', "can't be indexed")
arith_expr_e = arith_ast.arith_expr_e
#source_location = arith_ast.source_location
#op_id_e = arith_ast.op_id_e
class Visitor(object):
def __init__(self):
pass
# In Python, they do introspection on method names.
# method = 'visit_' + node.__class__.__name__
# I'm not going to bother, because I have ASDL! I want the generic visitor.
def Visit(self, node):
raise NotImplementedError
# Like ast.NodeVisitor().generic_visit!
def VisitChildren(self, node):
#print dir(node)
# TODO: Use node.ASDL_TYPE.GetFields()
# Only compound children get visited?
print [name for name in dir(node) if not name.startswith('_')]
# Call self.Visit()!
class PrettyPrinter(Visitor):
def Visit(self, node):
if node.tag == arith_expr_e.ArithUnary:
print 'ArithUnary %s' % node.child
else:
self.VisitChildren(node)
def t_parse(s, expected=None):
p = arith_parse.MakeParser(s)
tree = p.Parse()
print(tree)
#v = PrettyPrinter()
#v.Visit(tree)
#print('%-40s %s' % (s, sexpr))
return tree
def main():
t_parse = arith_parse.ParseShell
p = arith_parse.MakeParser
TestArith(t_parse)
return
TestBitwise(t_parse)
TestLogical(t_parse)
TestUnary(t_parse)
View
@@ -79,7 +79,12 @@ def ReflowLines(s, depth):
def FormatLines(s, depth, reflow=True):
"""Format lines."""
"""Make the generated code readable.
Args:
depth: controls indentation
reflow: line wrapping.
"""
if reflow:
lines = ReflowLines(s, depth)
else:
@@ -109,6 +114,14 @@ def VisitModule(self, module):
}
class AsdlVisitor:
"""Base class for visitors.
TODO:
- It might be useful to separate this into VisitChildren() / generic_visit()
like Python's ast.NodeVisitor does.
- Also remove self.f and self.Emit. Those can go in self.output?
- Move to common location, since gen_python uses it as well.
"""
def __init__(self, f):
self.f = f
@@ -227,6 +240,8 @@ def Emit(s, depth=depth):
Emit("};")
Emit("")
# TODO: This should be replaced with a call to the generic
# self.VisitChildren()
super_name = "%s_t" % name
for t in sum.types:
self.VisitConstructor(t, super_name, depth)
@@ -403,6 +418,8 @@ def main(argv):
# debugging. Might need to detect cycles though.
if action == 'cpp':
schema_path = argv[2]
# TODO: Use asdl.LoadSchema here.
with open(schema_path) as input_f:
module = asdl.parse(input_f)
View
@@ -62,7 +62,6 @@ def _tlog(msg):
from osh import word_parse # for tracing
from osh import cmd_parse # for tracing
from osh.meta import ast
from osh import ast_lib
from osh import parse_lib
@@ -392,7 +391,7 @@ def OshMain(argv, login_shell):
else:
raise AssertionError
abbrev_hook = (
ast.AbbreviateNodes if 'abbrev-' in opts.ast_format else None)
ast_lib.AbbreviateNodes if 'abbrev-' in opts.ast_format else None)
tree = fmt.MakeTree(node, abbrev_hook=abbrev_hook)
ast_f.FileHeader()
fmt.PrintTree(tree, ast_f)
@@ -460,25 +459,84 @@ def BoilMain(main_argv):
# TODO: Hook up to completion.
SUBCOMMANDS = ['translate', 'analyze-bin']
SUBCOMMANDS = ['translate', 'format', 'deps', 'undefined-vars']
def OilCommandMain(main_argv):
def OilCommandMain(argv):
"""Run an 'oilc' tool.
'oilc' is short for "oil compiler" or "oil command".
TODO:
- oilc --help
oilc deps
--path: the $PATH to use to find executables. What about libraries?
NOTE: we're leaving out su -c, find, xargs, etc.? Those should generally
run functions using the $0 pattern.
--chained-command sudo
"""
try:
action = main_argv[0]
action = argv[0]
except IndexError:
raise args.UsageError('oilc: Missing required subcommand.')
log('action %s', action)
# NOTE: Does every oilc subcommand take a source path? For now we assume it.
# TODO: fall back to stdin
try:
source_path = argv[1]
except IndexError:
raise args.UsageError('oilc: Missing required source path.')
pool = alloc.Pool()
arena = pool.NewArena()
arena.PushSource(source_path)
with open(source_path) as f:
line_reader = reader.FileLineReader(f, arena)
_, c_parser = parse_lib.MakeParser(line_reader, arena)
try:
node = c_parser.ParseWholeFile()
except util.ParseError as e:
ui.PrettyPrintError(e, arena, sys.stderr)
print('parse error: %s' % e.UserErrorString(), file=sys.stderr)
return 2
else:
# TODO: Remove this older form of error handling.
if not node:
err = c_parser.Error()
assert err, err # can't be empty
ui.PrintErrorStack(err, arena, sys.stderr)
return 2 # parse error is code 2
# Columns for list-*
# path line name
# where name is the binary path, variable name, or library path.
# bin-deps and lib-deps can be used to make an app bundle.
# Maybe I should list them together? 'deps' can show 4 columns?
#
# path, line, type, name
#
# --pretty can show the LST location.
# stderr: show how we're following imports?
from tools import deps
if action == 'translate':
# TODO: osh2oil
# TODO: osh2oil. Remove opts.fix
pass
elif action == 'analyze-bin':
# TODO: tools/analyze.py
elif action == 'format':
# TODO: autoformat code
raise NotImplementedError(action)
elif action == 'deps':
deps.Deps(node)
elif action == 'undefined-vars': # could be environment variables
pass
else:
@@ -158,7 +158,8 @@ X [xargs] each
OIL LIBRARIES
X [Builtin Procs] log die
X [Builtin Funcs] shEvalArith() shEvalWord() strftime()
X [Builtin Funcs] join() split() strftime()
shEvalArith() shEvalWord()
X [getopts] ?
X [Testing] ?
X [Data Formats] json csv tsv2
View
@@ -0,0 +1,64 @@
#!/bin/bash
#
# Usage:
# ./oilc.sh <function name>
set -o nounset
set -o pipefail
set -o errexit
# TODO: We need a common test framework for command-line syntax of bin/*. The
# spec tests are doing that now with $SH.
# osh2oil should be oilc translate.
fail() {
echo 'TEST FAILED'
exit 1
}
usage() {
set +o errexit
# missing required subcommand
bin/oilc
test $? -eq 2 || fail
bin/oilc invalid
test $? -eq 2 || fail
bin/oilc bin-deps
test $? -eq 2 || fail
return
# Doesn't work yet
echo --
bin/oilc --help
test $? -eq 0 || fail
set -o errexit
}
deps() {
bin/oilc deps $0
test $? -eq 0 || fail
}
readonly -a PASSING=(
usage
deps
)
all-passing() {
for t in "${PASSING[@]}"; do
# fail calls 'exit 1'
$t
echo "OK $t"
done
echo
echo "All osh2oil tests passed."
}
"$@"
View
@@ -45,10 +45,6 @@ banner() {
tests-to-run() {
# TODO: Add opy.
for t in {build,test,native,asdl,core,osh,test,tools}/*_test.py; do
# This test doesn't pass because it uses strings. Maybe remove it.
if [[ $t == asdl/arith_parse_test.py ]]; then
continue
fi
echo $t
done
}
Oops, something went wrong.

0 comments on commit 537b49d

Please sign in to comment.