~andyc/oil

3cbd9c8d051eb38b7ae699406d0f2f979876fbcc — Andy Chu a month ago 46a8b5e
[oil-language] Add support for char literals like \n \\

They evaluate to integers.

This is shorter and more efficient than ord($'\n') and ord($'\\').

Add test case for #'a' and so forth.
M doc/oil-language-tour.md => doc/oil-language-tour.md +2 -4
@@ 250,7 250,7 @@ The `$[myexpr]` syntax evaluates an expression and converts it to a string:
    echo $[1 + 2 * 3]                # => 7
    echo "_ $[1 + 2 * 3] _"          # => _ 7 _

<!-- TODO: safe substitution -->
<!-- TODO: safe substitution with $[a] -->

#### Function Sub



@@ 357,8 357,6 @@ If it's neither, then it's assumed to be an external command:
    ls -l /tmp           # The external 'ls' command

<!-- 
leaving off: aliases

TODO: We also need lazy arg lists: qtt | where (size > 10)
-->



@@ 614,7 612,7 @@ There are many ways to write integers:
    echo "$hex $octal $binary"           # => 65536 493 21

<!--
TODO: implement char literals
TODO: Implement char literals

Character literals can appear outside of strings, and are actually integers:


M frontend/consts.py => frontend/consts.py +16 -0
@@ 191,6 191,22 @@ def LookupCharC(c):
  return _ONE_CHAR_C[c]


_ONE_CHAR_INT = {
    '0': ord('\0'),
    'n': ord('\n'),
    'r': ord('\r'),
    't': ord('\t'),
    '\\': ord('\\'),
    "'": ord("'"),
    '"': ord('"'),
}

def LookupCharInt(c):
  # type: (str) -> int
  """Fatal if not present."""
  return _ONE_CHAR_INT[c]


# NOTE: Prompts chars and printf are consistent, e.g. \E is \e in printf, but
# not in PS1.
_ONE_CHAR_PROMPT = {

M oil_lang/expr_eval.py => oil_lang/expr_eval.py +12 -8
@@ 210,28 210,32 @@ class OilEvaluator(object):
      id_ = node.c.id
      if id_ == Id.Expr_DecInt:
        return int(c)
      elif id_ == Id.Expr_BinInt:
      if id_ == Id.Expr_BinInt:
        return int(c, 2)
      elif id_ == Id.Expr_OctInt:
      if id_ == Id.Expr_OctInt:
        return int(c, 8)
      elif id_ == Id.Expr_HexInt:
      if id_ == Id.Expr_HexInt:
        return int(c, 16)

      elif id_ == Id.Expr_Float:
      if id_ == Id.Expr_Float:
        return float(c)

      elif id_ == Id.Expr_Null:
      if id_ == Id.Expr_Null:
        return None
      elif id_ == Id.Expr_True:
      if id_ == Id.Expr_True:
        return True
      elif id_ == Id.Expr_False:
      if id_ == Id.Expr_False:
        return False

      elif id_ == Id.Expr_Name:
      if id_ == Id.Expr_Name:
        # for {name: 'bob'}
        # Maybe also :Symbol?
        return node.c.val

      elif id_ == Id.Char_OneChar:
        # In expression context, it's an integer
        return consts.LookupCharInt(node.c.val[1])

      # NOTE: We could allow Ellipsis for a[:, ...] here, but we're not using
      # it yet.
      raise AssertionError(id_)

M oil_lang/expr_to_ast.py => oil_lang/expr_to_ast.py +1 -1
@@ 618,7 618,7 @@ class Transformer(object):
          Id.Expr_Float):
        return expr.Const(tok)

      if id_ in (Id.Expr_Null, Id.Expr_True, Id.Expr_False):
      if id_ in (Id.Expr_Null, Id.Expr_True, Id.Expr_False, Id.Char_OneChar):
        return expr.Const(tok)

      raise NotImplementedError(Id_str(id_))

M oil_lang/grammar.pgen2 => oil_lang/grammar.pgen2 +1 -0
@@ 94,6 94,7 @@ atom: (
    # TODO: Allow suffixes on floats and decimals?
  | Expr_Float | Expr_DecInt | Expr_BinInt | Expr_OctInt | Expr_HexInt 

  | Char_OneChar  # \n \\ etc.
    # TODO: Expr_Symbol for %mykey

  | dq_string | sq_string

M spec/oil-expr.test.sh => spec/oil-expr.test.sh +14 -0
@@ 344,6 344,20 @@ SHELL
sum 40
## END

#### Backslash char literal (is an integer)
var newline = \n
var backslash = \\
echo "$newline $backslash"
## STDOUT:
10 92
## END

#### Pound char literal (is an integer)
var ch = #'a'
echo "[$ch]"
## STDOUT:
## END

#### Float Literals
shopt -s oil:basic
# 1+2 2.3

M test/spec.sh => test/spec.sh +1 -1
@@ 926,7 926,7 @@ oil-word-eval() {
}

oil-expr() {
  sh-spec spec/oil-expr.test.sh --osh-failures-allowed 5 \
  sh-spec spec/oil-expr.test.sh --osh-failures-allowed 6 \
    $OSH_LIST "$@"
}