Skip to content

Commit

Permalink
Plan out the parsing of a[x]=1.
Browse files Browse the repository at this point in the history
- Introduce a simpler word.IsVarLike() helper for lookahead.
- Simplify TildeDetect usage.

All unit and spec tests pass.
  • Loading branch information
Andy Chu committed Sep 15, 2018
1 parent 37879e0 commit c1132a3
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 15 deletions.
39 changes: 34 additions & 5 deletions core/word.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,24 +362,48 @@ def AsArithVarName(w):
return part0.token.val


def LooksLikeAssignment(w):
def IsVarLike(w):
"""Tests whether a word looks like FOO=bar.
This is a quick test for the command parser to distinguish:
func() { echo hi; }
func=(1 2 3)
"""
assert w.tag == word_e.CompoundWord
if len(w.parts) == 0:
return False

part0 = w.parts[0]
return _LiteralPartId(w.parts[0]) == Id.Lit_VarLike


def LooksLikeAssignment(w):
"""Tests whether a word looks like FOO=bar or FOO[x]=bar.
Returns:
(string, op, CompoundWord) if it looks like FOO=bar
False if it doesn't
TODO: could use assign_parse
Or (spid, k, (spid1, spid2), op, v)
spid1 and spid2 are [ and ]
Or do we reparse right here? Create our own ArithParser.
Cases:
s=1
s+=1
s[x]=1
s[x]+=1
a=()
a+=()
a[x]=()
a[x]+=() # Not valid because arrays can't be nested.
NOTE: a[ and s[ might be parsed separately?
a[x]=(
a[x]+=() # We parse this (as bash does), but it's never valid because arrays
# can't be nested.
"""
assert w.tag == word_e.CompoundWord
if len(w.parts) == 0:
Expand All @@ -389,6 +413,11 @@ def LooksLikeAssignment(w):
if _LiteralPartId(part0) != Id.Lit_VarLike:
return False

# TODO:
# if id0 == Id.Lit_ArrayLhsOpen:
# Look through for Id.Lit_ArrayLhsClose
# If you find it, then it is an array

s = part0.token.val
assert s.endswith('=')
if s[-2] == '+':
Expand Down
34 changes: 24 additions & 10 deletions osh/cmd_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ def _MakeAssignment(assign_kw, suffix_words):
while i < n:
w = suffix_words[i]
left_spid = word.LeftMostSpanForWord(w)
# declare x[y]=1 is valid
kov = word.LooksLikeAssignment(w)
if kov:
k, op, v = kov
t = word.TildeDetect(v)
if t:
# t is an unevaluated word with TildeSubPart
a = (k, op, t, left_spid)
else:
a = (k, op, v, left_spid) # v is unevaluated word
v = t
# t is an unevaluated word with TildeSubPart
a = (k, op, v, left_spid)
else:
# In aboriginal in variables/sources: export_if_blank does export "$1".
# We should allow that.
Expand Down Expand Up @@ -196,17 +196,29 @@ def _SplitSimpleCommandPrefix(words):
suffix_words.append(w)
continue

# TODO: LooksLikeAssignment should give you this spid.
left_spid = word.LeftMostSpanForWord(w)

# k, (spid1, spid2), op, v
#
# if bracketed:
# spid1, spid2 = bracketed
# Parse --> possibility of SYNTAX ERROR here, need to print it
# If successful, we get an arith_expr
# and then create LhsIndexedName(arith_expr)
#
# Problem: we have too many representations:
# kov
# -> prefix_bindings
# -> assign_pair

kov = word.LooksLikeAssignment(w)
if kov:
k, op, v = kov
t = word.TildeDetect(v)
if t:
# t is an unevaluated word with TildeSubPart
prefix_bindings.append((k, op, t, left_spid))
else:
prefix_bindings.append((k, op, v, left_spid)) # v is unevaluated word
if t: # t is an unevaluated word with TildeSubPart
v = t
prefix_bindings.append((k, op, v, left_spid))
else:
done_prefix = True
suffix_words.append(w)
Expand Down Expand Up @@ -679,6 +691,8 @@ def ParseSimpleCommand(self, cur_aliases):
here_end vs filename is a matter of whether we test that it's quoted. e.g.
<<EOF vs <<'EOF'.
"""
# TODO: Refactor this method to 'early return' style, not if-elif.

result = self._ScanSimpleCommand()
assert result is not None
redirects, words = result
Expand Down Expand Up @@ -1362,7 +1376,7 @@ def ParseCommand(self, cur_aliases=None):

if self.c_kind == Kind.Word:
if (self.w_parser.LookAhead() == Id.Op_LParen and
not word.LooksLikeAssignment(self.cur_word)):
not word.IsVarLike(self.cur_word)):
return self.ParseFunctionDef() # f() { echo; } # function
# echo foo
# f=(a b c) # array
Expand Down
12 changes: 12 additions & 0 deletions osh/osh.asdl
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ module osh
LhsName(string name)
| LhsIndexedName(string name, arith_expr index)

-- s=1 a[x]=1 a=(1 2 3)
rhs_expr =
Scalar(word w)
| Array(word items)

-- The more principled thing to do
assign_parse =
ScalarLocation(token var_like, assign_op op, rhs_expr rhs)
| ArrayLocation(token array_open, int spid1, int spid2, assign_op op,
rhs_expr rhs)
| Not

-- Need to preserve physical parens? Maybe need Parens node.
arith_expr =
-- TODO: need token
Expand Down

0 comments on commit c1132a3

Please sign in to comment.