Skip to content

Commit

Permalink
Various bugfixes and improvements.
Browse files Browse the repository at this point in the history
- Issue #10: Added p == "()"
- Issue #11: Destructuring assignment is now allowed in For loop headers
- Issue #23: Fixed scanning of Pattern literals
- Issue #25: Changed precedence of unary # A C AB SG FB to be lower than binary @
- Assigning to r now seeds the random number generator consistently
- Terminating with an error now gives exit code 1
  • Loading branch information
dloscutoff committed Jul 3, 2021
1 parent 03cee4b commit efee67b
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 61 deletions.
2 changes: 1 addition & 1 deletion docs/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Loop as long as the condition is false; stop when it becomes true.

foreach(i in l) {...}

Loops the variable `i` over each item in `l`. Legal types for the iteration object are scalar, list, and range. The variable must be a single identifier; this requirement allows the following very useful syntax with the unary range operator:
Loops the variable `i` over each item in `l`. The variable must be either a single identifier or a (possibly nested) list of identifiers in square brackets. Legal types for the iteration object are Scalar, List, and Range. Expressions that start with a unary operator are fine:

Fi,20{...}

Expand Down
6 changes: 3 additions & 3 deletions docs/Precedence table.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Multiplication and division | Binary | Left | `*` <br> `/` <br> `%` <br> `//` |
Unary arithmetic operators | Unary | – | `+` <br> `-` <br> `/` <br> `%` | Pos <br> Neg <br> Invert <br> Mod2 | None <br> None <br> None <br> None | List <br> Both <br> Both <br> Both | Yes <br> Yes <br> Yes <br> Yes
Exponentiation | Binary | Right | `E`/`**` <br> `EE` <br> `RT` | Pow <br> Poweroften <br> Root | `1` <br> `1` <br> `1` | Both <br> Both <br> Both | Yes <br> Yes <br> Yes
High-precedence numeric operators | Unary | – | `E`/`**` <br> `EE` <br> `RT` <br> `SQ` <br> `SI` <br> `CO` <br> `TA` <br> `SE` <br> `CS` <br> `CT` <br> `AT` <br> `RD` <br> `DG` <br> `EX` <br> `LN` | Pow <br> Poweroften <br> Sqrt <br> Square <br> Sine <br> Cosine <br> Tangent <br> Secant <br> Cosec <br> Cotan <br> Arctan <br> Radians <br> Degrees <br> Exponential <br> Naturallog | None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None | Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both <br> Both | Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes
From-base | Binary | Left | `FB` | Frombase | `0` | Both | Yes
Increment/decrement | Unary | – | `U` <br> `D` | Inc <br> Dec | None <br> None | List <br> List | Yes <br> Yes
Very high-precedence operators | Binary | Left | `FB` | Frombase | `0` | Both | Yes
Very high-precedence operators | Unary | – | `U` <br> `D` <br> `#` <br> `A` <br> `C` <br> `AB` <br> `SG` <br> `FB` | Inc <br> Dec <br> Len <br> Asc <br> Chr <br> Abs <br> Sign <br> Frombase | None <br> None <br> None <br> None <br> None <br> None <br> None <br> None | List <br> List <br> No <br> Both <br> Both <br> Both <br> Both <br> Both | Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes
Highest-precedence operators | Binary | Left | `@` <br> `@<` <br> `@>` | At <br> Leftof <br> Rightof | None <br> None <br> None | No <br> No <br> No | Yes <br> Yes <br> Yes
Highest-precedence operators | Unary | – | `@` <br> `@<` <br> `@>` <br> `++` <br> `--` <br> `#` <br> `A` <br> `C` <br> `AB` <br> `SG` <br> `FB` | At <br> Leftof <br> Rightof <br> Inc <br> Dec <br> Len <br> Asc <br> Chr <br> Abs <br> Sign <br> Frombase | None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None <br> None | No <br> No <br> No <br> List <br> List <br> No <br> Both <br> Both <br> Both <br> Both <br> Both | Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes <br> Yes
Highest-precedence operators | Unary | – | `@` <br> `@<` <br> `@>` <br> `++` <br> `--` | At <br> Leftof <br> Rightof <br> Inc <br> Dec | None <br> None <br> None <br> None <br> None | No <br> No <br> No <br> List <br> List | Yes <br> Yes <br> Yes <br> Yes <br> Yes
2 changes: 1 addition & 1 deletion docs/Variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Note: any sequence of two uppercase letters that isn't a command or an operator

`o` 1

`p` TBD; currently initialized to nil
`p` `"()"`

`s` Space character

Expand Down
8 changes: 4 additions & 4 deletions execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,15 +451,15 @@ def getr(self):
return Scalar(random.random())

def setr(self, rhs):
random.seed(rhs)
random.seed(str(rhs))

################################
### Pip built-in commands ###
################################

def FOR(self, loopVar, iterable, code):
"""Execute code for each item in iterable, assigned to loopVar."""
loopVar = Lval(loopVar)
loopVar = self.evaluate(loopVar)
iterable = self.getRval(iterable)
try:
iterator = iter(iterable)
Expand Down Expand Up @@ -557,7 +557,7 @@ def WIPEGLOBALS(self):
"m": Scalar("1000"),
"n": Scalar("\n"),
"o": Scalar("1"),
#p
"p": Scalar("()"),
#q is a special variable
#r is a special variable
"s": Scalar(" "),
Expand Down Expand Up @@ -3553,7 +3553,7 @@ def __str__(self):
evaluated = "=" + str(self.evaluated)
else:
evaluated = ""
string = "Lval({})".format(str(self.base) + evaluated + slices)
string = f"Lval({self.base}{evaluated}{slices})"
return string

def __eq__(self, rhs):
Expand Down
47 changes: 26 additions & 21 deletions operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ def __str__(self):
return self._text

def __repr__(self):
return "Command({},{},{})".format(self._text,
self.function,
self.argtypes)
return f"Command({self._text},{self.function},{self.argtypes})"

class Operator(tokens.Token):
def __init__(self, token, function, arity, precedence, associativity,
Expand All @@ -40,21 +38,28 @@ def __init__(self, token, function, arity, precedence, associativity,
elif type(default) is list:
self.default = ptypes.List(default)
else:
print("Unsupported operator default value type:", type(default))
print("Unsupported default value type for operator "
f"{token} ({function}):", type(default))
self.default = ptypes.nil

def __str__(self):
return (("$" if self.fold else "")
+ self._text
+ ("*" if self.map else "")
+ (":" if self.assign else ""))
opString = self._text
if self.fold:
opString = "$" + opString
if self.map:
if self._text + "*" in operators:
# Add a space to mapped versions of operators like @
# to prevent ambiguity with @* operator
opString += " *"
else:
opString += "*"
if self.assign:
opString += ":"
return opString

def __repr__(self):
return "Operator({},{},{},{},{})".format(str(self),
self.function,
self.arity,
self.precedence,
self.associativity)
return (f"Operator({self},{self.function},{self.arity},"
f"{self.precedence},{self.associativity})")

def copy(self):
cpy = Operator(self._text,
Expand All @@ -73,7 +78,7 @@ def copy(self):
# Each entry in the command table contains the command symbol, the function,
# and a list of parsing items that the command expects:
# NAME - a single variable name
# NAMES - one or more variable names
# LOOPVAR - a single name, or multiple names in square braces
# EXPR - any expression
# CODE - a single statement, or a block of statements in curly braces
# ELSE - the E token followed by CODE
Expand Down Expand Up @@ -355,6 +360,13 @@ def copy(self):
[1, None,
("U", "INC", None, VALS | IN_LAMBDA),
("D", "DEC", None, VALS | IN_LAMBDA),
("#", "LEN", None, RVALS | IN_LAMBDA),
("A", "ASC", None, RVALS | IN_LAMBDA | RANGE_EACH),
("C", "CHR", None, RVALS | IN_LAMBDA | RANGE_EACH),
("AB", "ABS", None, RVALS | IN_LAMBDA | RANGE_EACH),
("SG", "SIGN", None, RVALS | IN_LAMBDA | RANGE_EACH),
("FB", "FROMBASE", None, RVALS | IN_LAMBDA | RANGE_EACH),
# Unary mnemonic: FromBinary
],
[2, "L",
("@", "AT", None, VALS | IN_LAMBDA),
Expand All @@ -367,13 +379,6 @@ def copy(self):
("@>", "RIGHTOF", None, VALS | IN_LAMBDA),
("++", "INC", None, VALS | IN_LAMBDA),
("--", "DEC", None, VALS | IN_LAMBDA),
("#", "LEN", None, RVALS | IN_LAMBDA),
("A", "ASC", None, RVALS | IN_LAMBDA | RANGE_EACH),
("C", "CHR", None, RVALS | IN_LAMBDA | RANGE_EACH),
("AB", "ABS", None, RVALS | IN_LAMBDA | RANGE_EACH),
("SG", "SIGN", None, RVALS | IN_LAMBDA | RANGE_EACH),
("FB", "FROMBASE", None, RVALS | IN_LAMBDA | RANGE_EACH),
# Unary mnemonic: FromBinary
],
]

Expand Down
29 changes: 23 additions & 6 deletions parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,9 @@ def parseStatement(tokenList):
# Parse a code block
statement.append(parseBlock(tokenList))
elif argtype == "LOOPVAR":
# Parse a single name (used in FOR loops)
# TODO: allow for a (possibly nested) list of names
if type(tokenList[0]) is tokens.Name:
statement.append(tokenList.pop(0))
else:
err.die("Expected name, got", tokenList[0])
# Parse a single name or a (possibly nested) list of
# names (used in FOR loops)
statement.append(parseNameList(tokenList))
# A semicolon after the loop variable is unnecessary
# but legal
if tokenList[0] == ";":
Expand All @@ -64,6 +61,26 @@ def parseStatement(tokenList):
statement = parseExpr(tokenList)
return statement

def parseNameList(tokenList):
"Parse a (possibly nested) list containing names."
if isinstance(tokenList[0], tokens.Name):
return tokenList.pop(0)
elif tokenList[0] == "[":
tokenList.pop(0)
nameList = [operators.enlist]
while tokenList[0] != "]":
nameList.append(parseNameList(tokenList))
tokenList.pop(0)
if len(nameList) == 1:
# No names in the list, just the enlist operator
err.die("List of names in for-loop header cannot be empty")
return nameList
elif tokenList[0] is None:
err.die("Unterminated list of names in for-loop header")
else:
err.die("For-loop header must be name or list of names, not",
tokenList[0])

def parseBlock(tokenList):
"Parse either a single statement or a series of statements in {}."
if tokenList[0] == "{":
Expand Down
42 changes: 22 additions & 20 deletions pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from execution import ProgramState
from errors import FatalError

VERSION = "0.21.06.22"
VERSION = "0.21.07.03"

def pip(code=None, argv=None, interactive=True):
if code is not None or argv is not None:
Expand Down Expand Up @@ -152,8 +152,8 @@ def pip(code=None, argv=None, interactive=True):
# Treat first non-option arg as name of code file
options.file = options.args.pop(0)
else:
print("Type {} -h for usage information.".format(sys.argv[0]))
return
print(f"Type {sys.argv[0]} -h for usage information.")
sys.exit(0)
if code:
# Code is passed into function
program = code
Expand All @@ -169,7 +169,7 @@ def pip(code=None, argv=None, interactive=True):
program = f.read()
except:
print("Could not read from file", options.file, file=sys.stderr)
return
sys.exit(1)
elif options.stdin:
# Get code from stdin, stopping at EOF
program = ""
Expand All @@ -183,7 +183,7 @@ def pip(code=None, argv=None, interactive=True):
except FatalError:
print("Fatal error while scanning, execution aborted.",
file=sys.stderr)
return
sys.exit(1)
if options.verbose:
print(addSpaces(tokens))
print()
Expand All @@ -192,7 +192,7 @@ def pip(code=None, argv=None, interactive=True):
except FatalError:
print("Fatal error while parsing, execution aborted.",
file=sys.stderr)
return
sys.exit(1)
if options.verbose:
pprint.pprint(parse_tree)
print()
Expand All @@ -213,37 +213,36 @@ def pip(code=None, argv=None, interactive=True):
try:
arg_tokens = scan(arg)
except FatalError:
print("Fatal error while scanning argument {}, "
"execution aborted.".format(repr(arg)),
print(f"Fatal error while scanning argument {arg!r}, "
"execution aborted.",
file = sys.stderr)
return
sys.exit(1)
try:
arg_parse_tree = parse(arg_tokens)
except FatalError:
print("Fatal error while parsing argument {}, "
"execution aborted.".format(repr(arg)),
print(f"Fatal error while parsing argument {arg!r}, "
"execution aborted.",
file = sys.stderr)
return
sys.exit(1)
parsed_arg = arg_parse_tree[0]
try:
program_args.append(state.executeStatement(parsed_arg))
except FatalError:
print("Fatal error while evaluating argument {}, "
"execution aborted.".format(repr(arg)),
print(f"Fatal error while evaluating argument {arg!r}, "
"execution aborted.",
file = sys.stderr)
return
sys.exit(1)
except KeyboardInterrupt:
print("Program terminated by user while evaluating "
"argument {}.".format(repr(arg)),
f"argument {arg!r}.",
file=sys.stderr)
return
sys.exit(1)
except RuntimeError as err:
# Probably exceeded Python's max recursion depth
print("Fatal error while evaluating argument "
"{}:".format(repr(arg)),
print(f"Fatal error while evaluating argument {arg!r}:",
err,
file=sys.stderr)
return
sys.exit(1)
else:
# Treat each argument as a Scalar
program_args = [Scalar(arg) for arg in raw_args]
Expand All @@ -254,11 +253,14 @@ def pip(code=None, argv=None, interactive=True):
except FatalError:
print("Fatal error during execution, program terminated.",
file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("Program terminated by user.", file=sys.stderr)
sys.exit(1)
except RuntimeError as err:
# Probably exceeded Python's max recursion depth
print("Fatal error:", err, file=sys.stderr)
sys.exit(1)

if __name__ == "__main__":
if len(sys.argv) == 1:
Expand Down
6 changes: 3 additions & 3 deletions ptypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ def __repr__(self):
# Non-numbers must have quotes
if '"' in self._value:
# Use escaped-string format
return r'\"{}\"'.format(self._value.replace("\\", r"\\"))
return r'\"' + self._value.replace("\\", r"\\") + r'\"'
else:
# Use normal string format
return '"{}"'.format(self._value)
return f'"{self._value}"'

def __int__(self):
m = intRgx.match(self._value.lstrip())
Expand Down Expand Up @@ -213,7 +213,7 @@ def __str__(self):
return self._raw

def __repr__(self):
return '`{}`'.format(self._raw.replace("`", "\\`"))
return "`" + self._raw.replace("`", r"\`") + "`"

def __bool__(self):
"""A Pattern is false iff it is empty."""
Expand Down
2 changes: 1 addition & 1 deletion scanning.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

nameRgx = re.compile(r"[A-Z]+|[a-z_]|\$[][()$`'0-9]")
stringRgx = re.compile(r'"[^"]*"')
patternRgx = re.compile(r'`(\\`|\\\\|[^`])*`')
patternRgx = re.compile(r'`(\\`|\\\\|[^`\\])*`')
charRgx = re.compile(r"'(.|\n)")
escStringRgx = re.compile(r'\\"(\\[^"]|[^\\])*\\"')
numberRgx = re.compile(r'\d+(\.\d+)?')
Expand Down
2 changes: 1 addition & 1 deletion tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __repr__(self):
className = str(type(self))[8:-2]
if className[:7] == "tokens.":
className = className[7:]
return "{}({})".format(className, self._text)
return f"{className}({self._text})"

class Command(Token):
pass
Expand Down

0 comments on commit efee67b

Please sign in to comment.