Skip to content

Commit

Permalink
WIP implement set
Browse files Browse the repository at this point in the history
  • Loading branch information
voschezang committed Dec 25, 2023
1 parent 5404221 commit 5539b90
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 32 deletions.
4 changes: 3 additions & 1 deletion src/mash/shell/ast/infix.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


class Infix(Node):
def __init__(self, lhs: Node, rhs: Node, op=None):
def __init__(self, lhs: Node, rhs: Node, op: str):
"""An infix operator expression.
Parameters
Expand Down Expand Up @@ -164,6 +164,8 @@ class Map(Infix):
range 10 >>= echo The value is $ .
"""
def __init__(self, lhs: Node, rhs: Node):
super().__init__(lhs, rhs, 'map')

def run(self, prev_result='', shell: BaseShell = None, lazy=False):
prev = shell.run_commands(self.lhs, prev_result, run=not lazy)
Expand Down
10 changes: 6 additions & 4 deletions src/mash/shell/ast/set_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from mash.shell.ast.node import Node
from mash.shell.base import BaseShell
from mash.shell.cmd2 import Mode


class SetDefinition(Node):
Expand All @@ -26,10 +27,11 @@ def __init__(self, items, condition=None):

def run(self, prev_result='', shell: BaseShell = None, lazy=False):
items = []
for item in self.items.values:
results = shell.run_commands(item)
for row in results:
items.append(row.splitlines())
with shell.use_mode(Mode.COMPILE):
for item in self.items.values:
results = shell.run_commands(item)
for row in results:
items.append(row.splitlines())

if lazy:
return f'{{ {self.items} | {self.condition} }}'
Expand Down
18 changes: 17 additions & 1 deletion src/mash/shell/cmd2.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from asyncio import CancelledError
import cmd
from contextlib import contextmanager
from enum import Enum, auto
import logging
from pathlib import Path
import traceback

from mash import io_util
import cmd
from mash.io_util import log, read_file, shell_ready_signal, check_output
from mash.shell.errors import ShellError, ShellSyntaxError
import mash.shell.function as func
Expand All @@ -15,6 +16,11 @@
default_prompt = '$ '


class Mode(Enum):
REPL = auto()
COMPILE = auto()


class Cmd2(cmd.Cmd):
"""Extend cmd.Cmd with various capabilities.
This class is restricted to functionality that requires Cmd methods to be overrriden.
Expand All @@ -40,6 +46,7 @@ class Cmd2(cmd.Cmd):
prompt = default_prompt
completenames_options = []
ignore_invalid_syntax = False
mode = Mode.REPL

def onecmd(self, line: str) -> bool:
"""Parse and run `line`.
Expand Down Expand Up @@ -199,6 +206,15 @@ def do_range(self, args: str) -> str:
args = (int(a) for a in args)
return '\n'.join((str(i) for i in range(*args)))

@contextmanager
def use_mode(self, mode: Mode):
initial = self.mode
try:
self.mode = mode
yield
finally:
self.mode = initial


################################################################################
# Run interface
Expand Down
1 change: 1 addition & 0 deletions src/mash/shell/grammer/literals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
'not': 'NOT',
'and': 'AND',
'or': 'OR',
'in': 'IN',
'math': 'MATH',
}
token_values = {v: k for k, v in keywords.items()}
Expand Down
19 changes: 9 additions & 10 deletions src/mash/shell/grammer/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def parse(text, init=True):
('left', 'PIPE', 'BASH'),
('left', 'MATH'),
('left', 'INFIX_OPERATOR'),
('left', 'IN'),
('left', 'EQUALS'),
('left', 'OR'),
('left', 'AND'),
Expand Down Expand Up @@ -153,10 +154,6 @@ def p_expression_full_conditional(p):
# a full_conditional can be included inside a pipe
p[0] = p[1]

# def p_expression_set(p):
# 'expression : set'
# p[0] = p[1]

def p_expression(p):
"""expression : join
| logic_expression
Expand Down Expand Up @@ -185,11 +182,10 @@ def p_logic_binary(p):
p[0] = LogicExpression(p[1], p[3], p[2])

def p_logic_expression_infix(p):
'logic_expression : terms INFIX_OPERATOR logic_expression'
p[0] = BinaryExpression(p[1], p[3], p[2])

def p_logic_expression_infix_equals(p):
'logic_expression : logic_expression EQUALS logic_expression'
"""logic_expression : terms INFIX_OPERATOR logic_expression
| logic_expression EQUALS logic_expression
| logic_expression IN logic_expression
"""
p[0] = BinaryExpression(p[1], p[3], p[2])

def p_logic_negation(p):
Expand All @@ -201,6 +197,10 @@ def p_logic(p):
'logic_expression : terms'
p[0] = p[1]

def p_logic_set_definition(p):
'logic_expression : set'
p[0] = p[1]

def p_full_conditional(p):
'full_conditional : IF conjunction THEN conjunction ELSE conjunction'
_, _if, cond, _then, true, _else, false = p
Expand Down Expand Up @@ -283,7 +283,6 @@ def p_term_value(p):
"""term : value
| method
| scope
| set
"""
p[0] = p[1]

Expand Down
2 changes: 1 addition & 1 deletion src/mash/shell/grammer/tokenizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
'ASSIGN', # =
'ASSIGN_RIGHT', # ->
'EQUALS', # ==
'INFIX_OPERATOR', # == < >
'INFIX_OPERATOR', # >= <= < >

'RPAREN', # (
'LPAREN', # )
Expand Down
1 change: 0 additions & 1 deletion src/mash/shell/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from mash.shell.cmd2 import default_prompt, run
from mash.shell.grammer.literals import DEFINE_FUNCTION
from mash.shell.errors import ShellError, ShellPipeError, ShellSyntaxError
from mash.shell.errors import ShellSyntaxError
from mash.shell.function import ShellFunction as Function
from mash.shell.internals.if_statement import Abort, handle_prev_then_else_statements
from mash.shell.grammer.parser import parse
Expand Down
16 changes: 12 additions & 4 deletions src/mash/shell/with_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mash.filesystem.discoverable import Discoverable
from mash.filesystem.view import Path, ViewError
from mash.io_util import log
from mash.shell.cmd2 import Mode
from mash.shell.shell import build, set_completions, set_functions
from mash.shell.function import LAST_RESULTS, ShellFunction as Function
from mash.util import find_fuzzy_matches, hamming, has_method, is_digit, partial_simple
Expand Down Expand Up @@ -208,12 +209,19 @@ def default_method(self, dirname: str):
if candidates:
error = hamming(dirname, str(candidates[0]))
if error < 0.8:
self.repository.cd(candidates[0])
return
if self.shell.mode == Mode.REPL:
self.repository.cd(candidates[0])
return
elif self.shell.mode == Mode.COMPILE:
return self.get(candidates[0])

if is_digit(dirname):
self.repository.cd(dirname)
return
if self.shell.mode == Mode.REPL:
self.repository.cd(dirname)
return
elif self.shell.mode == Mode.COMPILE:
return self.get(dirname)


debug('No matching directory found')
return dirname
30 changes: 22 additions & 8 deletions test/shell/test_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ def test_parse_infix():
assert result.key.values == ['a', 'b']
assert result.value == '2'

def test_parse_equals():
result = parse_line('x == 2')
assert isinstance(result, BinaryExpression)
assert result.op == '=='

with raises(ShellSyntaxError):
result = parse_line('==')

def test_parse_contains():
result = parse_line('x in 1 2')
assert isinstance(result, BinaryExpression)
assert result.op == 'in'

with raises(ShellSyntaxError):
result = parse_line('in')

def test_parse_numbers():
numbers = ['-1', '-0.1', '.2', '-100.']
Expand Down Expand Up @@ -556,23 +571,22 @@ def test_parse_math():

def test_parse_set():
result = parse_line('{ users }')
assert isinstance(result, Terms)
assert isinstance(result.values[0], SetDefinition)
assert isinstance(result, SetDefinition)
result = parse_line('{ users }')
assert isinstance(result.values[0], SetDefinition)
assert isinstance(result, SetDefinition)
result = parse_line('{ users.id }')
assert isinstance(result.values[0], SetDefinition)
assert isinstance(result, SetDefinition)

def test_parse_set_with_filter():
result = parse_line('{ users | .id > 100 }')
assert isinstance(result.values[0], SetDefinition)
assert isinstance(result, SetDefinition)
result = parse_line('{ users | users.id > 100 }')
assert isinstance(result.values[0], SetDefinition)
assert isinstance(result, SetDefinition)
result = parse_line('{ users groups | x.id == y.id }')
assert isinstance(result.values[0], SetDefinition)
assert isinstance(result, SetDefinition)
result = parse_line('x <- { users }')
assert isinstance(result, Assign)
assert isinstance(result.rhs.values[0], SetDefinition)
assert isinstance(result.rhs, SetDefinition)

def test_parse_set_with_nested_filter():
# TODO
Expand Down
3 changes: 2 additions & 1 deletion test/shell/test_shell_rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ def test_rest_client_standard_set():
shell, _ = init()
shell = shell.shell

result = catch_output('{users} >>= show $.id', shell=shell)
result = catch_output(r'{users}', shell=shell)
result = catch_output(r'{users} >>= show $.id', shell=shell)
# TODO add assertions
# assert '1001' in result

Expand Down
2 changes: 1 addition & 1 deletion test/shell/test_shell_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def test_set_definition():
run_command('a <- range 3', shell=shell)
run_command('b <- range 3', shell=shell)
run_command('c <- { $a }', shell=shell)
assert shell.env['c'] == '0\n1\n2'
assert shell.env['c'] == '0 1 2'

# TODO
run_command('c <- { a b }', shell=shell)
Expand Down

0 comments on commit 5539b90

Please sign in to comment.