Skip to content
hhas edited this page Feb 3, 2023 · 6 revisions

iris

Syntax

Number

( '+' | '-' )? digit+ ( '.' digit+ )?

3
+3
-3
3.14

TO DO: exponent notation, e.g. 3.14e-10

TO‘ DO: lexer for matching thousands notation, e.g. 12,345,678

String

( '"' | '“' | '”' ) char* ( ( '""' | '““' | '””' ) char* )* ( '"' | '“' | '”' )

Strings are delimited by double quotes (straight or typographer’s):

"Hello"
“World”

Strings can contain any character, including linebreaks. To include a double quote, type it twice (i.e. double-quote characters escape themselves):

“Bob says ““Hello”” to you.”

Name

A name consists of one or more alphanumeric characters (the first character must not be a digit), OR one or more symbol characters:

i
document
and
+
>=
≤

Names are case-insensitive.

Multiple words in a name should always be separated by underscores:

application_file
this_is_a_name

(Always use snake_case, not camelCase, to prevent ambiguous names and to facilitate fuzzy autocomplete, including underscore insertion, and text-to-voice conversion. The pretty printer can also de-emphasize underscores in names so that the displayed text reads more naturally.)

Alphanumeric names are assumed to be ordinary command names (i.e. unreserved names). If a library’s custom operator syntax is loaded, any names reserved by those operators will be matched according to the operators’ custom syntax rules.

Symbolic names (e.g. +) are assumed to be custom operator names (i.e. reserved names). If no custom operator syntax is found for a symbolic name, it is assumed to be a syntax error.

To treat a name as an ordinary command name, regardless of whether or not it is reserved, enclose it in single quotes (straight or typographer’s):

‘document’
‘and’
‘+’
‘>=’

Symbol

'#' name

Symbols (a.k.a. “hashtags”) consist of a hash character followed by a valid name:

#document
#some_tag

Reserved alphanumeric names are treated as normal names when preceded by a hash:

#and

Symbolic names must be single-quoted when preceded by a hash:

#‘+’

Ordered list

'[' ']' | '[' expr ( sep expr )* ']'

Ordered lists are delimited by square brackets and contain an ordered sequence of 0+ values (“items”):

[]
[ 1, 2, 3 ]

Multiple items must be separated by commas and/or linebreaks:

[
  1
  2
  3
]

BUG: exprs within lists are not eagerly evaled, e.g. [ 1+1 ][ ‘+’ {1, 1} ] but should yield [ 2 ] unless explicitly coerced to expression

Key-value list

'[' : ']' | '[' key ':' expr ( sep key ':' expr )* ']'

Key-value lists are delimited by square brackets and contain an unordered sequence of 0+ colon-delimited key: value pairs (“items”):

[:]
[ “name”: “Bob”, “age”: 42 ]

Multiple items must be separated by commas and/or linebreaks:

[
  “name”: “Bob”
  “age”: 42
]

Keys are arbitrary numbers, strings, and/or symbols.

BUG: the parser currently fails on Symbol keys.

Record

'{' '}' | '{' label ':' expr ( sep label ':' expr )* '}'

Records are delimited by curly braces and contain an ordered sequence of 0+ colon-delimited label: value pairs (“properties”):

{}
{ name: “Bob”, age: 42 }

Multiple items must be separated by commas and/or linebreaks:

{
  name: “Bob”
  age: 42
}

Labels are names. Reserved alphanumeric names are treated as normal names. Symbolic names must be single-quoted.

Labels are optional:

{ “Bob”, 42 }

A record can contain both labeled and unlabeled properties. Unlabeled properties will be matched by position when coercing the record to a specific record type:

✎ { “Bob”, 42 } as record { name: text, age: integer }
☺︎ { name: “Bob”, age: 42 }

BUG: record coercions match property labels but do not coerce property values (the record constructor incorrectly ignores the given types and treats all properties as type anything).

Labeled properties can be accessed by name:

✎ name of { name: “Bob”, age: 42 }
☺︎ “Bob”

TO DO: access property name and/or value by index (need to decide semantics, since LH operand is a command [name]; thus key {at: 1} of {…} may be ambiguous if record contains a property named key); main reason for this is to enable native introspection of records

Group

'(' ')' | '(' expr ( sep expr )* ')'

Parentheses provide grouping of single expressions (e.g. to override operator precedence) or sequences of 0+ expressions (to denote blocks).

(1 + 2) * 3

( say { “Hello” }, say { “World” } )

Command

name record?

Commands are values. A command consists of a name optionally followed by an argument record of 0+ properties (“arguments”):

hello
hello {}
uppercase { “Bob” }
uppercase { text: “Bob” }
‘if’ { test: expr , then: action }

An iris script is composed almost entirely of nested and/or sequential commands. (Exceptions to this rule are other value literals such as numbers and strings, code annotations, and core punctuation.) This includes library-defined operators, which apply custom syntax and precedence rules on top of library-defined commands; e.g.:

(1 + 2) * 3

is equivalent to:

‘*’ { ‘+’ {1, 2}, 3 }    

Sequences of commands can be grouped in parentheses (blocks), e.g.:

( do_this, do_that )

An argument record’s { } braces and comma separators may be omitted for brevity (low-punctuation command syntax), e.g.:

‘if’ { test, then: action }

can be abbreviated to:

‘if’ test then: action

Caveat: when nesting low-punctuation commands, any labeled arguments are assumed to belong to the outermost command. If the inner command has any labeled arguments it must either be parenthesized or use explicit record syntax to prevent ambiguity, e.g.:

a_command { b_command { b_label: value }, a_label: value }

can be abbreviated to one of the following:

a_command b_command { b_label: value } a_label: value

a_command ( b_command b_label: value ) a_label: value

but not:

a_command b_command b_label: value a_label: value

as this will treat the b_label: value argument as belonging to a_command.

Clone this wiki locally