Skip to main content

Spec

Workflow

  1. YAML file would be converted to a tree of wasp_yaml.Node
  2. Convert the tree to a model.SchemaDef object
  3. Compile the model.SchemaDef object to model.Schema object by calling Compile()
  4. Convert model.Schema object to the Smart Contract of targeting languages

Types

model.SchemaDef

model.SchemaDef is a intermediate object during the Smart Contract generation. An YAML file will be converted to model.SchemaDef object. During the conversion, each YAML attribute except the top-level ones (name, description, events, structs, typedefs, state, funcs, views) will be converted into DefElt.

Therefore, for YAML tags name, description, the values of them will be converted into 2 independent DefElt.

name: TestName
description: This is test description

For keywords that can have multiple values can be seen as either a one layer map (i.e., typedefs and state) or two layer map (i.e., typedefs and state). A one layer map will be converted into DefMap which is a map whose key and value are both DefElt. And a two layer map will be converted into DefMapMap which is a map whose key is DefElt and value is DefMap.

The definition of DefElt is shown as following,

type DefElt struct {
Val string
Comment string
Line int
}

It contains the raw value of the YAML attributes (without extracting the information), the comment belongs to the YAML attribute, and the line number of the YAML attribute.

Here is an example of one layer map

typedefs:
TestTypedef1: String
TestTypedef2: String
state:
TestState1: Int64[]
TestState2: Int64[]

And an example of two layer map

structs:
point:
x: Int32
y: Int32
funcs:
testFunc:
params:
testFuncParam: Uint64
results:
testFuncResult: Uint64
views:
testView:
params:
testViewParam: Uint64
results:
testViewResult: Uint64

Next, schema tool will set each fields in SchemaDef variable.

type SchemaDef struct {
Copyright string
Name DefElt
Description DefElt
Events DefMapMap
Structs DefMapMap
Typedefs DefMap
State DefMap
Funcs FuncDefMap
Views FuncDefMap
}

model.Schema

By calling Schema.Compile(), model.SchemaDef object will be compiled into model.Schema. During the compilation, schema tool will extract the rules from the YAML attributes.

Here is the definition of a Schema object.

type Schema struct {
ContractName string
Copyright string
PackageName string
Description string
CoreContracts bool
SchemaTime time.Time
Events []*Struct
Funcs []*Func
Params []*Field
Results []*Field
StateVars []*Field
Structs []*Struct
Typedefs []*Field
}

And let's take a close look at Field object.

type Field struct {
Name string // external name for this field
Alias string // internal name alias, can be different from Name
Array bool
FldComment string
MapKey string
Optional bool
Type string
BaseType bool
Comment string
Line int // the line number originally in yaml/json file
}

As you can see typedefs was a simple DefMap, which consists a map whose key and value are both DefElt, and DefElt a simple object contains only raw string of the YAML attribute, comment and line number. However, after the compilation, information is extracted from the raw string, so do some checks are conducted in this step.

Compile

An emitter is used for filling corresponding values into templates under tools/schema/generator. For how to do meta-programming with emitter, see section Emitter

type (
FieldMap map[string]*Field
FieldMapMap map[string]FieldMap
StringMap map[string]string
StringMapMap map[string]StringMap
)

Comments

Header Comment and Line Comment

Header comment has higher priority than the line comment. If there are both header comment and line comment presented at same YAML attribute, then schema tool will keep only the header comment.

Comment Block

A comment block is a chunk of comment that doesn't have a line break to separate it. Schema tool would take the header comment that immediately followed by the YAML attribute or the line comment block if header comment block is not presented.

Therefore, for the following case

typedefs:
# header comment 1
# header comment 2

# header comment 3
# header comment 4
TestTypedef:
String # line comment 1
# line comment 2

only these 2 lines

# header comment 3
# header comment 4

will be kept and presented in the final Smart Contract.

And the next case

typedefs:
TestTypedef:
String # line comment 1
# line comment 2

# line comment 3
# line comment 4

only these 2 lines

# line comment 1
# line comment 2

will be kept and presented in the final Smart Contract.

Emitter

Access Keys

With $ prepending a key (keys are set in GenBase.setCommonKeys(), GenBase.setFieldKeys(), GenBase.setFuncKeys()), schema tool can access the value of the key in GenBase.keys according to the current context. For example, if you want to access lower case package name, you can access it with $package. To dynamically add a new key in templates (under gotemplates, rstemplates, and tstemplates), you can call $#set instruction, see section set for more information.

Key And Plain String Concatenation

To concatenate a value from accessing key and a plain string, you should use $+ operator. For example, here FuncName is a key that preserves the name of the function under current context, and we want to concatenate the function name with "Mutable" and "Results". In other words, we want to do the same task as the following python code and get the result in result variable.

func_name = "..." # function name under current context
result = "Mutable" + func_name + "Results" # concatenate the strings into the result

In the schema template language, we should call Mutable$FuncName$+Results.

Instructions

Keywords follows $# are the instructions defined in our schema template language. One thing you should aware, now, all the instruction should be presented at the beginning of each line. In other words, no spaces and characters are allowed to exist ahead of an instruction. Here is the list of all the instruction keywords.

  • emit
  • each
  • func
  • if
  • set

We are going to introduce how to use each instruction as follows. Or you can check the implementation of GenBase.emit() to know how are they implemented in detailed.

emit

emit is using for expanding templates. The syntax of emit instruction is

$#emit template

Here, template is any template which defined under gotemplates,rstemplates). Templates are defined in model.StringMap. In the instruction call of emit just simply use the name of the template (the key in model.StringMap). If you want to insert the copyright template to a assigned location, then you should call

$#emit copyright

each

each processes the template for each item in the array. The syntax of each instruction is

$#each array template

Here array is either a predefined keyword (we are going to introduce each of them as follow) or a multi-lines string. If a multi-lines string is presented, then the multi-lines string will be expanded and append newline escape character of targeting languages in the end of each line.

event

Iterate the fields in a event.

events

Iterate all the events in the contract.

func

Iterate all the funcs in the contract.

mandatory

Iterate all the mandatory fields in the current processed function. The mandatory field must be basetype and not an array or a map.

param

Iterate all the params fields in the current processed function.

params

Iterate all the params fields in the current contract.

result

Iterate all the results fields in the current processed function.

results

Iterate all the results fields in the current contract.

state

Iterate all the state in the contract.

struct

Iterate the fields in a struct.

structs

Iterate all the structs in the contract.

typedef

Iterate all the typedefs in the contract.

func

Currently not used.

if

The syntax of if is

$#if condition template [elseTemplate]

if processes template when the named condition is true, and it processes the optional elseTemplate when the named condition is false

condition is either the predefined conditions (explained as following) or a key that may exist in keys. If a key is presented, then if instruction would be used for check whether this key exists in keys or not. If the key exists, then if will return true, otherwise it will return false.

And here are the predefined conditions.

array

Is the current processed field an array?

basetype

Is the current processed field in basetype? basetypes are defined in the map FieldTypes in tools/schema/model/field.go.

core

Is the current processed contract a core contract?

event

Does the current processed event have any field?

events

Is there any event in the current processed contract?

exist

Does the value of key proxy exist?

func

Is the current processed function a func or a view? Return true if it is a func.

funcs

Is there any function in the current processed contract?

init

Is the current function an init function? An init function will automatically be called immediately after the first time the contract has been deployed to the VM.

mandatory

Is current field a mandatory field?

map

Is current processed field a map (check if the currentField.MapKey is empty)?

mut

Is the value in key mut Mutable?

param

Does the current processed function have any parameter?

params

Does the current contract have any params field?

ptrs

Does the current processed function have either params or results. This is used for implementing function object in Rust and TypeScript.

result

Does the current processed function have any return value?

results

Does the current contract have any results field?

state

Does the current contract have any state field?

structs

Does the current contract have any structs field?

this

Is the alias name of the current processed field this?

typedef

Is the current processed field a typedef?

typedefs

Does the current contract have any typedefs field?

view

Is the current processed function a view or a func? Return true if it is a view.

else

If you want to process a template under negate condition, then you can call

$#if condition else elseTemplate

Here else is a predefined empty template, which is defined at[tools/schema/generator/templates.go.

set

set is used for To dynamically specify a value to a certain key. The syntax is

$#set key value

For example, if you want to dynamically add a new key initFunc with the value in key nil you can call

$#set initFunc $nil

A special key exist is used to add a newly generated type.