Allow calling arbitrary values
This commit is contained in:
parent
fb81eb84a7
commit
7a55ee0777
@ -40,7 +40,7 @@ class VariableRef:
|
|||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class FunctionCall:
|
class FunctionCall:
|
||||||
pos: Position
|
pos: Position
|
||||||
name: str
|
value: 'Value'
|
||||||
args: list['Value']
|
args: list['Value']
|
||||||
|
|
||||||
Ref = VariableRef | FunctionCall
|
Ref = VariableRef | FunctionCall
|
||||||
|
@ -80,20 +80,19 @@ class Interp:
|
|||||||
return self._is_numerical(val) or isinstance(val, str)
|
return self._is_numerical(val) or isinstance(val, str)
|
||||||
|
|
||||||
def _exec_func_call(self, call: ast.FunctionCall) -> typing.Any:
|
def _exec_func_call(self, call: ast.FunctionCall) -> typing.Any:
|
||||||
if call.name not in self.vars:
|
val = self._convert_value(call.value)
|
||||||
raise KeyError(f'{call.pos}: no such function: {repr(call.name)}')
|
if not callable(val):
|
||||||
elif not callable(self.vars[call.name]):
|
|
||||||
raise ValueError(f'{call.pos}: cannot call non-callable object')
|
raise ValueError(f'{call.pos}: cannot call non-callable object')
|
||||||
args = []
|
args = []
|
||||||
for arg in call.args:
|
for arg in call.args:
|
||||||
if isinstance(arg, ast.Expansion):
|
if isinstance(arg, ast.Expansion):
|
||||||
val = self._convert_value(arg.value)
|
arg_val = self._convert_value(arg.value)
|
||||||
if not isinstance(val, typing.Iterable):
|
if not isinstance(arg_val, typing.Iterable):
|
||||||
raise ValueError(f"{arg.pos}: cannot perform expansion on non-iterable value ({type(val).__name__})")
|
raise ValueError(f"{arg.pos}: cannot perform expansion on non-iterable value ({type(arg_val).__name__})")
|
||||||
args.extend(val)
|
args.extend(arg_val)
|
||||||
else:
|
else:
|
||||||
args.append(self._convert_value(arg))
|
args.append(self._convert_value(arg))
|
||||||
return self.vars[call.name](*args)
|
return val(*args)
|
||||||
|
|
||||||
def _eval_unary_expr(self, expr: ast.UnaryExpression) -> float | int | bool:
|
def _eval_unary_expr(self, expr: ast.UnaryExpression) -> float | int | bool:
|
||||||
val = self._convert_value(expr.value)
|
val = self._convert_value(expr.value)
|
||||||
|
@ -41,6 +41,9 @@ class Lexer:
|
|||||||
self.unread = ''
|
self.unread = ''
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.pos.name = name
|
self.pos.name = name
|
||||||
|
|
||||||
|
def _pos(self) -> ast.Position:
|
||||||
|
return dataclasses.replace(self.pos)
|
||||||
|
|
||||||
def _peek(self, n: int) -> str:
|
def _peek(self, n: int) -> str:
|
||||||
if self.unread != '':
|
if self.unread != '':
|
||||||
@ -191,15 +194,15 @@ class Lexer:
|
|||||||
|
|
||||||
match char:
|
match char:
|
||||||
case '{' | '}':
|
case '{' | '}':
|
||||||
return Token.CURLY, self.pos, char
|
return Token.CURLY, self._pos(), char
|
||||||
case '[' | ']':
|
case '[' | ']':
|
||||||
return Token.SQUARE, self.pos, char
|
return Token.SQUARE, self._pos(), char
|
||||||
case '(' | ')':
|
case '(' | ')':
|
||||||
return Token.PAREN, self.pos, char
|
return Token.PAREN, self._pos(), char
|
||||||
case ',':
|
case ',':
|
||||||
return Token.COMMA, self.pos, char
|
return Token.COMMA, self._pos(), char
|
||||||
case ':':
|
case ':':
|
||||||
return Token.COLON, self.pos, char
|
return Token.COLON, self._pos(), char
|
||||||
case '"':
|
case '"':
|
||||||
return self._scan_str()
|
return self._scan_str()
|
||||||
case '<':
|
case '<':
|
||||||
@ -230,12 +233,12 @@ class Lexer:
|
|||||||
case '.':
|
case '.':
|
||||||
if (next := self._read()) != '.':
|
if (next := self._read()) != '.':
|
||||||
self._unread(next)
|
self._unread(next)
|
||||||
return Token.DOT, self.pos, next
|
return Token.DOT, self._pos(), next
|
||||||
elif (next := self._read()) != '.':
|
elif (next := self._read()) != '.':
|
||||||
raise ExpectedError(self.pos, '.', next)
|
raise ExpectedError(self.pos, '.', next)
|
||||||
return Token.ELLIPSIS, self.pos, "..."
|
return Token.ELLIPSIS, self._pos(), "..."
|
||||||
case '':
|
case '':
|
||||||
return Token.EOF, self.pos, char
|
return Token.EOF, self._pos(), char
|
||||||
|
|
||||||
if is_numeric(char):
|
if is_numeric(char):
|
||||||
return self._scan_number(char)
|
return self._scan_number(char)
|
||||||
@ -244,7 +247,7 @@ class Lexer:
|
|||||||
elif is_operator(char):
|
elif is_operator(char):
|
||||||
return self._scan_operator(char)
|
return self._scan_operator(char)
|
||||||
|
|
||||||
return Token.ILLEGAL, self.pos, char
|
return Token.ILLEGAL, self._pos(), char
|
||||||
|
|
||||||
def is_whitespace(char: str) -> bool:
|
def is_whitespace(char: str) -> bool:
|
||||||
return char in (' ', '\t', '\r', '\n')
|
return char in (' ', '\t', '\r', '\n')
|
||||||
|
@ -98,15 +98,14 @@ class Parser:
|
|||||||
|
|
||||||
return ast.Object(start_pos, items)
|
return ast.Object(start_pos, items)
|
||||||
|
|
||||||
def _parse_func_call(self) -> ast.FunctionCall:
|
def _parse_func_call(self, val: ast.Value, start_pos: ast.Position) -> ast.FunctionCall:
|
||||||
id_tok, id_pos, id_lit = self._scan()
|
tok, pos, lit = self._scan()
|
||||||
tok, pos, lit = self._scan()
|
|
||||||
if tok != lexer.Token.PAREN or lit != '(':
|
|
||||||
raise ExpectedError(pos, 'opening parentheses', lit)
|
|
||||||
|
|
||||||
tok, pos, lit = self._scan()
|
|
||||||
if tok == lexer.Token.PAREN and lit == ')':
|
if tok == lexer.Token.PAREN and lit == ')':
|
||||||
return ast.FunctionCall(pos=id_pos, name=id_lit, args=[])
|
out = ast.FunctionCall(pos=start_pos, value=val, args=[])
|
||||||
|
while self.lexer._peek(1) == '(':
|
||||||
|
_, start_pos, _ = self._scan()
|
||||||
|
out = self._parse_func_call(out, start_pos)
|
||||||
|
return out
|
||||||
self._unscan(tok, pos, lit)
|
self._unscan(tok, pos, lit)
|
||||||
|
|
||||||
args: list[ast.Value] = []
|
args: list[ast.Value] = []
|
||||||
@ -125,7 +124,12 @@ class Parser:
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ExpectedError(pos, 'comma or closing parentheses', lit)
|
raise ExpectedError(pos, 'comma or closing parentheses', lit)
|
||||||
return ast.FunctionCall(pos=id_pos, name=id_lit, args=args)
|
|
||||||
|
out = ast.FunctionCall(pos=start_pos, value=val, args=args)
|
||||||
|
while self.lexer._peek(1) == '(':
|
||||||
|
_, start_pos, _ = self._scan()
|
||||||
|
out = self._parse_func_call(out, start_pos)
|
||||||
|
return out
|
||||||
|
|
||||||
def _parse_value(self) -> ast.Value:
|
def _parse_value(self) -> ast.Value:
|
||||||
out = None
|
out = None
|
||||||
@ -140,11 +144,7 @@ class Parser:
|
|||||||
case lexer.Token.STRING:
|
case lexer.Token.STRING:
|
||||||
out = ast.String(pos=pos, value=pyast.literal_eval(lit))
|
out = ast.String(pos=pos, value=pyast.literal_eval(lit))
|
||||||
case lexer.Token.IDENT:
|
case lexer.Token.IDENT:
|
||||||
if self.lexer._peek(1) == '(':
|
out = ast.VariableRef(pos=pos, name=lit)
|
||||||
self._unscan(tok, pos, lit)
|
|
||||||
out = self._parse_func_call()
|
|
||||||
else:
|
|
||||||
out = ast.VariableRef(pos=pos, name=lit)
|
|
||||||
case lexer.Token.HEREDOC:
|
case lexer.Token.HEREDOC:
|
||||||
out = ast.String(pos=pos, value=lit)
|
out = ast.String(pos=pos, value=lit)
|
||||||
case lexer.Token.OPERATOR:
|
case lexer.Token.OPERATOR:
|
||||||
@ -171,6 +171,8 @@ class Parser:
|
|||||||
tok, pos, lit = self._scan()
|
tok, pos, lit = self._scan()
|
||||||
if tok == lexer.Token.SQUARE and lit == '[':
|
if tok == lexer.Token.SQUARE and lit == '[':
|
||||||
out = self._parse_index(out, pos)
|
out = self._parse_index(out, pos)
|
||||||
|
elif tok == lexer.Token.PAREN and lit == '(':
|
||||||
|
out = self._parse_func_call(out, pos)
|
||||||
elif tok == lexer.Token.DOT:
|
elif tok == lexer.Token.DOT:
|
||||||
out = self._parse_getattr(out, pos)
|
out = self._parse_getattr(out, pos)
|
||||||
else:
|
else:
|
||||||
|
@ -180,8 +180,11 @@ class TestExpressions(unittest.TestCase):
|
|||||||
def test_expansion(self):
|
def test_expansion(self):
|
||||||
val = parser.Parser(io.StringIO('x(y...)'), 'TestExpressions.test_expansion')._parse_expr()
|
val = parser.Parser(io.StringIO('x(y...)'), 'TestExpressions.test_expansion')._parse_expr()
|
||||||
self.assertEqual(val, ast.FunctionCall(
|
self.assertEqual(val, ast.FunctionCall(
|
||||||
pos = ast.Position(name='TestExpressions.test_expansion', line=1, col=1),
|
pos = ast.Position(name='TestExpressions.test_expansion', line=1, col=2),
|
||||||
name = 'x',
|
value = ast.VariableRef(
|
||||||
|
pos = ast.Position(name='TestExpressions.test_expansion', line=1, col=1),
|
||||||
|
name = 'x',
|
||||||
|
),
|
||||||
args = [
|
args = [
|
||||||
ast.Expansion(
|
ast.Expansion(
|
||||||
pos = ast.Position(name='TestExpressions.test_expansion', line=1, col=3),
|
pos = ast.Position(name='TestExpressions.test_expansion', line=1, col=3),
|
||||||
|
Loading…
Reference in New Issue
Block a user