4 Compact grammar
This page collects the compact syntax in one reference. The grammar and the rules below must be read together: the brackets alone cannot decide which names are visible, whether a path can be updated, or whether a call has the right inputs. These rules describe the language’s written forms. They do not define scientific adequacy or require scientific assessment applications to be written in Racket.
The standalone grammar file is included verbatim below. The explanations linked from Syntax define the meaning of each form. Compact and verbose syntax describes the normalization of verbose and mixed spellings to this compact grammar.
4.1 Grammar notation
In the grammar, = defines a rule, a comma joins parts, | offers alternatives, [ ... ] makes something optional, and { ... } repeats it zero or more times. Quoted punctuation is literal ScienceLogo punctuation. For example, "[" is a source bracket; an unquoted bracket belongs to the grammar notation. A description between question marks supplies a character or contextual condition. These rules are independent of a parser generator. The expression rules additionally use the binding procedure below.
(* ScienceLogo compact grammar |
Read together with the disambiguation and context rules |
on the manual's Compact grammar page (scribblings/grammar.scrbl). Those |
rules are part of this grammar. |
No verbose forms or scientific assessment algorithms are defined here. |
|
Notation: = defines; , sequences; | chooses; [ ] makes optional; |
{ } repeats zero or more times; ( ) groups; ? ... ? describes a condition. |
Quoted strings are literal source text, with no escape processing in EBNF. |
Thus "[" is a ScienceLogo bracket, but [ expression ] is EBNF notation. |
EOF means end of file. Lexical rules work on characters. Reference, input, |
and dotted-path rules also describe parts within word tokens: a dot is |
attached to the following segment or bracket. Other rules work on tokens. |
The expression rules also use the precedence rules in the manual. |
*) |
|
(* Files. A fragment's role is determined by the file that includes it. *) |
workflow-file = header, [ workflow-title ], [ description ], { import }, { workflow-form }, EOF ; |
library-file = header, "library", declaration-name, "[", { library-form }, "]", EOF ; |
interface-file = header, interface, EOF ; |
workflow-fragment = header, { workflow-form }, EOF ; |
library-fragment = header, { procedure | interface | include }, EOF ; |
header = ? first line, trimmed, is exactly #lang sciencelogo ? ; |
workflow-title = "workflow", text ; |
description = "about", text ; |
workflow-form = procedure | interface | include | performer | resource |
| proposition | obligation | file-provisions | command ; |
library-form = procedure | interface | include | import ; |
|
(* Words, names, numbers, and strings. Names are case-sensitive. Capitalization |
and hyphen conventions are optional; declarations determine interface names. *) |
word = word-character, { word-character } ; |
word-character = ? a non-whitespace character other than [ ] ( ) ; " # ? ; |
name = ? a word other than a reserved word, not beginning with : ? ; |
simple-name = ? a name containing no dot ? ; |
declaration-name = ? an unquoted simple name not beginning with an optional sign and a digit ? ; |
entry-name = ? a name not beginning with an optional sign and a digit ? ; |
segment = ? a nonempty dot-separated part of a word ? ; |
reference = simple-name, { ".", segment } ; |
input = ":", input-name ; |
input-name = ? a nonempty word with no dot, following : in the same token ? ; |
number = [ "+" | "-" ], digits, [ ".", digits ] ; |
digits = digit, { digit } ; |
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; |
text = '"', { string-character | escape }, '"' ; |
string-character = ? any character except double quote and backslash ? ; |
escape = "\", ( "a" | "b" | "t" | "n" | "v" | "f" | "r" | "e" |
| '"' | "'" | "\" | octal-escape | hex-escape | line-ending ) ; |
octal-escape = ? 1 to 3 octal digits, longest match, value 0 through 255 ? ; |
hex-escape = ? x then 1 to 2 hex digits; u then 1 to 4; U then 1 to 8; |
longest match; a valid Unicode scalar, or a paired u escape |
encoding a valid UTF-16 surrogate pair ? ; |
line-ending = ? LF, CR, or CR followed by LF ? ; |
|
(* Comments and whitespace are discarded between tokens, not inside text. *) |
line-comment = ";", { ? any character other than LF ? }, [ ? LF ? ] ; |
block-comment = "#|", { block-comment | block-comment-character }, "|#" ; |
block-comment-character = ? a character not starting #| or |# ? ; |
|
(* Expressions. Precedence and the stopping rules are defined in the manual. |
A prefix operator can occur wherever an operand begins. Each explicit |
parenthesis group is retained, including redundant and nested groups. *) |
expression = operand, { binary-operator, operand | "is", type-target } ; |
operand = "-", expression | "not", expression | primary, { suffix } ; |
primary = number | text | "true" | "false" | "unknown" | value-reference |
| list | record | range | proposition | "(", expression, ")" ; |
value-reference = reference | input, { ".", segment } | proposition-collection ; |
binary-operator = "+" | "-" | "*" | "/" | "==" | "!=" | "<" | "<=" |
| ">" | ">=" | "in" | "and" | "xor" | "or" ; |
type-target = "number" | "text" | "boolean" | "list" | "record" | reference ; |
list = "[", { expression }, "]" ; |
record = "[", field, { field }, "]" ; |
field = field-name, "=", expression ; |
field-name = name | proposition-collection ; |
object-fields = record | "[", "]" ; |
object = object-fields | "(", object, ")" ; |
range = "range", expression, "to", expression, "step", expression ; |
suffix = ".", segment | ".", selection | ".", object-fields | "u", uncertainty ; |
selection = "[", expression, { expression }, "]" ; |
|
(* Declarations and ordinary commands. Context rules restrict their placement. *) |
procedure = "to", simple-name, { input }, "[", [ description ], { command }, "]" ; |
block = "[", { command }, "]" ; |
command = assignment | dotted-update | call | state-call | "do", block |
| "print", expression | "output", expression |
| repeat | while | for-each | conditional | stage | mapping ; |
assignment = target, ( "=" | "+=" ), expression ; |
target = value-reference, { path-suffix } ; |
path-suffix = ".", segment | ".", selection ; |
dotted-update = target, ".", object-fields ; |
call = "do", reference, { expression }, [ "as", simple-name ] ; |
repeat = "repeat", expression, block | "repeat", block, "until", expression ; |
while = "while", expression, block ; |
for-each = "for", "each", simple-name, "in", expression, block ; |
conditional = "if", expression, block, |
{ "else", "if", expression, block }, [ "else", block ] ; |
|
(* Stages. A paired stage has entry and exit state and location-based calls. *) |
stage = "stage", declaration-name, [ description ] |
| "stage", declaration-name, [ "by", simple-name ], "from", state, |
[ description ], { command }, "end", "stage", "with", state ; |
state = "state", state-object ; |
state-object = "[", { state-set }, "]" | "(", state-object, ")" ; |
state-set = ( "information" | "environment" | "factors" ), "=", object ; |
state-call = "do", reference, [ "by", simple-name ], |
[ "with", "[", { input-mapping }, "]" ], "into", location ; |
input-mapping = simple-name, "at", location ; |
location = ( "information" | "environment" | "factors" ), ".", segment, |
{ ".", segment | ".", "[", position, "]" } ; |
position = ? a positive integer literal or a stage control name ? |
| "(", position, ")" ; |
performer = "performer", simple-name, object ; |
resource = "resource", simple-name, object ; |
|
(* Named propositions can occur as declarations or as values. *) |
proposition = proposition-type, simple-name, proposition-object ; |
proposition-type = "hypothesis" | "assumption" | "conclusion" ; |
proposition-collection = "hypotheses" | "assumptions" | "conclusions" ; |
proposition-object = "[", { proposition-field }, "]" |
| "(", proposition-object, ")" ; |
proposition-field = "text", "=", expression |
| proposition-collection, "=", proposition-children ; |
proposition-children = "[", { simple-name, "=", proposition-object }, "]" |
| "(", proposition-children, ")" ; |
|
(* Uncertainty source fields are open; only the two collection names are fixed. *) |
uncertainty = "[", { ( "declared" | "omitted" ), "=", sources }, "]" |
| "(", uncertainty, ")" ; |
sources = "[", { name, "=", object }, "]" | "(", sources, ")" ; |
|
(* Interface bodies and nested parts share their entry rules. *) |
interface = "interface", declaration-name, "[", { interface-root-entry }, "]" ; |
interface-root-entry = "version", text | "extends", reference | interface-entry ; |
interface-body = "[", { interface-entry }, "]" ; |
interface-entry = field-declaration | required-group | interface-if |
| interface-for-each | "affects", reference, reference ; |
field-declaration = entry-name, ( [ "=", default-value ] |
| "is", type-target | interface-body ) ; |
default-value = expression, [ unknown-type-suffix ] ; |
unknown-type-suffix = type-target ; |
required-group = "must", "[", required-entry, { required-entry }, "]" ; |
required-entry = field-declaration | value-requirement ; |
value-requirement = expression ; |
interface-if = "if", expression, interface-body, |
{ "else", "if", expression, interface-body }, |
[ "else", interface-body ] ; |
interface-for-each = "for", "each", simple-name, "in", expression, interface-body ; |
|
(* Mappings and top-level requirements on the written method. *) |
mapping = target, "implement", ( reference | text ) ; |
obligation = "must", "do", reference |
| "must", "stages", "in", "order", "[", reference, reference, { reference }, "]" ; |
|
(* Older supported file-reference form; locators are text, not direct mappings. *) |
file-provisions = "implements", text, "as", name, |
"[", { "provide", name, "by", text }, "]" ; |
|
(* Source dependencies are separate from external data resource paths. *) |
import = "import", text, "at", text, "as", simple-name ; |
include = "include", text ; |
|
4.2 Tokens, text, and names
After the first #lang sciencelogo line, whitespace and comments separate tokens. Newlines do not end commands. Brackets, parentheses, and quoted text separate themselves from adjacent words. Other punctuation stays inside a word: write x = 2, not x=2, and 1 + 2, not 1+2. A dot stays attached to the following path part, as in trial.period or trial.[1]; it is not an arithmetic operator. To apply a suffix directly to a numeric literal, group it, for example (2 u []).u, rather than merging the literal and path into one word. The grammar’s reference, input, and path rules describe those word parts; they do not require the lexer to emit separate dot or colon tokens. A qualified procedure or interface reference is one word. A value path can continue in a following word that starts with a dot: trial .period works, while trial . period does not.
Numbers use an optional sign, decimal digits, and an optional fractional part with digits on both sides of its dot. They are exact. Exponent notation, a leading decimal point, and a fraction such as 1/3 are not numeric literals. Division can produce an exact fraction. A sign attached to digits is part of the literal; a separate - is unary subtraction.
Quoted text may span lines. Escapes include quotes and backslash, \a, \b, \t, \n, \v, \f, \r, and \e; their character codes are respectively 7, 8, 9, 10, 11, 12, 13, and 27. Numeric and escaped-line-ending forms are listed in the grammar. An escaped line ending contributes no character. Other backslash escapes are invalid.
Syntax gives the naming conventions. Names are case-sensitive; letter case is guidance. Names in declarations and references are unquoted identifiers. This includes stage, interface, and library names. Quoted text is a string value; it cannot name or reference one of those declarations. Stage, interface, and library declaration names have no dots and do not begin with a number. A dot separates nested parts; a colon marks a procedure input and is used only inside that procedure. Input declarations and references keep the colon attached to their name. The reader treats kind labels such as number as ordinary names except in a type position. The words stages and order likewise have meaning in must stages in order without being reserved everywhere.
These words and operators are reserved as ordinary names:
to do stage end print output include implements must workflow interface about |
performer resource hypothesis assumption conclusion repeat for while if else |
set as at from with into state import library version require affects provide |
by each in until range step true false unknown = == += != < <= > >= |
+ - * / and or not xor of but implement is extends |
hypotheses assumptions conclusions u |
The plural proposition words are nevertheless valid collection roots and record keys. Reserved set, require, of, and but have no accepted compact command or operator form. Reserving a word does not implement a feature. about is accepted only at the description attachment points explained in Describe a workflow, stage, or procedure.
4.3 Expression binding
Read expressions with the following priorities, from strongest to weakest:
Priority | Forms |
Postfix | .field, .[...], u [...] |
70 | unary - |
60 | * / |
50 | + - |
40 | == != < <= > >= in is; operand of not |
30 | and |
20 | xor |
10 | or |
To read an expression with minimum priority p, first read an operand. A prefix - reads its operand with minimum 70; not reads its operand with minimum 40. An ordinary primary then consumes its postfix suffixes. While the next operator has priority at least p, consume it and read its right operand with minimum one greater than that priority. For is, consume one type reference instead of a right expression. The result becomes the left operand for the next operator. A full expression starts with minimum 0, and parentheses start a fresh full expression. This procedure gives left association within a priority and makes not period > 2 mean not (period > 2).
Unparenthesized comparison chains such as 1 < 2 < 3 are rejected; use 1 < 2 and 2 < 3 for those two conditions. A grouped comparison is a value: (1 == 1) == true and (2 is number) == true are valid. Every explicit pair of parentheses is retained in the parsed tree and workflow model, including extra groups such as (((1 == 1))). Checks and evaluation follow that grouping. Parentheses are available wherever the grammar accepts a value expression.
Range bounds and the step are full expressions: to and step stop the preceding bound. Parenthesize the range before applying a suffix to the whole list, as in (range 1 to 3 step 1).count. Similarly, (duration / swings) u [...] attaches the description to the quotient.
4.4 Choosing between bracket forms
At the start of a bracketed value, a word followed immediately by an = token selects a record. Otherwise the brackets contain list items. Thus [period = 2] is a record, [period 2] is a list, and [] is an empty list. Dotted field keys expand into nested records. Repeated or conflicting field paths are invalid. In an object position such as a performer declaration, [] denotes an empty object.
After a dot, named fields select a record update or a construction from an interface; [] is the empty update. Other nonempty brackets select fields or one-based list positions. A record selection uses bare field labels; a list selection uses index expressions. A single selection returns the field or item value. Several selected record fields return a record; several selected list positions return a list. An expression such as copy = trial.[period = 2] produces a value. The command trial.[period = 2] updates an existing writable name. See Values and expressions.
The in operator has one contextual distinction: a list receiver uses the left operand’s value, while a record receiver requires a bare unquoted field label on the left. The label identifies one immediate key, regardless of its value. It does not need a variable declaration. A nested receiver such as notes in trial.details inspects that nested record.
The grammar describes assignment targets using paths. A target must also resolve to a writable name or location. A selector used for assignment must identify one existing list position or one field; a record update may add fields. The list property .count is read only. An ordinary record’s count field remains writable. .u addresses an attached uncertainty description, which must retain the shape defined in Uncertainty descriptions.
4.5 Command boundaries and placement
The file’s role determines which declarations are allowed. A workflow title is optional and appears only first. A workflow’s optional about follows that title and precedes imports. Workflow imports precede other forms. Library imports belong directly in the root library block. Included workflow fragments have no title or imports; included library fragments contain only procedures, interfaces, and further includes. A file beginning with an interface and continuing with other forms is a workflow with an inline interface. A file containing only that declaration is a standalone interface.
A procedure’s optional about is first inside its opening bracket. A stage’s optional about immediately follows its complete header, including entry state for a paired stage. Each attachment accepts one quoted string. These descriptions are not commands in anonymous blocks and do not affect execution or checks; see Describe a workflow, stage, or procedure.
Procedure definitions, interfaces, performers, resources, proposition declarations, obligations, and the older file-provision form belong at workflow top level. Libraries contain only the declarations listed in library-form. A command block cannot define procedures or interfaces. The output command belongs inside a procedure; it returns immediately. See Procedures and control flow for value scope, protected names, and updates.
For an ordinary do name ... call, keep reading argument expressions until as, a closing bracket, end of file, a command-start word, or a recognized assignment or dotted-update start. A newline is not a boundary, and the procedure’s input count does not stop parsing arguments. The reader checks that count afterwards. The command-start words are:
to do stage end print output include implements must workflow interface |
performer resource hypothesis assumption conclusion repeat for while if else set about |
Because proposition declaration words end an argument list, enclose a new proposition value in parentheses when passing it directly as an argument.
An assignment start here is a word immediately followed by = or +=. A dotted-update start is a word ending in a dot followed by [, a field word, and =. These tests do not recognize every possible path command: for example, an indexed assignment or an empty dotted update after a call needs an explicit call boundary.
A mapping beginning with a stage, procedure, or value name does not itself end an argument list. An as result gives an explicit boundary; a mapping can also be separated by placing the call in do [...]. After a call’s as name, the next form starts normally. Expressions greedily consume operators: in do pair 1 - 2, the call receives one subtraction expression. Use do pair 1 (- 2) for two arguments.
4.6 Stage, data, and proposition constraints
A paired stage must supply both boundary state objects, even when empty. Its state sets are individually optional, but each present set contains named fields or []. Stages cannot nest inside a paired stage. Location calls are allowed only there; an ordinary value-argument call is not allowed in a paired stage. The location grammar does not permit multi-field projections or update objects. Its field parts cannot be reserved words.
Every location call maps each declared input exactly once and names a compulsory return location. A declared performer comes from the call or stage. Input paths must exist and return paths must have an existing parent; list positions must already exist. Local stage names describe integer counters and positions. Changes must account for the full closing state. These restrictions and the rules for branches and loops are detailed in Stages and state. Stage handoff is available without being compulsory. Paired bodies are preserved and checked, not executed.
All access to data outside the workflow uses a declared resource root and an ordinary dotted path. Assignment direction represents reading or writing. A resource declaration’s descriptive fields are not the data at those locations. Source dependencies use their own paths. See Data sources and storage.
A proposition must have a text field that evaluates to text, and it may have named child collections. Omitted collections are empty. Names are unique within their type and parent. Adding a named child with += rejects a duplicate; = updates fields without creating a new proposition or changing its identity. The language does not require a conclusion for every hypothesis. See Propositions.
4.7 Interface disambiguation and constraints
Inside must [...], first read a candidate expression. A comparison, is test, arithmetic or Boolean expression, unary expression, or literal is a value requirement. Otherwise read a field declaration or named group. A bare field must be written as a word, not a parenthesized expression. The group must contain at least one entry. Outside must, a bare field, field is Type, or named group is optional. A default makes its field compulsory even outside must; examples group defaults inside it.
The optional type suffix is allowed only after an explicit unknown default, including one with u. A primitive kind is recognized there immediately. A named interface reference is recognized after imports and includes are assembled, using the declarations rather than letter case. It consumes only an immediately adjacent bare entry in the same group. A following entry with its own = or is remains separate. A word that names no interface remains a separate field. Separate must groups disambiguate a field with the same name as an interface. See Records and interfaces.
An interface has a nonempty name and at most one nonempty version. It needs a field, parent, or value requirement. Only the root can use version and extends. Field paths are unique within their parent, including across branches. Parents must resolve without cycles or unresolved conflicts. A standalone interface cannot extend a local interface. affects links compulsory parts. Per-item names are local to their requirement blocks. Named type targets must resolve to an interface or nested group. See Records and interfaces.
Mapping targets may name an entire interface or an individual requirement. A stage mapping uses its declared, unquoted name, including a forward or imported qualified reference. Ambiguous targets that also identify another visible value or definition are rejected. A source-file mapping target is available for a value, not a stage or procedure. Mappings preserve the stated relationship; they do not establish scientific conformity. See Assessment support.
4.8 Resolution and implementation boundaries
All procedure, stage, interface, performer, and resource references must resolve. Declarations may follow their uses; local values must already be visible. Calls have the declared input count, requested results need an output, and recursive procedure calls are rejected. Stage names are unique across the workflow and its procedures. Procedure names are also unique; the naming rules specify conflicts with other namespaces. Includes must remain in their Git repository, without repeated files or cycles. Imports choose a tag or full commit on main; dependency aliases remain private. See Files and libraries.
Interface references are resolved from declarations after imports and includes are assembled. A library interface named timing_shape uses tools.timing_shape, including in constructors, named tests, defaults, and nested requirement mappings. Capitalization and hyphens remain optional naming conventions. Private dependency aliases remain private regardless of their spelling.
A grammar match is only the first check. Name resolution, placement, writability, and the stated structural constraints are also required. Value operations can still fail when run, for example because an index is out of bounds or division uses zero. Scientific assessments remain independent applications.