Thousand Parsec Component Language

Last updated: 15 July 2005

Introduction

The Thousand Parsec Component Language (TPCL) is designed for the usage by Thousand Parsec clients in the interactive designing of things.

The language is based on a subset of the Scheme language with a few minor changes. This language is close enough to the Scheme R5RS definition that any scheme interpreter which is compatible with this definition should be able to be used with little modification.

Scheme was choose for these reasons,

  • Scheme is extremely simple.
  • Scheme has already be used as an embedded language.
  • Scheme has a large number of resources for learning and coding scheme.
  • Scheme has interpreters written in C, C++, Python and Java (and many other languages).
  • Scheme has plenty of academic documentation on implementing your own scheme interpreter and it turns out to be a pretty trivial task.

Aim

The aim is to allow all designs to be made client side without any interaction with the server. This should allow instant feedback about the properties, makeup and validity of the design. This should make the designing experience much more pleasant for the user.

Key

  • TPCL code is marked like this.

Quick Overview

TPCL has the followings types,

  • Reals, Implemented be implemented so that precision is that of a standard double
  • Booleans,
  • Chars and Strings, All Strings are in UTF-8
  • Pairs and Lists
  • Design Structure
TPCL does not support the following normal schemetypes,
  • Rationals, Rational numbers are not supported, use Reals instead
  • Complex, Complex numbers are not supported, use a Pair of Reals instead
  • Vectors, Vectors are not supported, use Lists instead
  • Integers, Integers are not supported, use Reals instead
  • Object/Classes, there should be no use for these as TPCL is only for small functions with no persistent state

TPCL also does not include the following normal scheme features,

  • Ability to define new structures
  • Any input or output (include all functions related to input and output)
  • Macros

Details

TPCL is based on the Scheme R5RS standard.

The following section will highlight the differences between the R5RS standard. Both differences and compatibilities are listed for each section of the R5RS. Any section which is not mentioned should be assumed to be not relevant (to compatibility/difference).

1 Overview of Scheme

This section is not relevant as it is only a summary of other sections.

2 Lexical conventions

TPCL is completely compatible with this section.

3 Basic concepts

3.1 Variables, Syntactic Keywords and Regions

TPCL is compatible with this section. However some of the functions referenced in this section are not available. See Section 4 for more details.

3.2 Disjointness of Types

TPCL is compatible with this section. However neither vector or port types (and associated functions) are available.

3.3 External Representations

TPCL is compatible with this section. However that because there is no input or output neither "read" and "write" are implemented.

3.4 Storage Model

Full.

3.5 Proper Tail Recursion

TPCL does not require a full tail recursion implementation, however it is highly recommended.

4 Expressions

4.1 Primitive expression types

TPCL is completely compatible with this section

4.2 Derived expression types

4.2.1 Conditionals

TPCL is completely compatible with this section.

4.2.2 Bindings constructs

TPCL only has let and let*, letrec is not available.

4.2.3 Sequencing

TPCL does not have separate begin statement. All functions which have implicit begins still retain the normal behavior. This is because there are no cases in TPCL where a begin by itself makes sense.

4.2.4 Iteration

TPCL is completely compatible with this section.

4.2.5 Delayed Evaluation

TPCL does not support any form of delayed evaluation.

4.2.6 Quasiquotation

Should we support Quasiquotation?

4.3 Macros

TPCL does not support any form of macros.

5 Expressions

5.1 Programs

TPCL is typically never stored in files or entered interactively. TPCL has no "top-level" as normally found in Scheme program.

5.2 Definitions

TPCL is completely compatible with this section.

5.3 Syntax Definitions

TPCL does not support any form of syntax definitions.

6 Standard procedures

6.1 Equivalence Predicates

TPCL includes all Scheme equivalence predicates described in this section.

6.2.1 Numerical Types

TPCL only has a single numerical type which is equivalent to Schemes real.

6.2.2 Exactness

As TPCL has only Reals exactness make no sense. No exactness functions are available.

6.2.3 Implementation Restrictions

TPCL requires that IEEE 64-bit floating point standards be followed for Reals.

6.2.4 Syntax of Numerical Constants

TPCL includes all the syntax of types which are listed (except complex) but all result in the production of a real.

6.2.5 Numerical operations

TPCL does not have some operations described in this section. TPCL does not have optional operations, an available operations must be implemented.

Note that some of the definitions are slightly different because of the fact that only Reals exist in TPCL.

Available:

  • (number? obj)
  • (real? obj)
  • (= r1 r2 ...)
  • (< r1 r2 ...)
  • (> r1 r2 ...)
  • (<= r1 r2 ...)
  • (>= r1 r2 ...)
  • (zero? r)
  • (positive? r)
  • (negative? r)
  • (odd? r)
  • (even? r)
  • (max r1 r2 ...)
  • (min r1 r2 ...)
  • (+ r1 ...)
  • (* r1 ...)
  • (- r1 r2)
  • (- r)
  • (- r1 r2 ...)
  • (/ r1 r2)
  • (/ r)
  • (/ r1 r2 ...)
  • (abs r1)
  • (quotient r1 r2)
  • (remainder r1 r2)
  • (modulo n1 n2)
  • (gcd r1 ...)
  • (lcd r1 ...)
  • (floor r)
  • (ceiling r)
  • (truncate x)
  • (round x)
  • (exp r)
  • (log r)
  • (sin r)
  • (cos r)
  • (tan r)
  • (asin r)
  • (acos r)
  • (atan r)
  • (atan r1 r2)
  • (sqrt z)
  • (expt z1 z2)
Not Available:
  • (complex? obj)
  • (rational? obj)
  • (integer? obj)
  • (exact? obj)
  • (inexact? obj)
  • (rationalize x y)
  • (make-rectangular x1 x2)
  • (make-polar x3 x4)
  • (real-part z)
  • (imag-part z)
  • (magnitude z)
  • (angle z)
  • (exact->inexact z)
  • (inexact->exact z)

6.2.6 Numerical Input and Output

Not implemented.

6.3.1 Booleans

TPCL is completely compatible with this section.

6.3.2 Pairs and Lists

TPCL is completely compatible with this section.

6.3.3 Symbols

TPCL is completely compatible with this section.

6.3.4 Characters

TPCL is completely compatible with this section.

6.3.5 Strings

TPCL is completely compatible with this section.

6.3.6 Vectors

TPCL is completely compatible with this section.

6.4 Control Features

TPCL is compatible with this section however it the following procedures are not available,

  • for-each
  • force (no delay)
  • call-with-current-continuation
  • values
  • call-with-values
  • dynamic-wind

6.5 Eval

TPCL does not include an eval function.

6.6 Input and output

TPCL does not include any input or output support. No function found in this section is available.

Additional Functions

TPCL also includes the following extra functions as standard.

  • rad->deg : (num -> num)

    Converts radians into degrees

  • deg->rad : (num -> num)

    Converts degrees into radians

  • logt : (num num -> num)

    Inverse of the expt function

  • pi : real

    pi to exactly 12 decimal places

  • e : real

    e to exactly 12 decimal places

  • append : ((listof any) ... -> (listofany))
  • assq : (x (listof (cons x y)) -> (union false (cons x y)))
  • caaar : ((cons (cons (cons w (listof z)) (listof y)) (listof x)) -> w)
  • caadr : ((cons (cons (cons w (listof z)) (listof y)) (listof x)) -> (listof z))
  • caar : ((cons (cons z (listof y)) (listof x)) -> z)
  • cadar : ((cons (cons w (cons z (listof y))) (listof x)) -> z)
  • cadddr : ((listof y) -> y)
  • caddr : ((cons w (cons z (cons y (listof x)))) -> y)
  • cadr : ((cons z (cons y (listof x))) -> y)
  • car : ((cons y (listof x)) -> y)
  • cdaar : ((cons (cons (cons w (listof z)) (listof y)) (listof x)) -> (listof z))
  • cdadr : ((cons w (cons (cons z (listof y)) (listof x))) -> (listof y))
  • cdar : ((cons (cons z (listof y)) (listof x)) -> (listof y))
  • cddar : ((cons (cons w (cons z (listof y))) (listof x)) -> (listof y))
  • cdddr : ((cons w (cons z (cons y (listof x)))) -> (listof x))
  • cddr : ((cons z (cons y (listof x))) -> (listof x))
  • cdr : ((cons y (listof x)) -> (listof x))
  • cons : (x (listof x) -> (listof x))
  • cons? : (any -> boolean)
  • eighth : ((listof y) -> y)
  • empty? : (any -> boolean)
  • first : ((cons y (listof x)) -> y)
  • length : (list -> number)
  • list : (any ... (listof any) -> (listof any))
  • list : (any ... -> (listof any))
  • list* : (any ... (listof any) -> (listof any))
  • list-ref : ((listof x) natural-number -> x)
  • list? : (any -> boolean)
  • member : (any list -> (union false list))
  • memq : (any list -> (union false list))
  • memv : (any list -> (union false list))
  • null : empty
  • null? : (any -> boolean)
  • pair? : (any -> boolean)
  • rest : ((cons y (listof x)) -> (listof x))
  • reverse : (list -> list)
Strings
  • format : (string any ... -> string)
  • list->string : ((listof char) -> string)
  • make-string : (nat char -> string)
  • string-append : (string ... -> string)
  • string-copy : (string -> string)
  • string-length : (string -> nat)
  • string-ref : (string nat -> char)
  • string? : (any -> boolean)
  • substring : (string nat nat -> string)
  • sprintf : (format-spec obj ... -> string)

    Note: Not part of the R5RS standard, see here for more information.

Misc
  • =~ : (real real non-negative-real -> boolean)
  • eq? : (any any -> boolean)
  • equal? : (any any -> boolean)
  • equal~? : (any any non-negative-real -> boolean)
  • eqv? : (any any -> boolean)
  • error : (symbol string -> void)
  • identity : (any -> any)
  • struct? : (any -> boolean)

TPCL Requirement function

Look here for more information about components.

This TPCL function is called (on all components which are in a design) when adding a new component is added to a design.

If the component is allowed to be added to a design then the function should return a pair with True and a string to be displayed (empty strings are acceptable), otherwise it should return a pair with false and a string which describes the reason for not being able to add the component.

The function is given a design object which has appears as if the component was already added.

Examples

The most basic case which always allows this component to be added,

(lambda (design)
	(cons #t ""))

An more advanced case would be the "Personal Pod" which cannot be added to any Design where the radiation level is above one.

(lambda (design)
	(if (> (designtype.radiation design) 1)
		(cons #t "")
		(cons #f "Radiation Level is too high for Personal Pod to be added")
	)
)

Another example would be the "Sheep Skin" component which only provides shields on unarmed ships.

(lambda (design)
	(if (> (designtype.firepower design) 1)
		(cons #t "Sheep Skin cannot provide shields as the ship is armed")
		(cons #t "")
	)
)

TPCL Property Value function

Look here for more information about components. Look here for more information about properties.

A TPCL function which is called to work out the amount this component contributes to a property. It should return a valid number. It is given the current design.

Examples

The most basic case where the component has a constant value for this property,

(lambda (design)
	1)

A more advanced example would be the "Sheep Skin" component will only have a cloaking effect if the design has no firepower.

(lambda (design)
	(if (> (designtype.firepower design) 0)
		0 100))

This function can not depend on any property which has an rank which is less then or equal to this property.

For example if the "firepower" property was order 1 and the "cloaking" property was order 0, then the cloaking property can not depend on the firepower property. This would mean the "Sheep Skin" would be an invalid component. This is needed to stop circular dependency such as,

  • "Blaster", only provides firepower if the ship has no shields
  • "Sheep Skin", only provides shields if the ship has no firepower

TPCL Property Calculate function

Look here for more information about properties.

A TPCL function which is called to work out how to display the property.

It should return a pair which contains the actual value and a formatted string (as describe by the protocol3.php document) which will be displayed (The string could be anything from "3.39 psi" to "35 Sheep of Possible 100 Sheep").

It is called with the current design and a list containing the value each component contributes to the property.

It is important that the function is not dependent on the list being in any order. If order is required the function must first sort it. (For example calling with [10, 4, 5] should return the same result as calling with [5, 4, 10] or [4, 5, 10].)

This function can not depend on any property which has an rank which is less then or equal to this property.

Examples

A simple linear example which displays "10 PSI",

(lambda (design bits)
	(let ((n (apply + bits)))
		(cons n
			(string-append (number->string n) " PSI")
		)
	)
)

A more advanced linear example which displays different units depending on the value of the input

(lambda (design bits)
	(let ((n (apply + bits)))
		(cond			  
			((< n 100) (cons n (string-append n " grams")) )  
			((< n 1000) (cons (/ n 100) (string-append n " kilograms")) )
			((< n 10000) (cons (/ n 1000) (string-append n " tons")) )
		)
	)
)

A compound example which depends only on other properties, calculates acceleration,

(lambda (design bits)
	(let ((n (/ (designtype.force design) (designtype.mass design))))
		(cons n (string-append n " m/s^2"))
	)
)

A compound example which depends on both other properties and the bits, calculates the cloaking,

(lambda (design bits)
	(let ((n (/ (apply + bits) (designtype.mass design)))
		(set! n (/ n (n+1)))
		(cons n (string-append (number->string n) " %"))
	)
)

Implmentation Tips

At first figuring out how to calculate all this seems hard, but it turns out to be quite easy.

Firstly we need to understand the difference between the a Property and the PropertyValue on each component. A PropertyValue evaluates to the value of the property for a specific component. A Property takes a list of values (that evaluating PropertyValues has produced) and calculates the overall value of the property for the design.

With this in mind it is easy to see how to calculate the design.

Step 1

Loop over all the components in a design and collect their properties into a list ordered on their order.

The following code in pseudo-python should serve as an example,

 properties = {}
 for component in components:
	 for propertyvalue in component.properties:
		 order = propertyvalue.property.order
		 if not properties.has_key(order):
			 properties[order] = []
		 if propertyvalue.property not in properties[order]:
			 properties[order].append(propertyvalue.property)
With the following input,
 components = [
   <Large Solar Sail [
	   PropertyValue(EnergyPerYear)]>
   <Large Jump Engine [
	   PropertyValue(RequiredEnergy),
	   PropertyValue(RechargeTime)]>
 ] 
 # Where EnergyPerYear and RequiredEnergy are both order 1 and RechargeTime is order 2
You get the following output
 { 1: [Property(EnergyPerYear), Property(RequiredEnergy)],
   2: [Property(RechargeTime)]}

Step 2

Create an empty "design" storage object

Step 3

Loop over the newly created collection, for each property, evaluate the property value for each component and then evaluate the property with the property values.

Something like the below pseudo-python code should serve as an example,
 keys = properties.keys()
 keys.sort()

 for key in keys:
	 for property in properties[key]:
		 bits = []

		 # This evaluates the property value for each component
		 for component in components:
			 if component.has_property(property):
				 bits.append(component.get_property(property.name).eval(design))

		 # This evaluates the overall value of the property
		 value, text = property.eval(design, bits)
		 setattr(design, property.name, value)

		 # Do something with the property text here...
		 do_something(text)