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:

A left-hand-side is one of:

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:

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 ;:

  1. Evaluate expression1 to a value v.
  2. Evaluate expression2 and yield its result.

Evaluating the If Expression

To evaluate if (expression1) then expression2 else expression3:

  1. Evaluate expression1 to a value v.
  2. If v is the value false, then evaluate expression3 and yield its result.
  3. Otherwise, evaluate expression2 and yield its result.

Evaluating the For Expression

To evaluate for (init; test; update) { body }:

  1. Evaluate init to an value v.
  2. Evaluate test to a value v2.
  3. If v2 is false, the yield v as the result of the whole expression.
  4. Otherwise
    1. Evaluate body to a value v3.
    2. Evaluate update to a value v4.
    3. Evaluate test to a value v5.
    4. If v5 is the value false, then yield v3 as the result of the whole expression.
    5. Otherwise, go to sub-step 4.1 above and repeat.

Evaluating the While Expression

To evaluate while (test) { body }:

  1. Evaluate test to a value v.
  2. If v is the value false, then yield false as the result of the whole expression
  3. Otherwise
    1. Evaluate body to a value v2.
    2. Evaluate test to a value v3.
    3. If v3 is the value false, then yield v2 as the result of the whole expression.
    4. Otherwise, go to sub-step 3.1 above and repeat.

Evaluating the Lambda Expression

To evaluate lambda (identifier ...) { body }:

  1. Let env be the current environment.
  2. Yield the value lambdaenv (identifier ...) { body }

Evaluating the Application Expression

To evaluate func(expr1, ..., exprn):

  1. Evaluate func to a value v
  2. Yield the result of evaluating Apply(v, expr1, ..., exprn)

Evaluating the Comparative Operators

To evaluate op(expr1, expr2), when op is < or >:

  1. Evaluate expr1 to a value v1.
  2. Evaluate expr2 to a value v2.
  3. If both v1 or v2 are numbers, then perform the Racket op operation on v1 and v2 (this always yields either true or false).
  4. 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:

  1. Evaluate expr1, ..., exprn to a list of values v1, ..., vn, starting with expr1, from left to right.
  2. If all of v1, ..., vn are numbers, then yield the result of applying the Racket + operation to v1, ..., vn
  3. If all of v1, ..., vn are strings, then yield the result of applying the Racket string-append operation to v1, ..., vn
  4. Otherwise, yield error("Bad arguments to +")

Evaluating the Subtraction Operation

To evaluate the expression -(expr1, ..., exprn), for n > 0:

  1. Evaluate expr1, ..., exprn to a list of values v1, ..., vn, starting with expr1, from left to right.
  2. If n is 1 and v1 is a number, then yield v1.
  3. Otherwise, if all of v1, ..., vn are numbers, then yield the result of applying the Racket - operation to v1, ..., vn
  4. Otherwise, yield error("Bad arguments to -")

Evaluating the == Operation

To evaluate the expression ==(expr1, expr2):

  1. Evaluate expr1 to a value v1.
  2. Evaluate expr2 to a value v2.
  3. Yield the result of applying Equal(v1, v1)

Evaluating the Print Operator

To evaluate the expression print(expr):

  1. Evaluate expr to a value v.
  2. Output Pretty(v)

Evaluating erroneous operator patterns

To evaluate the expression op():

  1. Yield error("Empty list for prim op")

To evaluate the expression op(expr1, ..., exprn), with n > 2 and op one of < > ==:

  1. Yield error("Bad primop")

To evaluate the expression print(expr1, ..., exprn), with n > 1:

  1. Yield error("Bad primop")

Evaluating the Deffun Expression

To evaluate the expression deffun identifier(identifier1, ...) expression1 in expression2:

  1. Let store be the current store.
  2. Let location be a new location not in store.
  3. Let env be the current environment.
  4. Let env' be the result of applying AddBinding(env, identifier, location)
  5. Let v be lambdaenv'(identifier1, ...) expression1
  6. Let store' be the result of applying UpdateStore(store, location, v).
  7. Set the current store to store'
  8. Set the current environment to env'
  9. Yield the result of evaluating expression2
  10. Set the current environment back to env

Evaluating the Defvar Expression

To evaluate the expression defvar identifier = expression1 in expression2:

  1. Evaluate expression1 to a value v.
  2. Let store be the current store.
  3. Let location be a new location not in store.
  4. Let env be the current environment.
  5. Let env' be the result of applying AddBinding(env, identifier, location)
  6. Let store' be the result of applying UpdateStore(store, location, v).
  7. Set the current store to store'
  8. Set the current environment to env'
  9. Yield the result of evaluating expression2
  10. Set the current environment back to env

Evaluating the Object Expression

To evaluate the expression {identifier:expression,...}:

  1. If more than one of the identifiers in (identifier, ...) are identical, then yield error("Multiply-defined fields").
  2. Otherwise:
    1. Evaluate all the expressions from left to right, to a sequence of values v, ...
    2. Yield the value {"identifier" : v, ...}

Evaluating the Dotted Object Lookup Expression

To evaluate the expression expression.identifier:

  1. Evaluate expression to a value v.
  2. Yield the result of applying Lookup(v, "identifier")

Evaluating the Bracket Object Lookup Expression

To evaluate the expression expressionobj[expressionfld]:

  1. Evaluate expressionobj to a value vo.
  2. Evaluate expressionfld to a value vf.
  3. Yield the result of applying Lookup(vo, vf)

Evaluating the Dotted Method Expression

To evaluate the expression expression@identifier(expression1,...):

  1. Evaluate expression to a value v.
  2. Let vf be the result of evaluating Lookup(v, "identifier")
  3. Yield the result of applying Apply(vf, v, expression1, ...)

Evaluating the Bracket Method Expression

To evaluate the expression expression@[expressionfld](expression1, ...):

  1. Evaluate expressionobj to a value vo.
  2. Evaluate expressionfld to a value vf.
  3. Let vf be the result of evaluating Lookup(v, "identifier")
  4. Yield the result of applying Apply(vf, v, expression1, ...)

Evaluating the Variable Assignment Expression

To evaluate the expression identifier = expression:

  1. Evaluate expression to a value v
  2. Let location be the mapping of identifier in the current environment
  3. Let store' be the result of applying UpdateStore(store, location, v)
  4. Set the current store to store'

Evaluating the Dotted Object Assignment Expression

To evaluate the expression expressionobj.identifier = expressionnew

  1. Evaluate expressionobj to a value vobj
  2. Evaluate expressionnew to a value vnew
  3. Yield the result of applying UpdateObject(vobj, "identifier", vnew)

Evaluating the Bracket Object Assignment Expression

To evaluate the expression expressionobj[expressionfld] = expressionnew

  1. Evaluate expressionobj to a value vobj
  2. Evaluate expressionfld to a value vfld
  3. Evaluate expressionnew to a value vnew
  4. 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 -:

  1. Evaluate expression to a value v
  2. Let location be the mapping for identifier in the current environment
  3. Let vnow be the current value of the mapping for location in the current store
  4. Let vnew be the result of applying Combine(op, vnow, v)
  5. Let store' be the result of applying UpdateStore(store, location, vnew)
  6. Set the current store to be store'
  7. Yield vnew

Evaluating the Dotted Assignment Operator Expression

To evaluate the expression expressionobj.identifier op= expression, where op is + or -:

  1. Evaluate expressionobj to a value vo
  2. Let vf be the result of applying Lookup(vo, "identifier")
  3. Evaluate expression to a value v
  4. Let vnew be the result of applying Combine(op, vf, v)
  5. Yield the result of applying UpdateObject(vo, "identifier", vnew)

Evaluating the Bracket Assignment Operator Expression

To evaluate the expression expressionobj[expressionfld] op= expression

  1. Evaluate expressionobj to a value vo
  2. Evaluate expressionfld to a value vfld
  3. Evaluate expression to a value v
  4. Let vf be the result of applying Lookup(vo, vfld)
  5. Let vnew be the result of applying Combine(op, vf, v)
  6. Yield the result of applying UpdateObject(vo, vfld, vnew)

Evaluating the Post-Increment Operator Expression

To evaluate the expression identifier++:

  1. Let location be the mapping of identifier in the current environment
  2. Let v be the mapping of location in the current store
  3. Let v' be the result of applying Combine(+, 1 v)
  4. Let store' be the result of UpdateStore(location, v')
  5. Yield v

Evaluating the Pre-Increment Operator Expression

To evaluate the expression ++identifier:

  1. Let location be the mapping of identifier in the current environment
  2. Let v be the mapping of location in the current store
  3. Let v' be the result of applying Combine(+, 1 v)
  4. Let store' be the result of UpdateStore(location, v')
  5. Yield v'

Evaluating the Post-Decrement Operator Expression

To evaluate the expression identifier--:

  1. Let location be the mapping of identifier in the current environment
  2. Let v be the mapping of location in the current store
  3. Let v' be the result of applying Combine(-, 1 v)
  4. Let store' be the result of UpdateStore(location, v')
  5. Yield v

Evaluating the Pre-Decrement Operator Expression

To evaluate the expression --identifier:

  1. Let location be the mapping of identifier in the current environment
  2. Let v be the mapping of location in the current store
  3. Let v' be the result of applying Combine(-, 1 v)
  4. Let store' be the result of UpdateStore(location, v')
  5. Yield v'

Metafunctions

Combine(op, value1, value2)

  1. If both v1 and v2 are numbers, yield the result of applying the Racket op operation to them
  2. If both v1v2 are strings, and op is +, yield the result of applying the Racket string-append operation to them
  3. For any other combination of values and operators, yield error("Bad primop")

Apply(value, expr1, ..., exprn)

  1. 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)
  2. Otherwise:
    1. Evaluate expr1, ..., exprn to a sequence of values v1, ..., vn starting with expr1 and going from left to right.
    2. 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").
    3. Otherwise:
      1. Let env' be a copy of env.
      2. Let store' be a copy of store.
      3. Create n new locations, l1, ..., ln, that don't exist in the store.
      4. 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).
      5. 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).
      6. Set the current environment to env'.
      7. Set the current store to store'
      8. Evaluate body to a value v.
      9. Set the current environment back to env
      10. Yield v as the result of the entire expression.

Lookup(vo, vf)

  1. 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).
  2. 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).
  3. If vo does not have a field named vf, yield error(S), where S is the string "Field not found: " concatenated with vf.
  4. Otherwise, yield the value of the field named vf in vo.

UpdateObject(vobj, vf, vnew)

  1. 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).
  2. 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).
  3. Otherwise:
    1. 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.
    2. Otherwise, yield a new object with the value of the field vf replaced with value vnew.

Equal(value1, value2)

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: