Skip to content
This repository has been archived by the owner on Apr 13, 2019. It is now read-only.

schema language

Konstantin Sobolev edited this page Jul 25, 2017 · 29 revisions

This is a draft page filled as we go with the language implementation. The ultimate source of truth for the syntax is schema.bnf.

Overview

Epigraph schema files have an .epigraph extension and consist of a namespace declaration followed by imports followed by type and resource declarations.

General syntax

  • Schema is case-sensitive
  • White spaces have no semantics
  • Most of the commas are optional, for instance between field or parameter declarations
  • C++ style single and multi-line comments are supported

In the following sections angle brackets will be used for non-terminal parts of the syntax, and curved brackets for optional parts, for example:

list[〈 value type〉] 〈type name〉 ⧼extends clause⧽

Namespace declaration

Namespace declaration is mandatory and consists of a namespace keyword followed by a fully-qualified namespace. Fully-qualified namespace is a list of dot-separated namespace names forming a hierarchy, similar to Java packages. Namespace names must be lower-cased.

Namespaces are used to organize types and resources into logical groups. They may also be translated by the code generators into appropriate language constructs such as packages.

Imports

There are two kinds of imports: namespace imports and single type imports.

Single type import makes one single type from another package available:

import foo.bar.Baz

record R {
  myField: Baz
}

Importing types with the same short name from different namespaces is not allowed:

import foo.bar.Baz
import qux.Baz // clash!

Namespace import brings given namespace into the scope, so that namespace prefix can be omitted, except for the last segment:

import foo.bar

record R {
  myField: bar.Baz
}

Importing namespaces with the same last segment is not allowed:

import foo.bar
import qux.bar // clash!

Standard imports

The following imports are always implicitly present:

import epigraph.String
import epigraph.Integer
import epigraph.Long
import epigraph.Double
import epigraph.Boolean

Resolution sequence

Type references are resolved in the following order:

  1. Using explicit imports
  2. Using implicit (standard) imports
  3. Types from the same (current) namespace

See References implementation for more technical details.

Keywords

Here's a full list of reserved keywords that can't be used as type names, field names, parameter names etc.

CREATE CUSTOM DELETE GET POST PUT READ UPDATE abstract boolean default deleteProjection double enum extends forbidden import inputProjection inputType integer integer list long map meta method namespace outputProjection outputType override path projection record required resource string supplement supplements transformer vartype with

Annotations

todo

Type Declarations

Type declarations allow to define new custom named types. Custom type can be one of the following supported kinds:

  • entity
  • record
  • map
  • list
  • primitive
  • enum (unimiplemented as of writing)

Further definitions will be using a few building blocks:

Type name

Type name is either a

  • string that starts with an upper case letter, only contains letters or digits and is not one of the keywords
  • or an arbitrary string of any characters except backticks, enclosed in backticks

Keep in mind that codegens may not be happy if you go too creative with the latter case.

Type reference

Type reference is a type name that can be resolved, for instance String or foo.bar.Baz

Value type

Value type is an entity type reference or model value type. It describes a field, map entry or a list element type: it can be a either a type reference, or an inline anonymous map or list declaration.

Model value type

Model value type is a model type reference or an anonymous map or an anonymous list. It is a subset of a value type that can't resolve to an entity type and is used for entity tags. This is important because entity types can't act as each others entity models.

Anonymous list

Is an inline list declaration without a name. Syntax is

list[〈value type〉]

Examples:

list[String]
list[some.Type]
list[list[list[Boolean]]]

Anonymous map

Is an inline map declaration without a name. Syntax is

map[〈model value type〉, 〈value type〉]

Notable limitation here is that only model types can act as map keys, entity types are not allowed.

Examples:

map[String, String]
map[some.UserId, some.UserRecord]

Entity Type

Entity types describe entities that may have multiple representations, or models. Models are identified by unique named tags and their values are model value types. This means that entity types can't act as other entity models.

Entity type declaration syntax:

entity 〈type name〉 ⧼extends⧽ ⧼supplements⧽ ⧼meta⧽ ⧼body⧽

Where extends is extends clause, supplements is [supplements clause](#supplements clause), meta is metadata clause. Optional body is a list of annotations and tag declarations enclosed in curly braces.

Tag declaration syntax:

⧼override⧽ 〈tag name〉: 〈model value type〉 ⧼tag body⧽

tag name obeys same rules as type name, but must start with a lower-case letter.

Optional tag body is a list of annotations enclosed in curly braces.

Examples:

entity Creature

entity Person extends Creature {
  @Doc "Person entity type"
  id: PersonId
}

entity User extends Person {
  override id: UserId { @Doc "User ID" }
}

Entity type inherits all the tags from it's parents and there should be no type conflicts between them. Type of the overriding tag must be a sub-type of the overriden tag (or be the same type). Annotations on overriding tags hide annotations coming from overriden tags.

override modifier is optional, but a tag must override some other tag if it is present.

Primitive

Primitive declaration syntax

〈primitive kind〉 〈type name〉 ⧼extends⧽ ⧼supplements⧽ ⧼meta⧽ ⧼body⧽

Where extends is extends clause, supplements is [supplements clause](#supplements clause), meta is metadata clause.

primitive kind is one of string, long, integer, boolean, double.

Optional body is a list of annotations in curly braces.

Primitive types can only inherit other primitive types of the same pimitive kind, i.e a long can only extend another long and can't extend a double.

Examples:

long PersonId
long UserId extends PersonId { @Doc "User ID" }

Record

Record type declaration syntax:

record 〈type name〉 ⧼extends⧽ ⧼supplements⧽ ⧼meta⧽ ⧼body⧽

Where extends is extends clause, supplements is [supplements clause](#supplements clause), meta is metadata clause. Optional body is a list of annotations and field declarations enclosed in curly braces.

Field declaration syntax:

⧼override⧽ 〈field name〉: 〈value type〉 ⧼field body⧽

field name obeys same rules as type name, but must start with a lower-case letter.

Optional field body is a list of annotations enclosed in curly braces.

Examples:

record PersonRecord {
  @Doc "Person record type"
  id: PersonId
  name: String
}

record UserRecord extends PersonRecord {
  override id: UserId
  profile: Url { @Doc "User profile URL" }
}

Record type inherits all the fields from it's parents and there should be no type conflicts between them. Type of the overriding field must be a sub-type of the overriden field (or be the same type). Annotations on overriding fields hide annotations coming from overriden fields.

override modifier is optional, but a tag must override some other tag if it is present.

Map

Syntax:

map[〈model value type〉, 〈value type〉] 〈type name〉 ⧼extends⧽ ⧼supplements⧽ ⧼meta⧽ ⧼body⧽

Where extends is extends clause, supplements is [supplements clause](#supplements clause), meta is metadata clause. Optional body is a list of annotations enclosed in curly braces.

Limitations:

  • only model types can act as map keys, entity types not allowed
  • maps can only inherit maps with the same key/value types

Examples:

map[UserId, UserRecord] UserMap meta Pagination {
  @Doc "Users map"
}

List

Syntax:

list[〈value type〉] 〈type name〉 ⧼extends⧽ ⧼supplements⧽ ⧼meta⧽ ⧼body⧽

Where extends is extends clause, supplements is [supplements clause](#supplements clause), meta is metadata clause. Optional body is a list of annotations enclosed in curly braces.

Lists can only inherit lists with the same element types.

Examples:

list[User] UsersList Pagination { @Doc "Users list" }

Inheritance

Most of custom types support inheritance. Common limitation is that only type of the same kind may be inherited, for instance a record can extend another record but can't extend a primitive. Specific kinds impose their own additional constraints which are discussed in corresponding sections.

Multiple inheritance is supported. Circular inheritance is not allowed. Linearization algorithm similar to Scala trait linearization is used to flatten out inheritance hierarchies and resolve diamond problem.

Inheritance relationship can be initiated by both child side (using extends clause) and parent side (using supplements clause). It can also be declared using standalone supplement statement. This means that in addition to traditional way of defining subtypes, it is also possible to inject supertypes into already existing types.

Any annotations specified for child types hide annotations of the same type coming from parent types.

Extends clause

Extends clause provides a list of types that are inherited by a given type. Every list element is a type reference of the same kind. Examples:

extends String
extends foo.Bar, bar.Baz

Supplements clause

Supplements clause specifies a list of type that will inherit a given type. Every list element is a type reference of the same kind. Examples:

supplements some.UserRecord, some.other.PersonRecord

Supplement statement

Supplement statement allows to establish inheritance between types defined somewhere else. General syntax is

supplement 〈comma-separated list of type references〉 with 〈type reference〉

For example

supplement some.UserRecord with twitter.WithTwitterAccount

Metadata

Clone this wiki locally