Language Reference

Grammar

An informal but complete EBNF grammar for Psy v1, plus the rules a grammar cannot state.

Informal, but complete for v1. Layout tokens are written INDENT, DEDENT and NEWLINE; the lexer produces them from indentation, and blank and comment-only lines never produce them.

Module        = { NEWLINE | Statement } ;

Statement     = Use | Import | ReExport | Const | Spec ;

Use           = "use" String NEWLINE ;

Import        = "import" "{" [ SpecifierList ] "}" "from" String NEWLINE ;
ReExport      = "export" "{" [ SpecifierList ] "}" "from" String NEWLINE ;
SpecifierList = Specifier { "," Specifier } [ "," ] ;
Specifier     = Identifier [ "as" Identifier ] ;

Const         = [ "export" ] "const" Identifier
                ( "=" InlineValue NEWLINE
                | ":" NEWLINE Block ) ;

Spec          = [ "export" ] ( SpecHead | KeywordHead ) ":" NEWLINE [ Body ] ;
SpecHead      = [ "abstract" ] "spec" Identifier [ Extends ] [ "as" Keyword ] ;
KeywordHead   = Keyword Identifier [ Extends ] ;
Extends       = "extends" Identifier { "," Identifier } ;
Keyword       = /[a-z][a-z0-9]*(-[a-z0-9]+)*/ ;

Body          = INDENT { NEWLINE | Member } DEDENT ;
Member        = AbstractProperty | Property ;
AbstractProperty
              = "abstract" Identifier ":" Type NEWLINE ;
Property      = Identifier ":" ( InlineValue NEWLINE | NEWLINE Block ) ;

Block         = INDENT { NEWLINE | Operation } DEDENT ;
Operation     = Assign | Item | Compose | TextRun ;
Assign        = Identifier ":" ( InlineValue NEWLINE | NEWLINE Block ) ;
Item          = "-" ( InlineValue NEWLINE | NEWLINE Block ) ;
Compose       = ( "+" | "++" ) InlineValue NEWLINE ;
TextRun       = TextLine { TextLine } ;   (* runs to the end of the block *)

InlineValue   = Number
              | Boolean
              | "null"
              | "super"
              | String
              | Reference
              | Identifier
              | InlineList
              | InlineObject
              | BareString ;

InlineList    = "[" [ InlineValue { "," InlineValue } ] "]" ;
InlineObject  = "{" [ Entry { "," Entry } ] "}" ;
Entry         = Identifier ":" InlineValue ;

Reference     = Identifier "." Identifier { "." Identifier } ;

Type          = TypeAtom { "[]" | "?" } ;
TypeAtom      = "string" | "number" | "boolean" | "null" | Identifier ;

Identifier    = /[A-Za-z_][A-Za-z0-9_]*(-[A-Za-z0-9_]+)*/ ;
Number        = /-?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?/ ;
Boolean       = "true" | "false" ;
String        = '"' { StringChar } '"' ;
BareString    = /* any remaining run of characters on the line */ ;
Comment       = "//" { any } ;

What the grammar cannot say

Six rules live outside it. They are not omissions; they are questions a context-free grammar structurally cannot answer.

Value classification. The text after : is an InlineValue only if the whole run forms one. Otherwise it is a BareString. This is why description: This is some text needs no quotes.

Identifier resolution. Identifier as a value is a reference when the name is bound in the module namespace, and a string otherwise. A Reference — a dotted path — is always a reference. The grammar cannot know what is bound.

Block kind. A Block is an object, a list or a string depending on its first non-Compose operation. Mixing kinds is an error.

Text runs. A TextRun extends to the end of its block, including blank and more-deeply-indented lines. Comment-only lines inside it are comments, not content.

Keyword validity. KeywordHead parses any lowercase kebab-case identifier. Whether that keyword is activated is a semantic question.

as placement. as may only appear on an abstract spec, and only after extends.

Reserved words

abstract  as     boolean  const  export  extends  false  from
import    null   number   self   spec    string   super  true   use

On this page