View
@@ -35,10 +35,9 @@
import time
import traceback
from osh.meta import ast
from osh.meta import ast, runtime
from osh import parse_lib
from core import alloc
from osh.meta import runtime
from core import state
from core import ui
from core import util
@@ -47,6 +46,7 @@
command_e = ast.command_e
value_e = runtime.value_e
completion_state_e = runtime.completion_state_e
log = util.log
@@ -411,19 +411,6 @@ def _FindLastSimpleCommand(node):
return _FindLastSimpleCommand(node.children[-1])
ECompletionType = util.Enum('ECompletionState',
'NONE FIRST REST VAR_NAME HASH_KEY REDIR_FILENAME'.split())
# REDIR_FILENAME: stdin only
# HASH_KEY: do this later
# NONE: nothing detected, should we default to filenames/directories?
# Note: this could also be a CompRequest
# CompRequest(FIRST, prefix)
# CompRequest(REST, prefix, comp_words) # use the full contact
# CompRequest(VAR_NAME, prefix)
# CompRequest(HASH_KEY, prefix, hash_name) # use var name
# CompRequest(REDIR_FILENAME, prefix)
# Or it could just be a Completer / Chain?
# CommandCompleter
@@ -438,7 +425,7 @@ def _FindLastSimpleCommand(node):
def _GetCompletionType(w_parser, c_parser, ev, status_out):
"""
Parser returns completion state.
Then we translate that into ECompletionType.
Then we translate that into completion_state_e.
Returns:
comp_type
@@ -516,7 +503,7 @@ def _GetCompletionType(w_parser, c_parser, ev, status_out):
status_out.Write(6, 'com_node: %s', repr(com_node) if com_node else '<None>')
# TODO: Fill these in
comp_type = ECompletionType.FIRST
comp_type = completion_state_e.FIRST
prefix = ''
words = []
@@ -568,18 +555,18 @@ def _GetCompletionType1(parser, buf):
# state. And also we didn't see $${, which would be a special var. Oil
# rules are almost the same.
if n > 0 and words[-1].startswith('$'):
comp_type = ECompletionType.VAR_NAME
comp_type = completion_state_e.VAR_NAME
prefix = words[-1]
# Otherwise complete words
elif n == 0:
comp_type = ECompletionType.FIRST
comp_type = completion_state_e.FIRST
prefix = ''
elif n == 1:
comp_type = ECompletionType.FIRST
comp_type = completion_state_e.FIRST
prefix = words[-1]
else:
comp_type = ECompletionType.REST
comp_type = completion_state_e.REST
prefix = words[-1]
comp_index = len(words) - 1
@@ -609,22 +596,22 @@ def Matches(self, buf, status_out):
# TODO: I don't get bash -D vs -E. Might need to write a test program.
if comp_type == ECompletionType.VAR_NAME:
if comp_type == completion_state_e.VAR_NAME:
# Non-user chain
chain = self.var_comp
elif comp_type == ECompletionType.HASH_KEY:
elif comp_type == completion_state_e.HASH_KEY:
# Non-user chain
chain = 'TODO'
elif comp_type == ECompletionType.REDIR_FILENAME:
elif comp_type == completion_state_e.REDIR_FILENAME:
# Non-user chain
chain = 'TODO'
elif comp_type == ECompletionType.FIRST:
elif comp_type == completion_state_e.FIRST:
chain = self.comp_lookup.GetFirstCompleter()
elif comp_type == ECompletionType.REST:
elif comp_type == completion_state_e.REST:
chain = self.comp_lookup.GetCompleterForName(comp_words[0])
elif comp_type == ECompletionType.NONE:
elif comp_type == completion_state_e.NONE:
# Null chain? No completion? For example,
# ${a:- <TAB> -- we have no idea what to put here
chain = 'TODO'
View
@@ -17,7 +17,7 @@
except ImportError:
from benchmarks import fake_libc as libc
from osh.meta import BOOL_OPS, Id, types
from osh.meta import BOOL_ARG_TYPES, Id, types
from core import util
from osh.meta import runtime
@@ -480,7 +480,7 @@ def Eval(self, node):
s = self._EvalCompoundWord(node.child)
# Now dispatch on arg type
arg_type = BOOL_OPS[op_id] # could be static in the LST?
arg_type = BOOL_ARG_TYPES[op_id] # could be static in the LST?
if arg_type == bool_arg_type_e.Path:
# Only use lstat if we're testing for a symlink.
@@ -550,7 +550,7 @@ def Eval(self, node):
s2 = self._EvalCompoundWord(node.right, do_fnmatch=do_fnmatch)
# Now dispatch on arg type
arg_type = BOOL_OPS[op_id]
arg_type = BOOL_ARG_TYPES[op_id]
if arg_type == bool_arg_type_e.Path:
st1 = os.stat(s1)
View
@@ -16,7 +16,7 @@
from osh.meta import (
Id, IdName, IdInstance,
Kind, LookupKind,
ID_SPEC, BOOL_OPS, _ID_NAMES, _kind_sizes)
ID_SPEC, BOOL_ARG_TYPES, _ID_NAMES, _kind_sizes)
from osh.meta import ast
@@ -94,7 +94,7 @@ def testPrintStats(self):
def PrintBoolTable():
for i, arg_type in BOOL_OPS.items():
for i, arg_type in BOOL_ARG_TYPES.items():
row = (IdName(i), arg_type)
print('\t'.join(str(c) for c in row))
View
@@ -21,6 +21,7 @@
from osh.meta import Id
redirect_e = runtime.redirect_e
process_state_e = runtime.process_state_e
e_die = util.e_die
log = util.log
@@ -352,12 +353,9 @@ def Run(self):
sys.exit(0) # Could this fail?
ProcessState = util.Enum('ProcessState', """Init Done""".split())
class Job(object):
def __init__(self):
self.state = ProcessState.Init
self.state = process_state_e.Init
def State(self):
return self.state
@@ -438,15 +436,15 @@ def WaitUntilDone(self, waiter):
#log('WAITING')
if not waiter.Wait():
break
if self.state == ProcessState.Done:
if self.state == process_state_e.Done:
break
return self.status
def WhenDone(self, pid, status):
#log('WhenDone %d %d', pid, status)
assert pid == self.pid, 'Expected %d, got %d' % (self.pid, pid)
self.status = status
self.state = ProcessState.Done
self.state = process_state_e.Done
if self.job_state:
self.job_state.WhenDone(pid)
@@ -525,7 +523,7 @@ def WaitUntilDone(self, waiter):
#log('WAIT pipeline')
if not waiter.Wait():
break
if self.state == ProcessState.Done:
if self.state == process_state_e.Done:
#log('Pipeline DONE')
break
@@ -543,7 +541,7 @@ def WhenDone(self, pid, status):
self.pipe_status[i] = status
if all(status != -1 for status in self.pipe_status):
self.status = self.pipe_status[-1] # last one
self.state = ProcessState.Done
self.state = process_state_e.Done
if self.job_state:
self.job_state.WhenDone(self.pipe_status[-1])
@@ -578,12 +576,12 @@ def IsDone(self, jid):
if jid not in self.jobs:
return False, False
job = self.jobs[jid]
return True, job.State() == ProcessState.Done
return True, job.State() == process_state_e.Done
def AllDone(self):
"""Test if all jobs are done. Used by 'wait' builtin."""
for job in self.jobs.itervalues():
if job.State() != ProcessState.Done:
if job.State() != process_state_e.Done:
return False
return True
View
@@ -54,4 +54,42 @@ module runtime
-- Word splitting in legacy.py
span = Black | Delim | Backslash
builtin =
NONE | READ | ECHO | SHIFT
| CD | PUSHD | POPD | DIRS
| EXPORT | UNSET | SET | SHOPT
| TRAP | UMASK
| SOURCE | DOT | EVAL | EXEC | WAIT | JOBS
| COMPLETE | COMPGEN | DEBUG_LINE
| TRUE | FALSE
| COLON
| TEST | BRACKET | GETOPTS
| COMMAND | TYPE | HELP
| DECLARE | TYPESET
-- word_eval.py: SliceParts is for ${a-} and ${a+}, Error is for ${a?}, and
-- SliceAndAssign is for ${a=}.
effect = SpliceParts | Error | SpliceAndAssign | NoOp
-- core/process.py
process_state = Init | Done
-- core/completion.py
-- NONE: nothing detected, should we default to filenames/directories?
-- REDIR_FILENAME: stdin only
-- HASH_KEY: do this later
-- Note: this could also be a CompRequest
-- CompRequest(FIRST, prefix)
-- CompRequest(REST, prefix, comp_words) # use the full contact
-- CompRequest(VAR_NAME, prefix)
-- CompRequest(HASH_KEY, prefix, hash_name) # use var name
-- CompRequest(REDIR_FILENAME, prefix)
completion_state =
NONE | FIRST | REST | VAR_NAME | HASH_KEY | REDIR_FILENAME
-- tools/osh2oil.py
word_style = Expr | Unquoted | DQ | SQ
}
View
@@ -138,58 +138,6 @@ def GetHomeDir():
return e.pw_dir
class _EnumValue(object):
"""A unique name."""
def __init__(self, namespace, name, value):
self.namespace = namespace
self.name = name
self.value = value
def __repr__(self):
return '<%s.%s %s>' % (self.namespace, self.name, self.value)
# I think this is not needed?
def __hash__(self):
# Needed for the LEXER_DEF dictionary
return hash(self.name)
# Why is this needed? For ASDL serialization? But we're not using it like
# that.
def __eq__(self, other):
if isinstance(other, int):
return self.value == other
elif isinstance(other, _EnumValue):
return self is other
else:
raise ValueError('%r is not comparable with %r' % (self, other))
class Enum(object):
def __init__(self, enum_name, spec):
self._values = []
self._lookup = {}
counter = 0
for item in spec:
if isinstance(item, tuple):
name, i = item
v = _EnumValue(enum_name, name, i)
counter = i + 1
else:
name = item
v = _EnumValue(enum_name, name, counter)
counter += 1
self._values.append(v)
self._lookup[name] = v
def __getattr__(self, name):
"""Get a value by name, e.g. Color.red."""
val = self._lookup.get(name)
if val is None:
raise AttributeError(name)
return val
# Mutate the class after defining it:
#
# http://stackoverflow.com/questions/3467526/attaching-a-decorator-to-all-functions-within-a-class
View
@@ -42,45 +42,5 @@ def testWrapMethods(self):
print(p.ParseCommandList({}))
class EnumTest(unittest.TestCase):
def testEnum(self):
Color = util.Enum('Color', 'red green blue'.split())
print(Color._values)
print(Color._lookup)
Color = util.Enum('Color', ['red', ('green', 3), 'blue'])
print(Color._values)
print(Color._lookup)
print(Color.red)
print(Color.green)
try:
print(Color.BAD)
except AttributeError as e:
self.assertEqual('BAD', e.args[0])
else:
self.fail("Expected error")
self.assertEqual(Color.red, Color.red)
self.assertNotEqual(Color.red, Color.green)
self.assertEqual(Color.red, 0)
self.assertEqual(Color.blue, 4)
try:
print(Color.blue == '')
except ValueError as e:
pass
else:
self.fail("Expected error")
d = {
Color.red: 'R',
Color.green: 'Blue',
Color.blue: 'B',
}
self.assertEqual('R', d[Color.red])
if __name__ == '__main__':
unittest.main()
View
@@ -18,10 +18,12 @@
part_value_e = runtime.part_value_e
value_e = runtime.value_e
effect_e = runtime.effect_e
bracket_op_e = ast.bracket_op_e
suffix_op_e = ast.suffix_op_e
word_part_e = ast.word_part_e
log = util.log
e_die = util.e_die
@@ -122,12 +124,6 @@ def _DecayPartValuesToString(part_vals, join_char):
return ''.join(out)
# SliceParts is for ${a-} and ${a+}, Error is for ${a?}, and SliceAndAssign is
# for ${a=}.
Effect = util.Enum('Effect', 'SpliceParts Error SpliceAndAssign NoOp'.split())
class _WordEvaluator:
"""Abstract base class for word evaluators.
@@ -219,7 +215,7 @@ def _EvalSpecialVar(self, op_id, quoted):
def _ApplyTestOp(self, val, op, quoted, part_vals):
"""
Returns:
assign_part_vals, Effect
assign_part_vals, effect_e
${a:-} returns part_value[]
${a:+} returns part_value[]
@@ -256,17 +252,17 @@ def _ApplyTestOp(self, val, op, quoted, part_vals):
if op.op_id in (Id.VTest_ColonHyphen, Id.VTest_Hyphen):
if is_falsey:
self._EvalWordToParts(op.arg_word, quoted, part_vals)
return None, Effect.SpliceParts
return None, effect_e.SpliceParts
else:
return None, Effect.NoOp
return None, effect_e.NoOp
elif op.op_id in (Id.VTest_ColonPlus, Id.VTest_Plus):
# Inverse of the above.
if is_falsey:
return None, Effect.NoOp
return None, effect_e.NoOp
else:
self._EvalWordToParts(op.arg_word, quoted, part_vals)
return None, Effect.SpliceParts
return None, effect_e.SpliceParts
elif op.op_id in (Id.VTest_ColonEquals, Id.VTest_Equals):
if is_falsey:
@@ -276,9 +272,9 @@ def _ApplyTestOp(self, val, op, quoted, part_vals):
# Append them to out param and return them.
part_vals.extend(assign_part_vals)
return assign_part_vals, Effect.SpliceAndAssign
return assign_part_vals, effect_e.SpliceAndAssign
else:
return None, Effect.NoOp
return None, effect_e.NoOp
elif op.op_id in (Id.VTest_ColonQMark, Id.VTest_QMark):
# TODO: Construct error
@@ -509,10 +505,10 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
# NOTE: Splicing part_values is necessary because of code like
# ${undef:-'a b' c 'd # e'}. Each part_value can have a different
# do_glob/do_elide setting.
if effect == Effect.SpliceParts:
if effect == effect_e.SpliceParts:
return # EARLY RETURN, part_vals mutated
elif effect == Effect.SpliceAndAssign:
elif effect == effect_e.SpliceAndAssign:
if var_name is None:
# TODO: error context
e_die("Can't assign to special variable")
@@ -524,7 +520,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
state.SetLocalString(self.mem, var_name, rhs_str)
return # EARLY RETURN, part_vals mutated
elif effect == Effect.Error:
elif effect == effect_e.Error:
raise NotImplementedError
else:
View
@@ -92,7 +92,7 @@ def IdInstance(i):
# Id -> bool_arg_type_e
BOOL_OPS = {} # type: dict
BOOL_ARG_TYPES = {} # type: dict
# Used by test_builtin.py
TEST_UNARY_LOOKUP = {}
@@ -106,7 +106,7 @@ def IdInstance(i):
ID_SPEC = id_kind.IdSpec(Id, Kind,
_ID_NAMES, _ID_INSTANCES, _ID_TO_KIND,
BOOL_OPS)
BOOL_ARG_TYPES)
id_kind.AddKinds(ID_SPEC)
id_kind.AddBoolKinds(ID_SPEC, Id, types.bool_arg_type_e) # must come second
@@ -188,7 +188,7 @@ def IdInstance(i):
redir_arg_type_e = types.redir_arg_type_e
REDIR_TYPE = {
REDIR_ARG_TYPES = {
# filename
Id.Redir_Less: redir_arg_type_e.Path,
Id.Redir_Great: redir_arg_type_e.Path,
View
@@ -15,7 +15,7 @@ filter-py() {
# Oil-only would exclude core/legacy.py, etc.
oil-osh-files() {
{ ls {bin,osh,core}/*.py native/*.c osh/osh.asdl core/runtime.asdl; } |
{ ls {bin,osh,core}/*.py native/*.c osh/{osh,types}.asdl core/runtime.asdl; } |
filter-py | grep -E -v '_gen.py$|test_lib.py'
}
View
@@ -7,12 +7,14 @@
import sys
from asdl import const
from core import util
from core import word
from osh.meta import Id
from asdl import const
from osh.meta import ast, Id
from _devbuild.gen import runtime_asdl
from osh.meta import ast
word_style_e = runtime_asdl.word_style_e
log = util.log
@@ -110,13 +112,6 @@ def PrintAsOil(arena, node, debug_spans):
# Or only at the front?
SPLIT, EXPR, UNQUOTED, DQ, SQ = range(5) # 5 modes of expression
# DQ: \$ \\ \"
# SQ: \\ \'
WordStyle = util.Enum('WordStyle', 'Expr Unquoted DQ SQ'.split())
# QEFS is wrong? Because RHS never gets split! It can always be foo=$1/foo.
# Not used because RHS not split:
# $x -> @-x and ${x} -> @-x
@@ -146,29 +141,29 @@ def _GetRhsStyle(w):
# ~andy -> homedir('andy')
# tilde()
# tilde('andy') ?
return WordStyle.Expr
return word_style_e.Expr
elif part0.tag in OTHER_SUBS:
return WordStyle.Unquoted
return word_style_e.Unquoted
elif part0.tag == word_part_e.DoubleQuotedPart:
if len(part0.parts) == 1:
dq_part0 = part0.parts[0]
# "$x" -> x and "${x}" -> x and "${x:-default}" -> x or 'default'
if dq_part0.tag in VAR_SUBS:
return WordStyle.Expr
return word_style_e.Expr
elif dq_part0.tag in OTHER_SUBS:
return WordStyle.Unquoted
return word_style_e.Unquoted
# Tilde subs also cause double quoted style.
for part in w.parts:
if part.tag == word_part_e.DoubleQuotedPart:
for dq_part in part.parts:
if dq_part.tag in ALL_SUBS:
return WordStyle.DQ
return word_style_e.DQ
elif part.tag in ALL_SUBS:
return WordStyle.DQ
return word_style_e.DQ
return WordStyle.SQ
return word_style_e.SQ
# TODO: Change to --assume, and have a default for each one?
@@ -822,11 +817,11 @@ def DoCommand(self, node, local_symbols, at_top_level=False):
def DoWordAsExpr(self, node, local_symbols):
style = _GetRhsStyle(node)
if style == WordStyle.SQ:
if style == word_style_e.SQ:
self.f.write("'")
self.DoWordInCommand(node, local_symbols)
self.f.write("'")
elif style == WordStyle.DQ:
elif style == word_style_e.DQ:
self.f.write('"')
self.DoWordInCommand(node, local_symbols)
self.f.write('"')
View
@@ -7,10 +7,13 @@
from core import word
from tools import osh2oil # module under test
WordStyle = osh2oil.WordStyle
from _devbuild.gen import runtime_asdl
from osh.word_parse_test import _assertReadWord
word_style_e = runtime_asdl.word_style_e
def assertStyle(test, expected_style, word_str):
w = _assertReadWord(test, word_str)
@@ -26,28 +29,28 @@ def assertStyle(test, expected_style, word_str):
class FixTest(unittest.TestCase):
def testGetRhsStyle(self):
w = assertStyle(self, WordStyle.SQ, 'foo')
w = assertStyle(self, WordStyle.SQ, "'hi'")
w = assertStyle(self, word_style_e.SQ, 'foo')
w = assertStyle(self, word_style_e.SQ, "'hi'")
w = assertStyle(self, WordStyle.Expr, '$var')
w = assertStyle(self, WordStyle.Expr, '${var}')
w = assertStyle(self, word_style_e.Expr, '$var')
w = assertStyle(self, word_style_e.Expr, '${var}')
w = assertStyle(self, WordStyle.Expr, ' "$var" ')
w = assertStyle(self, WordStyle.Expr, ' "${var}" ')
w = assertStyle(self, word_style_e.Expr, ' "$var" ')
w = assertStyle(self, word_style_e.Expr, ' "${var}" ')
w = assertStyle(self, WordStyle.Unquoted, ' $((1+2)) ')
w = assertStyle(self, WordStyle.Unquoted, ' $(echo hi) ')
w = assertStyle(self, word_style_e.Unquoted, ' $((1+2)) ')
w = assertStyle(self, word_style_e.Unquoted, ' $(echo hi) ')
w = assertStyle(self, WordStyle.Unquoted, ' "$((1+2))" ')
w = assertStyle(self, WordStyle.Unquoted, ' "$(echo hi)" ')
w = assertStyle(self, word_style_e.Unquoted, ' "$((1+2))" ')
w = assertStyle(self, word_style_e.Unquoted, ' "$(echo hi)" ')
w = assertStyle(self, WordStyle.DQ, ' $src/file ')
w = assertStyle(self, WordStyle.DQ, ' ${src}/file ')
w = assertStyle(self, word_style_e.DQ, ' $src/file ')
w = assertStyle(self, word_style_e.DQ, ' ${src}/file ')
w = assertStyle(self, WordStyle.DQ, ' "$src/file" ')
w = assertStyle(self, WordStyle.DQ, ' "${src}/file" ')
w = assertStyle(self, word_style_e.DQ, ' "$src/file" ')
w = assertStyle(self, word_style_e.DQ, ' "${src}/file" ')
w = assertStyle(self, WordStyle.DQ, ' $((1+2))$(echo hi) ')
w = assertStyle(self, word_style_e.DQ, ' $((1+2))$(echo hi) ')
# PROBLEM: How do you express it quoted?
# "$~/src"
@@ -66,13 +69,13 @@ def testGetRhsStyle(self):
w = assertStyle(self, WordStyle.DQ, ' ~/src ')
w = assertStyle(self, WordStyle.DQ, ' ~bob/foo ')
w = assertStyle(self, WordStyle.SQ, 'notleading~')
w = assertStyle(self, word_style_e.DQ, ' ~/src ')
w = assertStyle(self, word_style_e.DQ, ' ~bob/foo ')
w = assertStyle(self, word_style_e.SQ, 'notleading~')
# These tildes are quoted
w = assertStyle(self, WordStyle.SQ, ' "~/src" ')
w = assertStyle(self, WordStyle.SQ, ' "~bob/foo" ')
w = assertStyle(self, word_style_e.SQ, ' "~/src" ')
w = assertStyle(self, word_style_e.SQ, ' "~bob/foo" ')
if __name__ == '__main__':