Skip to content

Commit

Permalink
Added new operators, unify command, bugfixes.
Browse files Browse the repository at this point in the history
 - Bitwise operators
 - ^@ split at index
 - Fixed bug with fold on lists of lists
 - Fixed bug preventing explicit split on empty string
  • Loading branch information
dloscutoff committed May 2, 2015
1 parent 864df0b commit 1d329fa
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 49 deletions.
10 changes: 10 additions & 0 deletions Documentation/Operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ U/B/T = Unary/Binary/Ternary

<code>^&ensp; U</code> Split string into list of characters

<code>^@ B</code> Split iterable at index or list of indices

<code>|&ensp; B</code> Logical or (short-circuiting)

### Alphabetic operators
Expand All @@ -86,6 +88,14 @@ U/B/T = Unary/Binary/Ternary

<code>AL B</code> List with appended list

<code>BA B</code> Bitwise and

<code>BN U</code> Bitwise not

<code>BO B</code> Bitwise or

<code>BX B</code> Bitwise xor

<code>C&ensp; U</code> Convert ASCII value/Unicode point to character

<code>EQ B</code> String equal
Expand Down
135 changes: 90 additions & 45 deletions execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,19 @@ def evaluate(self, expression):
#!print("evaluating", fnName)
opFunction = getattr(self, fnName)

if operator.fold:
# A binary operator being used in a unary fold operation
# TBD: what do we do for empty sequences? Need default
# value for each operator?
result = self.FOLD(opFunction, *args)
elif operator.assign:
if operator.assign:
# This is a compute-and-assign operator like +:
# Compute the expression, and then assign it back to the lval
lval = self.evaluate(args[0])
normalOp = operator.copy()
normalOp.assign = False
result = self.evaluate([normalOp] + args)
result = self.ASSIGN(lval, result)
elif operator.fold:
# A binary operator being used in a unary fold operation
# TBD: what do we do for empty sequences? Need default
# value for each operator?
result = self.FOLD(operator, *args)
else:
## blockArg = False
## if operator.modifiesBlocks:
Expand Down Expand Up @@ -465,6 +465,19 @@ def TILL(self, cond, code):
self.executeStatement(statement)
condVal = self.getRval(cond)

def UNIFY(self, lvals, rval):
"""Unify lvals with items of rval, like Python's tuple unpacking."""
rval = self.getRval(rval)
if type(rval) in (List, Scalar, Range):
for i, lval in enumerate(lvals):
if i < len(rval):
self.assign(self.evaluate(lval), rval[i])
else:
self.assign(self.evaluate(lval), nil)
else:
self.err.warn("Unimplemented argtype for UNIFY:", type(rval))
# TBD: assign nil to all variables, or leave them unmodified?

def WHILE(self, cond, code):
"""Loop, executing code, while cond evaluates to true."""
condVal = self.getRval(cond)
Expand All @@ -477,27 +490,21 @@ def WHILE(self, cond, code):
### Pip meta-operators ###
###############################

def FOLD(self, opFunction, iterable):
def FOLD(self, operator, iterable):
iterable = self.getRval(iterable)
normalOp = operator.copy()
normalOp.fold = False
if type(iterable) in (Scalar, List, Range):
if len(iterable) == 0:
# TODO: set default values for each operator so $+[] returns 0
# TODO: default values for each operator so e.g. $+[] == 0
return nil
else:
# TBD: does $-[5] return 5 or -5? Python's reduce() returns 5,
# like this code does currently. Haskell has two separate
# kinds of fold operation, one (which only works on nonempty
# lists) that would return 5, and the other (with a default
# value) that would return -5. Lisp's (- 5) returns -5, if I'm
# not mistaken.
foldValue = None
for val in iterable:
if foldValue is None:
# First time through
foldValue = val
else:
foldValue = opFunction(foldValue, val)
foldValue = iterable[0]
for val in iterable[1:]:
foldValue = self.evaluate([normalOp, foldValue, val])
return foldValue
elif iterable is nil:
return nil
else:
# TODO: allow fold in lambda expressions, e.g. $+_ ?
self.err.warn("Can't fold", type(iterable))
Expand All @@ -510,27 +517,7 @@ def FOLD(self, opFunction, iterable):
#@rvals
#@memberwise
def ADD(self, lhs, rhs):
blockOperand = False
if type(lhs) is Block:
blockOperand = True
if lhs.isExpr():
lhs = lhs.getReturnExpr()
else:
self.err.warn("Cannot modify non-expression function")
return nil
if type(rhs) is Block:
blockOperand = True
if rhs.isExpr():
rhs = rhs.getReturnExpr()
else:
self.err.warn("Cannot modify non-expression function")
return nil
if blockOperand:
# One or both of the operands is a modifiable expression-function;
# return a partial result, which will be repackaged as a new
# Block by execute()
return [lhs, rhs]
elif type(lhs) is type(rhs) is Scalar:
if type(lhs) is type(rhs) is Scalar:
result = lhs.toNumber() + rhs.toNumber()
return Scalar(result)
elif type(lhs) is Scalar and type(rhs) is Range:
Expand Down Expand Up @@ -641,6 +628,41 @@ def AT(self, lhs, rhs):
self.err.warn("Cannot index into", type(lhs))
return nil

def BITWISEAND(self, lhs, rhs):
if type(lhs) is type(rhs) is Scalar:
result = int(lhs) & int(rhs)
return Scalar(result)
else:
self.err.warn("Unimplemented argtypes for BITWISEAND:",
type(lhs), "and", type(rhs))
return nil

def BITWISENOT(self, rhs):
if type(rhs) is Scalar:
result = ~int(rhs)
return Scalar(result)
else:
self.err.warn("Unimplemented argtype for BITWISENOT:", type(rhs))
return nil

def BITWISEOR(self, lhs, rhs):
if type(lhs) is type(rhs) is Scalar:
result = int(lhs) | int(rhs)
return Scalar(result)
else:
self.err.warn("Unimplemented argtypes for BITWISEOR:",
type(lhs), "and", type(rhs))
return nil

def BITWISEXOR(self, lhs, rhs):
if type(lhs) is type(rhs) is Scalar:
result = int(lhs) ^ int(rhs)
return Scalar(result)
else:
self.err.warn("Unimplemented argtypes for BITWISEXOR:",
type(lhs), "and", type(rhs))
return nil

def BLOCK(self, statements):
if len(statements) > 0 and isExpr(statements[-1]):
# The last expression is the return value of the function
Expand Down Expand Up @@ -742,7 +764,7 @@ def FROMBASE(self, number, base=None):
return Scalar(result)
except ValueError:
# TBD: make more robust? Or just let it stay nil
self.err.warn("Failed converting", number, "to base", base)
self.err.warn("Failed converting", number, "from base", base)
return nil
else:
self.err.warn("Unimplemented argtype for FROMBASE:",
Expand Down Expand Up @@ -1299,7 +1321,7 @@ def REMOVE(self, lhs, rhs):
if type(lhs) is Scalar and type(rhs) is Scalar:
result = str(lhs).translate({ord(c):None for c in str(rhs)})
return Scalar(result)
elif type(lhs) is List:
elif type(lhs) in (List, Range):
result = list(lhs)
try:
while True:
Expand All @@ -1322,6 +1344,8 @@ def REPR(self, rhs):
#@rvals
#@rangeAsList # TODO: negative step value
def REVERSE(self, rhs):
if type(rhs) is Range:
rhs = List(rhs)
if type(rhs) in (Scalar, List):
# Let those classes' __getitem__ do the work for us
return rhs[::-1]
Expand Down Expand Up @@ -1385,7 +1409,7 @@ def SPLIT(self, string, sep=None):
# Some other type, not a valid separator
return nil
if type(string) is Scalar:
if sep is None:
if sep is None or sep == "":
result = (Scalar(char) for char in str(string))
else:
result = (Scalar(substr) for substr in str(string).split(sep))
Expand All @@ -1399,6 +1423,27 @@ def SPLIT(self, string, sep=None):
type(string), "and", type(sep))
return nil

def SPLITAT(self, iterable, indices):
# Splits iterable at given indices
if type(indices) is Scalar:
indices = [int(indices)]
elif type(indices) in (List, Range):
indices = list(set(int(index) for index in indices))

if type(iterable) in (List, Scalar, Range) and type(indices) is list:
results = []
prevIndex = 0
length = len(iterable)
for i in range(length):
if i in indices or i - length in indices:
results.append(iterable[prevIndex:i])
prevIndex = i
results.append(iterable[prevIndex:])
return List(results)
else:
self.err.warn("Unimplemented argtypes for SPLITAT:",
type(iterable), "and", type(indices))

#@rvals
def STR(self, rhs):
return Scalar(str(rhs))
Expand Down
12 changes: 11 additions & 1 deletion operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def copy(self):
("Q", "QUERY", ["LVAL"]),
("S", "SWAP", ["LVAL", "LVAL"]),
("T", "TILL", ["RVAL", "CODE"]),
("U", "UNIFY", ["NAMES", "WITH"]),
("W", "WHILE", ["RVAL", "CODE"]),
]

Expand Down Expand Up @@ -131,13 +132,15 @@ def copy(self):
],
[2,
("^", "SPLIT", "L", RVALS | LIST_EACH),
("^@", "SPLITAT", "L", RVALS),
("<>", "GROUP", "L", RVALS),
("J", "JOIN", "L", RVALS),
("RL", "REPEATLIST", "L", RVALS),
],
[1,
("^", "SPLIT", "L", RVALS | LIST_EACH),
("J", "JOIN", "L", RVALS),
("RV", "REVERSE", "L", RVALS),
],
[3,
("R", "REPLACE", "L", RVALS),
Expand All @@ -152,7 +155,6 @@ def copy(self):
("X", "STRMUL", "L", RVALS | RANGE_EACH | LIST_EACH),
],
[1,
("RV", "REVERSE", "L", RVALS),
("LC", "LOWERCASE", "L", RVALS | LIST_EACH),
("UC", "UPPERCASE", "L", RVALS | LIST_EACH),
],
Expand All @@ -167,6 +169,14 @@ def copy(self):
("TB", "TOBASE", "L", RVALS | RANGE_EACH | LIST_EACH), # Unary mnemonic:
# ToBinary
],
[2,
("BA", "BITWISEAND", "L", RVALS | RANGE_EACH | LIST_EACH),
("BO", "BITWISEOR", "L", RVALS | RANGE_EACH | LIST_EACH),
("BX", "BITWISEXOR", "L", RVALS | RANGE_EACH | LIST_EACH),
],
[1,
("BN", "BITWISENOT", "L", RVALS | RANGE_EACH | LIST_EACH),
],
[2,
("<=>", "NUMCMP", "L", RVALS),
],
Expand Down
19 changes: 18 additions & 1 deletion parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,33 @@ def parseStatement(tokenList):
else:
# There is no else branch--use an empty block
statement.append([])
elif argtype == "WITH":
# The with-clause of unify expressions, e.g. UabcWx
if tokenList[0] == "W":
# Match the W and parse an expression
tokenList.pop(0)
statement.append(parseExpr(tokenList))
else:
err.die("Expected W after U, got", tokenList[0])
elif argtype == "CODE":
#!print("CODE", tokenList)
# Parse a code block
statement.append(parseBlock(tokenList))
elif argtype == "NAME":
# Parse a single name (this is used in FOR loops)
# Parse a single name (used in FOR loops)
if type(tokenList[0]) is tokens.Name:
statement.append(tokenList.pop(0))
else:
err.die("Expected name, got", tokenList[0])
elif argtype == "NAMES":
# Parse 1 or more names (used in UNIFY)
if type(tokenList[0]) is tokens.Name:
nameList = []
while type(tokenList[0]) is tokens.Name:
nameList.append(tokenList.pop(0))
statement.append(nameList)
else:
err.die("Expected name, got", tokenList[0])
else:
#!print("expression", tokenList)
# The arg is an expression (whether LVAL or RVAL)
Expand Down
2 changes: 1 addition & 1 deletion pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from errors import FatalError
import sys, argparse

VERSION = "0.15.04.23"
VERSION = "0.15.04.26"

def pip(interactive=True):
if interactive:
Expand Down
2 changes: 1 addition & 1 deletion ptypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def __init__(self, value, upperVal=None):

def copy(self):
return Range(self._lower,
self._upper if self_upper is not None else nil)
self._upper if self._upper is not None else nil)

def getLower(self):
return self._lower
Expand Down

0 comments on commit 1d329fa

Please sign in to comment.