| Home | Documents | Code Breakdown | Source Code | Wiki | GIT Web | Bugs (68 open) |
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
- 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)
- (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)
- 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.
- =~ : (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 2You 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)
