diff --git a/oil_lang/builtin_funcs.py b/oil_lang/builtin_funcs.py index 435844632b..bc96f9b901 100644 --- a/oil_lang/builtin_funcs.py +++ b/oil_lang/builtin_funcs.py @@ -41,21 +41,22 @@ def Init(mem): # SetGlobalFunc(mem, 'Table', objects.Table) + SetGlobalFunc(mem, 'Array', objects.ParameterizedArray()) # Types: # TODO: Should these be Bool Int Float Str List Dict? - SetGlobalFunc(mem, 'bool', bool) - SetGlobalFunc(mem, 'int', int) + SetGlobalFunc(mem, 'Bool', bool) + SetGlobalFunc(mem, 'Int', int) # TODO: Enable float # OVM: PyOS_string_to_double() # osh: Python/ovm_stub_pystrtod.c:10: PyOS_string_to_double: Assertion `0' failed. - #SetGlobalFunc(mem, 'float', float) + #SetGlobalFunc(mem, 'Float', float) - SetGlobalFunc(mem, 'tuple', tuple) - SetGlobalFunc(mem, 'str', str) - SetGlobalFunc(mem, 'list', list) - SetGlobalFunc(mem, 'dict', dict) + SetGlobalFunc(mem, 'Tuple', tuple) + SetGlobalFunc(mem, 'Str', str) + SetGlobalFunc(mem, 'List', list) + SetGlobalFunc(mem, 'Dict', dict) SetGlobalFunc(mem, 'len', len) SetGlobalFunc(mem, 'max', max) diff --git a/oil_lang/expr_eval.py b/oil_lang/expr_eval.py index a57e39cec8..f655e96302 100644 --- a/oil_lang/expr_eval.py +++ b/oil_lang/expr_eval.py @@ -453,6 +453,9 @@ def EvalExpr(self, node): # TODO: Should this just be an object that ~ calls? return objects.Regex(self.EvalRegex(node.regex)) + if node.tag == expr_e.ArrayLiteral: # obj.attr + return objects.IntArray(self.EvalExpr(item) for item in node.items) + raise NotImplementedError(node.__class__.__name__) def _EvalClassLiteralPart(self, part): diff --git a/oil_lang/expr_to_ast.py b/oil_lang/expr_to_ast.py index fef17b2759..66a08c9de2 100644 --- a/oil_lang/expr_to_ast.py +++ b/oil_lang/expr_to_ast.py @@ -529,12 +529,7 @@ def Expr(self, pnode): elif typ == grammar_nt.array_literal: left_tok = children[0].tok - # Approximation for now. - tokens = [ - pnode.tok for pnode in children[1:-1] if pnode.tok.id == - Id.Lit_Chars - ] - items = [expr.Const(t) for t in tokens] # type: List[expr_t] + items = [self._ArrayItem(p) for p in children[1:-1]] return expr.ArrayLiteral(left_tok, items) # @@ -589,6 +584,18 @@ def Expr(self, pnode): from core.meta import IdInstance raise NotImplementedError(IdInstance(typ)) + def _ArrayItem(self, p_node): + assert p_node.typ == grammar_nt.array_item + + child0 = p_node.children[0] + typ0 = child0.typ + if ISNONTERMINAL(typ0): + return self.Expr(child0) + else: + if child0.tok.id == Id.Op_LParen: # (x+1) + return self.Expr(p_node.children[1]) + return self.Expr(child0) # $1 ${x} etc. + def VarDecl(self, pnode): # type: (PNode) -> command__VarDecl """Transform an Oil assignment statement.""" diff --git a/oil_lang/grammar.pgen2 b/oil_lang/grammar.pgen2 index 06f6a970b8..8fab75ae1e 100644 --- a/oil_lang/grammar.pgen2 +++ b/oil_lang/grammar.pgen2 @@ -147,17 +147,14 @@ word_part: Lit_Chars | Lit_Other word: word_part* # TODO: Change this to types and expressions, like # @[1 2 3] @[(x) (y+1)] @[true false false] -# -# Empty: -# @Bool[] @Int[] @Float[] -# Do we need @Str[]? Or that's just @()? array_item: ( - # NOTE: Most of these occur in 'atom' above + # NOTE: Most of these occur in 'atom' above Expr_Name | Expr_Null | Expr_True | Expr_False | Expr_Float | Expr_DecInt | Expr_BinInt | Expr_OctInt | Expr_HexInt | dq_string | sq_string | - sh_command_sub | braced_var_sub | simple_var_sub + sh_command_sub | braced_var_sub | simple_var_sub | + '(' test ')' ) array_literal: ( '@[' array_item* Op_RBracket diff --git a/oil_lang/objects.py b/oil_lang/objects.py index 4ee93c1851..66dc2dfa38 100644 --- a/oil_lang/objects.py +++ b/oil_lang/objects.py @@ -16,6 +16,27 @@ _ = log +class ParameterizedArray(object): + """ + Parameterized + For Array[Bool] + """ + def __getitem__(self, typ): + if typ is bool: + return BoolArray + if typ is int: + return IntArray + if typ is float: + return FloatArray + if typ is str: + return StrArray + raise AssertionError('typ: %s' % typ) + + def __call__(self): + # Array(1 2 3) + raise AssertionError("Arrays need a parameterized type") + + # These are for data frames? class BoolArray(list): diff --git a/spec/oil-array.test.sh b/spec/oil-array.test.sh new file mode 100644 index 0000000000..19a4b02eb2 --- /dev/null +++ b/spec/oil-array.test.sh @@ -0,0 +1,76 @@ +# Typed arrays + +#### integer array +var x = @[1 2 3] +echo len=$len(x) +## STDOUT: +len=3 +## END + +#### string array with command sub, varsub, etc. +shopt -s oil:basic + +var x = 1 +var a = @[$x $(echo hi)] +echo len=$len(a) +echo @a +## STDOUT: +len=2 +1 +hi +## END + +#### arrays with expressions +shopt -s oil:basic + +# Does () make makes sense? + +var x = 5 +var y = 6 +var a = @[(x+1) (y*2)] + +echo len=$len(a) +echo @a + +## STDOUT: +len=2 +6 +12 +## END + +#### Empty arrays and using Array[T] +shopt -s oil:basic + +var b = Array[Bool]() +var i = Array[Int]() + +#var f = Array[Float]() +echo len=$len(b) +echo len=$len(i) + +var b2 = Array[Bool]([true, false]) +echo @b2 + +#echo len=$len(f) +## STDOUT: +len=0 +len=0 +True +False +## END + + +#### Arrays from generator expressions +shopt -s oil:basic + +var b = Array[Bool](true for _ in 1:3) +var i = Array[Int](i+1 for i in 1:3) +var f = Array[Float](i * 2.5 for i in 1:3) +echo @b +echo @i +echo @f +## STDOUT: +len=0 +len=0 +len=0 +## END diff --git a/spec/oil-assign.test.sh b/spec/oil-assign.test.sh new file mode 100644 index 0000000000..56d6da930c --- /dev/null +++ b/spec/oil-assign.test.sh @@ -0,0 +1,112 @@ +# Test var / setvar / etc. + +# TODO: GetVar needs a mode where Obj[str] gets translated to value.Str? +# Then all code will work. +# +# word_eval: +# +# val = self.mem.GetVar(var_name) -> +# val = GetWordVar(self.mem, var_name) +# +# Conversely, in oil_lang/expr_eval.py: +# LookupVar gives you a plain Python object. I don't think there's any +# downside here. +# +# repr exposes the differences. +# +# Notes: +# +# - osh/cmd_exec.py handles OilAssign, which gets wrapped in value.Obj() +# - osh/word_eval.py _ValueToPartValue handles 3 value types. Used in: +# - _EvalBracedVarSub +# - SimpleVarSub in _EvalWordPart +# - osh/expr_eval.py: _LookupVar wrapper should disallow using Oil values +# - this is legacy stuff. Both (( )) and [[ ]] +# - LhsIndexedName should not reference Oil vars either + + +#### integers expression and augmented assignment +var x = 1 + 2 * 3 +echo x=$x + +setvar x += 4 +echo x=$x +## STDOUT: +x=7 +x=11 +## END + +#### setvar when variable isn't declared results in fatal error +var x = 1 +f() { + # setting global is OK + setvar x = 2 + echo x=$x + + setvar y = 3 # NOT DECLARED + echo y=$y +} +f +## status: 1 +## STDOUT: +x=2 +## END + +#### var/setvar x, y = 1, 2 + +# Python doesn't allow you to have annotation on each variable! +# https://www.python.org/dev/peps/pep-0526/#where-annotations-aren-t-allowed +#var x Int, y Int = 3, 4 +setvar x, y = 1, 2 +echo $x $y +## STDOUT: +1 2 +## END + +#### setvar x[1] = 42 +shopt -s oil:basic +var mylist = [1,2,3] +setvar x[1] = 42 +echo -sep ' ' @x +## STDOUT: +1 42 3 +## END + + +#### duplicate var def results in fatal error +var x = "global" +f() { + var x = "local" + echo x=$x +} +f +var x = "redeclaration is an error" +## status: 1 +## STDOUT: +x=local +## END + +#### setvar dynamic scope (TODO: change this?) +modify_with_shell_assignment() { + f=shell +} + +modify_with_setvar() { + setvar f = "setvar" +} + +f() { + var f = 1 + echo f=$f + modify_with_shell_assignment + echo f=$f + modify_with_setvar + echo f=$f +} +f +## STDOUT: +f=1 +f=shell +f=setvar +## END + diff --git a/spec/oil-expr.test.sh b/spec/oil-expr.test.sh index dcade18d9f..5e05d68866 100644 --- a/spec/oil-expr.test.sh +++ b/spec/oil-expr.test.sh @@ -1,115 +1,3 @@ -# Test var / setvar / etc. - -# TODO: GetVar needs a mode where Obj[str] gets translated to value.Str? -# Then all code will work. -# -# word_eval: -# -# val = self.mem.GetVar(var_name) -> -# val = GetWordVar(self.mem, var_name) -# -# Conversely, in oil_lang/expr_eval.py: -# LookupVar gives you a plain Python object. I don't think there's any -# downside here. -# -# repr exposes the differences. -# -# Notes: -# -# - osh/cmd_exec.py handles OilAssign, which gets wrapped in value.Obj() -# - osh/word_eval.py _ValueToPartValue handles 3 value types. Used in: -# - _EvalBracedVarSub -# - SimpleVarSub in _EvalWordPart -# - osh/expr_eval.py: _LookupVar wrapper should disallow using Oil values -# - this is legacy stuff. Both (( )) and [[ ]] -# - LhsIndexedName should not reference Oil vars either - - -#### integers expression and augmented assignment -var x = 1 + 2 * 3 -echo x=$x - -setvar x += 4 -echo x=$x -## STDOUT: -x=7 -x=11 -## END - -#### setvar when variable isn't declared results in fatal error -var x = 1 -f() { - # setting global is OK - setvar x = 2 - echo x=$x - - setvar y = 3 # NOT DECLARED - echo y=$y -} -f -## status: 1 -## STDOUT: -x=2 -## END - -#### var/setvar x, y = 1, 2 - -# Python doesn't allow you to have annotation on each variable! -# https://www.python.org/dev/peps/pep-0526/#where-annotations-aren-t-allowed -#var x Int, y Int = 3, 4 -setvar x, y = 1, 2 -echo $x $y -## STDOUT: -1 2 -## END - -#### setvar x[1] = 42 -shopt -s oil:basic -var mylist = [1,2,3] -setvar x[1] = 42 -echo -sep ' ' @x -## STDOUT: -1 42 3 -## END - - -#### duplicate var def results in fatal error -var x = "global" -f() { - var x = "local" - echo x=$x -} -f -var x = "redeclaration is an error" -## status: 1 -## STDOUT: -x=local -## END - -#### setvar dynamic scope (TODO: change this?) -modify_with_shell_assignment() { - f=shell -} - -modify_with_setvar() { - setvar f = "setvar" -} - -f() { - var f = 1 - echo f=$f - modify_with_shell_assignment - echo f=$f - modify_with_setvar - echo f=$f -} -f -## STDOUT: -f=1 -f=shell -f=setvar -## END - #### command sub $(echo hi) var x = $(echo hi) var y = $(echo '') diff --git a/test/spec.sh b/test/spec.sh index 4ea68ea438..f4dc38e9d8 100755 --- a/test/spec.sh +++ b/test/spec.sh @@ -803,6 +803,16 @@ all-and-smoosh() { # Oil Language # +oil-array() { + sh-spec spec/oil-array.test.sh --cd-tmp --osh-failures-allowed 1 \ + $OSH_LIST "$@" +} + +oil-assign() { + sh-spec spec/oil-assign.test.sh --cd-tmp --osh-failures-allowed 1 \ + $OSH_LIST "$@" +} + oil-blocks() { sh-spec spec/oil-blocks.test.sh --cd-tmp \ $OSH_LIST "$@" @@ -819,7 +829,7 @@ oil-options() { } oil-expr() { - sh-spec spec/oil-expr.test.sh --cd-tmp --osh-failures-allowed 7 \ + sh-spec spec/oil-expr.test.sh --cd-tmp --osh-failures-allowed 5 \ $OSH_LIST "$@" }