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