ParselTongue 0.1
Chapter 0: Introduction and Motivation
This document is an informative specification of the ParselTongue programming language at version 0.1. ParselTongue's implementation is the normative language definition, but this document strives to document its functionality in accurate detail.
Chapter 1: Syntax
This section presents the syntax of ParselTongue, which defines the expression forms used for evaluation. Examples are given for each kind of expression.
An expression is one of:
number
42
Note: ParselTongue implementations may fail to parse negative numbers. This dubious design choice will be revisited in later versions of the language.
string
"Are you having fun yet?"
true
true
false
false
expression
;
expression;
5; "another expression";
if (
expression) then
expressionelse
expressionif true then "passed" else "failed"
for (
expression;
expression;
expression) {
expression}
defvar x = 0 in { for(x = 0; <(x,5); x++) { print(x); print("\n"); }; print("Another print statement!"); } # You cannot include another expression here
while (
expression) {
expression}
while(true) { print("infinite loops are bad for the environment!"); }
lambda (
identifier ...) {
expression}
(lambda(x) { x })(4)
expression(expression, ...)
myFunction(1, 2, 3)
deffun
identifier(
identifier,
...)
expressionin
expressiondeffun not(b) if b then false else true in { if (true) then print(not(true)) else 5 } # multiple deffuns and defvars can appear in a row, but the final body # must be enclosed in {} defvar x = 0 in deffun inc() x++ in deffun dec() x-- in { inc(); dec(); dec(); inc(); }
defvar
identifier=
expressionin
expression# You can also nest deffuns and defvars deffun not(b) if b then false else true in defvar x = 0 in { if ==(x, 0) then print("Passed") else print("Failed") ; x; }
{ identifier : expression, ... }
{ prop1: 'a property', prop2: 'another property' }
expression
@
identifier(
expression,
...)
# @ provides an object as an implicit argument defvar o = {f:lambda(self) { self.x }, x:3} in { o@f() }
expression
@[
expression](
expression,
...)
defvar o = {fg:lambda(self) { self.x }, x:3} in { o@[+("f", "g")]() }
left-hand-side
see below
left-hand-side
=
expressiono = 3 o.x = 3 o[+("baz", "x")] = 22
left-hand-side
+=
expressionleft-hand-side
-=
expressiono += 3 o.x -= 5 o[+("foo", "bar")] += "hello"
- identifier
++
- identifier
--
++
identifier--
identifierop
(
expression,
...)
print("This will go to standard out") <(4, 5) ==(5, 5)
A left-hand-side is one of:
- identifier
- expression
[
expression]
- expression
.
identifier
An op is one of:
< > + - == print
You can also comment an entire line by starting it with "#";
Whitespace is not significant.
Chapter 2: Values, Answers, and Output
There are several types of values that a ParselTongue program might evaluate to. These are written in boldface to distinguish them from expressions.
A value is one of:
- number, corresponding to Racket numbers
- string, corresponding to finite sequences of Unicode characters
- true
- false
- {string : value, ...} (an object with strings for field names and values for all fields)
- lambdaenvironment (identifier, ...) { expression } (a function paired with the environment it was evaluated in)
A ParselTongue program may also terminate its computation and signal an error, which is a valid program result, but not a value. An error is always associated with a string value:
An error is: error(string)
Collectively, errors and values are referred to as answers.
Chapter 3: Evaluating Expressions
When evaluating expressions, there is always a current environment, and for each program, there is a single store. The environment maps identifiers to locations, and the store maps locations to values.
Sometimes, evaluating an expression results in an error. If evaluating an expression ever results in an error, the entire evaluation stops with that error as the result. Thus, we write that sub-expressions evaluate to values, rather than answers, because if they had evaluated to an error, computation would have stopped already.
Evaluating the Number Expression
The expression number evaluates to the result of performing the
string->number
Racket function call on number.
Evaluating the String Expression
The expression string evaluates to the string value string.
Evaluating the True Expression
The expression true evaluates to the value true.
Evaluating the False Expression
The expression false evaluates to the value false.
Evaluating the Sequence Expression
To evaluate expression1;
expression2 ;
:
- Evaluate expression1 to a value v.
- Evaluate expression2 and yield its result.
Evaluating the If Expression
To evaluate if (
expression1) then
expression2 else
expression3:
- Evaluate expression1 to a value v.
- If v is the value false, then evaluate expression3 and yield its result.
- Otherwise, evaluate expression2 and yield its result.
Evaluating the For Expression
To evaluate for (
init;
test;
update) {
body }
:
- Evaluate init to an value v.
- Evaluate test to a value v2.
- If v2 is false, the yield v as the result of the whole expression.
- Otherwise
- Evaluate body to a value v3.
- Evaluate update to a value v4.
- Evaluate test to a value v5.
- If v5 is the value false, then yield v3 as the result of the whole expression.
- Otherwise, go to sub-step 4.1 above and repeat.
Evaluating the While Expression
To evaluate while (
test) {
body }
:
- Evaluate test to a value v.
- If v is the value false, then yield false as the result of the whole expression
- Otherwise
- Evaluate body to a value v2.
- Evaluate test to a value v3.
- If v3 is the value false, then yield v2 as the result of the whole expression.
- Otherwise, go to sub-step 3.1 above and repeat.
Evaluating the Lambda Expression
To evaluate lambda (
identifier ...) {
body }
:
- Let env be the current environment.
- Yield the value lambdaenv (identifier ...) { body }
Evaluating the Application Expression
To evaluate func(
expr1,
...,
exprn)
:
- Evaluate func to a value v
- Yield the result of evaluating Apply(v, expr1, ..., exprn)
Evaluating the Comparative Operators
To evaluate op(
expr1,
expr2)
, when op is
<
or >
:
- Evaluate expr1 to a value v1.
- Evaluate expr2 to a value v2.
- If both v1 or v2 are numbers, then perform the Racket op operation on v1 and v2 (this always yields either true or false).
- Otherwise, yield error(S), where S is the concatenation of
the strings "Bad arguments for
<
:\n", Pretty(v1), "\n" and Pretty(v2).
Evaluating the Addition Operator
The addition operator +
behaves differently depending on whether it is
applied to numbers or strings. To evaluate the expression
+(
expr1,
...,
exprn)
, for n > 0:
- Evaluate expr1, ..., exprn to a list of values v1, ..., vn, starting with expr1, from left to right.
- If all of v1, ..., vn are numbers, then
yield the result of applying the Racket
+
operation to v1, ..., vn - If all of v1, ..., vn are strings, then
yield the result of applying the Racket
string-append
operation to v1, ..., vn - Otherwise, yield error("Bad arguments to +")
Evaluating the Subtraction Operation
To evaluate the expression
-(
expr1,
...,
exprn)
, for n > 0:
- Evaluate expr1, ..., exprn to a list of values v1, ..., vn, starting with expr1, from left to right.
- If n is 1 and v1 is a number, then yield v1.
- Otherwise, if all of v1, ..., vn are numbers, then
yield the result of applying the Racket
-
operation to v1, ..., vn - Otherwise, yield error("Bad arguments to -")
Evaluating the == Operation
To evaluate the expression
==(
expr1,
expr2)
:
- Evaluate expr1 to a value v1.
- Evaluate expr2 to a value v2.
- Yield the result of applying Equal(v1, v1)
Evaluating the Print Operator
To evaluate the expression print(
expr)
:
- Evaluate expr to a value v.
- Output Pretty(v)
Evaluating erroneous operator patterns
To evaluate the expression op()
:
- Yield error("Empty list for prim op")
To evaluate the expression op(
expr1,
...,
exprn)
, with n > 2 and op one of < > ==
:
- Yield error("Bad primop")
To evaluate the expression print(
expr1,
...,
exprn)
, with n > 1:
- Yield error("Bad primop")
Evaluating the Deffun Expression
To evaluate the expression deffun
identifier(
identifier1,
...)
expression1 in
expression2:
- Let store be the current store.
- Let location be a new location not in store.
- Let env be the current environment.
- Let env' be the result of applying AddBinding(env, identifier, location)
- Let v be lambdaenv'(identifier1, ...) expression1
- Let store' be the result of applying UpdateStore(store, location, v).
- Set the current store to store'
- Set the current environment to env'
- Yield the result of evaluating expression2
- Set the current environment back to env
Evaluating the Defvar Expression
To evaluate the expression defvar
identifier =
expression1 in
expression2:
- Evaluate expression1 to a value v.
- Let store be the current store.
- Let location be a new location not in store.
- Let env be the current environment.
- Let env' be the result of applying AddBinding(env, identifier, location)
- Let store' be the result of applying UpdateStore(store, location, v).
- Set the current store to store'
- Set the current environment to env'
- Yield the result of evaluating expression2
- Set the current environment back to env
Evaluating the Object Expression
To evaluate the expression {
identifier:
expression,
...}
:
- If more than one of the identifiers in (identifier, ...) are identical, then yield error("Multiply-defined fields").
- Otherwise:
- Evaluate all the expressions from left to right, to a sequence of values v, ...
- Yield the value {"identifier" : v, ...}
Evaluating the Dotted Object Lookup Expression
To evaluate the expression expression.identifier:
- Evaluate expression to a value v.
- Yield the result of applying Lookup(v, "identifier")
Evaluating the Bracket Object Lookup Expression
To evaluate the expression expressionobj[expressionfld]:
- Evaluate expressionobj to a value vo.
- Evaluate expressionfld to a value vf.
- Yield the result of applying Lookup(vo, vf)
Evaluating the Dotted Method Expression
To evaluate the expression expression@
identifier(
expression1,
...)
:
- Evaluate expression to a value v.
- Let vf be the result of evaluating Lookup(v, "identifier")
- Yield the result of applying Apply(vf, v, expression1, ...)
Evaluating the Bracket Method Expression
To evaluate the expression expression@[
expressionfld](
expression1,
...)
:
- Evaluate expressionobj to a value vo.
- Evaluate expressionfld to a value vf.
- Let vf be the result of evaluating Lookup(v, "identifier")
- Yield the result of applying Apply(vf, v, expression1, ...)
Evaluating the Variable Assignment Expression
To evaluate the expression identifier =
expression:
- Evaluate expression to a value v
- Let location be the mapping of identifier in the current environment
- Let store' be the result of applying UpdateStore(store, location, v)
- Set the current store to store'
Evaluating the Dotted Object Assignment Expression
To evaluate the expression expressionobj.identifier = expressionnew
- Evaluate expressionobj to a value vobj
- Evaluate expressionnew to a value vnew
- Yield the result of applying UpdateObject(vobj, "identifier", vnew)
Evaluating the Bracket Object Assignment Expression
To evaluate the expression expressionobj[expressionfld] = expressionnew
- Evaluate expressionobj to a value vobj
- Evaluate expressionfld to a value vfld
- Evaluate expressionnew to a value vnew
- Yield the result of applying UpdateObject(vobj, vfld, vnew)
Evaluating the Variable Assignment Operator Expression
To evaluate the expression identifier op= expression, where op is
either +
or -
:
- Evaluate expression to a value v
- Let location be the mapping for identifier in the current environment
- Let vnow be the current value of the mapping for location in the current store
- Let vnew be the result of applying Combine(op, vnow, v)
- Let store' be the result of applying UpdateStore(store, location, vnew)
- Set the current store to be store'
- Yield vnew
Evaluating the Dotted Assignment Operator Expression
To evaluate the expression expressionobj.identifier op= expression, where op is +
or -
:
- Evaluate expressionobj to a value vo
- Let vf be the result of applying Lookup(vo, "identifier")
- Evaluate expression to a value v
- Let vnew be the result of applying Combine(op, vf, v)
- Yield the result of applying UpdateObject(vo, "identifier", vnew)
Evaluating the Bracket Assignment Operator Expression
To evaluate the expression expressionobj[expressionfld] op= expression
- Evaluate expressionobj to a value vo
- Evaluate expressionfld to a value vfld
- Evaluate expression to a value v
- Let vf be the result of applying Lookup(vo, vfld)
- Let vnew be the result of applying Combine(op, vf, v)
- Yield the result of applying UpdateObject(vo, vfld, vnew)
Evaluating the Post-Increment Operator Expression
To evaluate the expression identifier++
:
- Let location be the mapping of identifier in the current environment
- Let v be the mapping of location in the current store
- Let v' be the result of applying Combine(
+
, 1 v) - Let store' be the result of UpdateStore(location, v')
- Yield v
Evaluating the Pre-Increment Operator Expression
To evaluate the expression ++
identifier:
- Let location be the mapping of identifier in the current environment
- Let v be the mapping of location in the current store
- Let v' be the result of applying Combine(
+
, 1 v) - Let store' be the result of UpdateStore(location, v')
- Yield v'
Evaluating the Post-Decrement Operator Expression
To evaluate the expression identifier--
:
- Let location be the mapping of identifier in the current environment
- Let v be the mapping of location in the current store
- Let v' be the result of applying Combine(
-
, 1 v) - Let store' be the result of UpdateStore(location, v')
- Yield v
Evaluating the Pre-Decrement Operator Expression
To evaluate the expression --
identifier:
- Let location be the mapping of identifier in the current environment
- Let v be the mapping of location in the current store
- Let v' be the result of applying Combine(
-
, 1 v) - Let store' be the result of UpdateStore(location, v')
- Yield v'
Metafunctions
Combine(op, value1, value2)
- If both v1 and v2 are numbers, yield the result of applying the Racket op operation to them
- If both v1v2 are strings, and op is
+
, yield the result of applying the Racketstring-append
operation to them - For any other combination of values and operators, yield error("Bad primop")
Apply(value, expr1, ..., exprn)
- If value is not of the form lambdaenv (identifier1, ..., identifierm) { body }, then yield error(S), where S is the string "Not a function: " concatenated with the result of Pretty(value)
- Otherwise:
- Evaluate expr1, ..., exprn to a sequence of values v1, ..., vn starting with expr1 and going from left to right.
- If n does not equal m (e.g. the argument list is a different length than the list of argument names), yield error("Application failed with arity mismatch").
- Otherwise:
- Let env' be a copy of env.
- Let store' be a copy of store.
- Create n new locations, l1, ..., ln, that don't exist in the store.
- From left to right, for i from 1 to n in the identifier list, let env' be the result of applying the metafunction AddBinding(env', identifieri, li).
- From left to right, for i from 1 to n in the location list, let store' be the result of applying the metafunction UpdateStore(store', li, vi).
- Set the current environment to env'.
- Set the current store to store'
- Evaluate body to a value v.
- Set the current environment back to env
- Yield v as the result of the entire expression.
Lookup(vo, vf)
- If vf is not a string, yield error(S), where S is the string "Non-string in field update: " concatenated with the result of Pretty(vf).
- If vo is not an object, yield error(S), where S is the string "Non-object in field lookup: " concatenated with the result of Pretty(vo).
- If vo does not have a field named vf, yield error(S), where S is the string "Field not found: " concatenated with vf.
- Otherwise, yield the value of the field named vf in vo.
UpdateObject(vobj, vf, vnew)
- If vf is not a string, yield error(S), where S is the string "Non-string in field update: " concatenated with the result of Pretty(vf).
- If vobj is not an object, yield error(S), where S is the string "Non-object in field update: " concatenated with the result of Pretty(vobj).
- Otherwise:
- If vo does not have a field named vf, yield a new object with all the fields of vobj, plus a new field that maps vf to vnew.
- Otherwise, yield a new object with the value of the field vf replaced with value vnew.
Equal(value1, value2)
- Equal(string, string) = true for identical strings
- Equal(number, number) = true for identical numbers
- Equal(true, true) = true
- Equal(false, false) = true
- Equal({string1 : value1, ...}, {string2 : value2, ...}) = true when the same strings are in the sequences {string1, ...}, and {string2, ...} (in the same order), and when Equal(value1, value2) ... for all the values, in order. (At each position, the values are pairwise equal)
- Equal( lambdaenvironment1 (identifier1, ...) { expression1 }, lambdaenvironment2 (identifier2, ...) { expression2 } ) = true when the environments map the same identifiers to Equal values, and when expression1 is the same as expression2
- Equal(v1, v2) = false in all other cases
AddBinding(env, identifier, location)
If env has a mapping for identifier, replace the mapping with one from identifier to location. Otherwise, add a mapping to env from identifier to location. Return the resulting env.
UpdateStore(store, location, value)
If store has a mapping for location, replace the mapping with one from location to value. Otherwise, add a mapping to store from location to value. Return the resulting store.
Pretty(v)
Pretty(v) provides a string value that describes the value v provided as an argument. It yields the following results:
- Pretty(string) = string
- Pretty(number) = Racket's
number->string
applied to number - Pretty(true) = "true"
- Pretty(false) = "false"
- Pretty({string : value, ...}) = "object"
- Pretty(lambdaenvironment (identifier, ...) { expression }) = "function"