Frost Schema Language (FSL)
Structure
Syntax
In this section we'll define the essential parts that comprise the FSL syntax
Keywords
In FSL we have three main keywords
model
used to define a data model.enum
used to define an enum.type
used to define a data type (struct).
Comments
FSL supports double-slash single line comments, you can write comments at the beging and end of each line or in seperate lines.
// This is a comment
model A { // This is a comment
// This is a comment
...
}
// This is a comment
type Data { // This is a comment
// This is a comment
...
} // This is a comment
Pragmas
Pragma in FSL are used to declare or modify the behaviour in a scope (block, global).
Pragmas are written in the following pattern @@pragma_name(pragma_arguments)
. (pragma arguments can be either named or positional or a combination of both)
an example of pragmas is the node
pragma
Modifiers
Modifiers in FSL are used to modify the behaviour of a statement or add certain metadata .
Modifiers are written in the following pattern @modifier_name(modifier_arguments)
. (modifier arguments can be either named or positional or a combination of both)
an example of pragmas is the Relation
modifier
Modfiers and Pragmas are written in a similar pattern but Modfiers start with a single annotation while pragmas start with two. Modifiers names are in Pascal Case while Pragmas names are in snake_case
Arguments
- Positional Arguments: comma-seperated values, for example
(1,'a',true)
. Position is important hence the name so(1,'a',true)
is not the same as('a',true,1)
- Named Arguments: each argument is written in the following pattern
name: value
, arguments are sperated by commas also. for example(name: 'frost',year:2022)
. Position is not important so(name: 'frost',year:2022)
is the same as(year:2022, name: 'frost')
. - Both Types of Arguments:
- Position of the named arguments is not relevant they can be at the begining or the end or scatterd between the parenthesis.
- Position of the Positional arguments is determined relative to the occurence of the specific argument, so a third positional argument could be number 5 in the parenthesis given that there are two named arguments before it.
- for example:
('first_pos_arg','second_pos_arg',a:1,b:2,'third_pos_arg')
- the arguments map will be as follows:
args[0] = 'first_pos_arg'
args[1] = 'second_pos_arg'
args[2] = 'third_pos_arg'
args['a'] = 1
args['b] = 2
- the arguments map will be as follows:
- for example:
Even though frost allows the user to mix the position in the arguments, The user should follow a structure to avoid confusion. Recommend Structure: to write all the posiotional arguments first then the named arguments, this way it's easy to determine the position of the positional arguments.
Primitive Values
FSL allows three types of primitive values to be used in arguments, pragmas, modifiers, and definitions. These three types are:
- Boolean: [
true
,false
]. (case sensitive). - Numbers:
- Integers: examples(
1
,20
) - Decimal (floats): examples(
1.0
,20.2
) - Negative: examples(
-1.0
,-20
)
- Integers: examples(
- Strings:
- single quotation: example(
'name'
) - double quotation: example(
"name"
) - backtick quotation: example(
`name`
)
- single quotation: example(
Allowed Data types
Data Types will be used to define the type of properties in Models and Types.
The Allowed Data Types are:
FSL | Typescript/Javascript |
---|---|
String | string |
Boolean | boolean |
Double, Float, Decimal, Long, Short | number |
Date | Date |
Json | any |
- Also, Any Enum or Type that have been already defined can be used as the type of the property.
- Inside Model Relations Definitions, Other Models Could be used as Data types.
- All these types can be used as arrays by adding
[]
, Eaxmple:String[]
- All these types can be marked as optional using
?
, Eaxmples:String?
,String[]?
- Map/Record: Coming soon use json type instead or define a specific type
All types are case insensitive except for user-defined types and enums.
Defintions
There are types of definitions in FSL [model
,enum
,type
]. each definition starts with the declartion statement then followed by a block of statements
(assignment statements in enum
block, and property declartion statements in type
an model
)
Each Definition follows the following pattern
<definition_type> <instance_name> {
<statements>
}
for example:
enum Name {
FIRST
MIDDLE
LAST
}
Defining Enums
Enums in FSL, follow the same style of enums in Typescript. To declare the enum:
- write down
enum <enum_name>
and follow with a block of code by opening two curly brackets{}
- Between the two curly brackets, define the enum values using assignment statements.
- each statement follows the pattern
<name> = <value>
, so something likeA = 'a'
- If the value is not provided, it defaults to the typical behaviour of Typescript. ie; the enum value will be the integer representing the position stating from zero.
- each statement follows the pattern
enum Character {
A
B
C
...
}
//The Above is equivalent to
enum Character {
A = 0
B = 1
C = 2
...
}
The value of the enum values could be any of the primitives, so you could something like:
enum Character {
A = 'a'
B = 'b'
C = 'c'
...
}
// or even something mixed like
enum Character {
A = 'a'
B = 2
C = true
D
...
}
Defining Types
Types in FSL are like types in Typescript, the represent data interfaces. to declare a type:
- write down
type <type_name>
and follow with a block of code by opening two curly brackets{}
- Between the two curly brackets, define the properties with the pattern
<property_name> <property_type>
, property_type should follow mentioned above in "Allowed Data types" section.
type Name {
first_name String
middle_name String? // This an optional property
last_name String
}
// another example
type ContactInfo {
name Name // this uses the type defined above
email String // this is a primitive string
initials Character[]? // this uses the enum defined in the previous section and it's an array and also optional
}
Defining Models
Models in FSL represent the sturcture of the data objects in a certain node in the database. The syntax for defining a model is very similar for defining a Type except for a few differences.
- Of course you will use the keyword
model
instead oftype
at the begining of the declaration. - Property definition statements in the model can accept the
Relation
modifier.- Other models can be used as data types in the model properties declaration, and actually you will have to use them if you want to decalre a relation between two models
- The `node`` pragma can be used in the model definition block.
model Student {
// the students list will be stored in te following path in the database /students/<student_id>
@@node(path:'students')
contactInfo ContactInfo // this uses the data type defined in the previous section
dateOfBirth Date // Native Date type
courses Courses[] @Relation()
// this property is used to define a relation with the Course model, since the data type is model.
// you will find a corresponding property in the Course model
// Since properties on both side are array types then the relation is `many to many`
}
model Course {
// the courses list will be stored in te following path in the database /courses/<course_id>
@@node(path:'courses')
level CourseLevel // a user-defined enum
name String
duration Int
students Student[] @Relation()
}
node
Pragma
The node pragma is used to add any extra metadata for the node model. As of right now the only option available inside the node pragma is the path
.
The path
argument is used to determine the path of the node in the firebaseDB that will contain the list of the model nodes. if the node pragma is not used the path will default to the same name of the model.
Relation
Modifier
The Relation
Modifier is used to mark which properties have relations with other Models.
By default the Relation modifier requires no arguments, But if you have multiple relations between the same models then you have to specify the name of the relation using the name
argument.
The Type of the relation is determined from the Data type of the property.
- Many To Many: if both sides are arrays
- One To Many: if one side is an array and the other is not.
- One To One: if both sides are not arrays
model A {
...
b1 B @Relation()
b2 B @Relation(name: "A_B_second")
}
model B {
a1 A @Relation()
a2 A @Relation(name: "A_B_second")
}
You don't have to specify the name for the first relation between b1
and a1
,
but you could if you want to make it less ambigous. you could put name: "A_B_first"
between the parenthesis @Relation(name: "A_B_first")
on both sides
Syntax Highlighting
Vscode
An Extension for Syntax Highlighting FSL in VScode was created. You can find it here