I enabled constant data constructors. I added more to the documentation.

This commit is contained in:
Christian Rinderknecht 2019-05-17 16:29:22 +02:00
parent 90a9e1a783
commit 6028cd9abd
No known key found for this signature in database
GPG Key ID: 9446816CFD267040
7 changed files with 277 additions and 20 deletions

View File

@ -199,10 +199,9 @@ and type_expr =
and cartesian = (type_expr, times) nsepseq reg
and variant = { (* TODO: Constant constructors *)
constr : constr;
kwd_of : kwd_of;
product : cartesian
and variant = {
constr : constr;
args : (kwd_of * cartesian) option
}
and record_type = field_decl reg injection reg

View File

@ -184,9 +184,8 @@ and type_expr =
and cartesian = (type_expr, times) nsepseq reg
and variant = {
constr : constr;
kwd_of : kwd_of;
product : cartesian
constr : constr;
args : (kwd_of * cartesian) option
}
and record_type = field_decl reg injection reg

View File

@ -137,8 +137,257 @@ The directory contains the following:
common parts of the grammar.
However you change this file, the grammar must remain without LR
conflicts, without resorting %prec or %assoc annotations.
Another example of maintenance task is to enable unary
constructors. An oversight made the definition of enumerated types
impossible, like t = A | B | C. You can first change the AST, then
the parser, or vice-versa, and then ligo/src/simplify/pascaligo.ml
if necessary.
PASCALIGO
Generalities
Pascaligo is an imperative language for writing smart contracts on the
Tezos blockchain. As such, it is compiled to Michelson, the native
language of the Tezos blockchain. Its design is inspired by Pascal,
OCaml and Michelson.
An imperative language is a language in which the value bound to a
variable can change over time, as opposed to a constant. That change,
called _side-effect_, is often leveraged through loops, enabling data
to be modified and accumulated repeatedly. For example, here is how
the integer value associated to the variable "x" is incremented by an
instruction, called _assignment_:
x := x + 1;
A loop computing the sum of all integers from 1 to 10 would be written
as follows:
y := 0;
for x := 1 to 10
begin
y := y + x
end
(Note that this is useless in practice, as a closed-form formula
exists for that computation.)
In Pascaligo, expressions and instructions are
distinguished. Expressions are evaluated and yield values, whilst
instructions are evaluated but do not yield values. Instructions are
meant to perform side-effects, like changing the value of a variable,
whereas expressions are purely computational, like calculating a an
arithmetic means. Instructions and expressions can be compounded, and
instructions can evaluate expressions as a means to perform
side-effects.
Pascaligo is strongly and statically typed, which means that the
composition of data and functions is contrained so the compiler can
check that no such composition can fail at run-time, e.g., because of
a meaningless expression. Pascaligo requires that variables are
declared together with their type and an initial value.
Declarations of values come in two kinds: either constants or
variables. The former are assigned only once at their declaration, and
the latter can be reassigned. The syntax is slightly different for
both. For example, the variables "x" and "y" above could have been
declared as follows:
var x : nat := 0;
var y : nat := 0;
It is possible to specify that the value of a variable will not change
(the name "variable" is misleading in that context), that is, they
remain constant:
const ten : nat = 10;
const eleven : nat = ten + 1;
Similarly, function declarations have their parameters and return
value annotated with their types. For instance,
function sum (const n : nat; const m : nat) : nat is
begin
end with n + m;
declarares the function "sum" that takes as argument two constant
natural numbers and return their sum. The expression whose value is
the result of calling the function is given after the keyword "with".
A another example would be
function factorial (const n : nat) : nat is
var m : nat := 0;
var f : nat := 1;
begin
if n <= 0 then f := 1
else
for m := 1 to n
begin
f := f * m
end
end with f
Like Pascal, Pascaligo offers procedures, as well as functions. The
difference follows the divide between expressions and instructions:
function calls are expressions, procedure calls are instructions.
A special case of functions are entry points, which are functions that
can be called when interacting with the contract after it has been
originated on the chain. The only difference with function is that
they are introduced by a keyword "entrypoint", instead of "function",
and they (currently) must have a special parameter for the storage,
and the return type must be a pair made of a new storage and a list of
operations. For example,
entrypoint contribute (storage store : store; ...)
: store * list (operation) is
var operations : list (operation) := nil
begin
...
end with (store, operations)
where "storage" is a keyword.
Pascaligo features predefined types, like integers, natural numbers,
mutez, strings, maps, lists etc. and constructs to combine those into
structured types. Amongst those constructs are the records, which
group and map names (_fields_) to values of potentially different
types. For instance,
type point is
record
x : int;
y : int
end
defines a record type "store" with three fields, each made of a name
and a type. Values of record types are made by assigning a value to
each field (in any order). Like so:
const origin : point =
record
x = 0;
y = 0
end
At this point it is perhaps useful to remark that there are actually
two flavours of Pascaligo recognised by the same parser: they should
be separated in the future, and, for now, it is best to not mix both
styles. Those style differ in the manner compound constructs are
delimited
For example, the type "point" above could have been alternatively
defined as follows:
type point is
record [
x : int;
y : int
]
and the value as
const origin : point = record [x = 0; y = 0];
When updating the contents of a record, Pascaligo offers some
syntactic support. Instead of writing all the assignments, most of
which are left unchanged, there is the record patch, which corresponds
to a functional update in OCaml. For example,
var p : point := origin;
patch p with record y = 10 end;
will update only the field "y" of "p". Of course, this example is not
impressive, but imagine that one has to update a small number of
fields in a large record. An alternative syntax is
patch p with record [y = 10];
Another way to combine types are disjunctive types, which are a
generalisation of enumerated types found in OCaml. They can be
interpreted as being a disjoint partition of value sets, each being
disinguished by a unique tag, or _data constructor_. For example,
type u = unit
type t = A | B of u | C of int * string
See OCaml.
As in OCaml, their values can be matched against patterns:
match v with
A -> "A"
| B Unit -> "B"
| C (_, s) -> s
Of course, we also find type aliases, which simply rename a type. More
importantly, Pascaligo has predefined types that cannot be defined by
the contract programmer. Indeed, user-defined types are monomorphic
and non-recursive, in other words, they are not parameterised and
cannot be defined in terms of themselves. This limitation precludes
defining lists of values, as a list is an inductive data type: a list
is either empty or a pair made of an item (the first item) and another
list (the remaining items). That is why Pascaligo features a native
polymorphic list type, with the condition that all list values must
instantiate the type of the items it contains. Another useful data
abstraction, native to Pascaligo, is the map, which relates values of
a given type to values of another given type. The last type predefined
by Pascaligo is the set.
Pascaligo is inspired by Pascal and OCaml. Semi-colons are separators,
but they can be used as terminators as well.
For example
type store is
record
goal : nat;
deadline : timestamp;
backers : map (address, nat);
funded : bool;
end
can alternatively be written
type store is
record
goal : nat;
deadline : timestamp;
backers : map (address, nat);
funded : bool
end
Only non-recursive types are user-definable in LIGO.
A predefined recursive and polymorphic type is the list (or
stack). The syntax is the same as in OCaml, with some extra syntax for
convenience. For example, the empty list can be written in three
different ways (only one is recommended by contract):
list end
list []
nil
A non-empty list starts with the keyword "list" and comes in two
flavours (only one is recommended by contract):
list 1; 2; 3 end
list [1; 2; 3]
To push (cons) and element in a list, the infix operator is "::", as
in OCaml:
1::2::l
All user-definable values in Pascaligo are monomorphic and must be
annotated with a their types, except in arithmetic or boolean
expressions, or at their declaration (since their type is given in the
left-hand side). In particular, empty lists need to be annotated, like
so
1 :: (nil : list (int))
But
var l : list (int) := list [];
works, as the type is available.

View File

@ -213,9 +213,11 @@ sum_type:
variant:
Constr Of cartesian {
let region = cover $1.region $3.region
and value = {constr = $1; kwd_of = $2; product = $3}
in {region; value} }
(* TODO: Unary constructors *)
and value = {constr = $1; args = Some ($2, $3)}
in {region; value}
}
| Constr {
{region=$1.region; value= {constr=$1; args=None}} }
record_type:
Record series(field_decl,End) {

View File

@ -98,10 +98,13 @@ and print_cartesian {value; _} =
print_nsepseq "*" print_type_expr value
and print_variant {value; _} =
let {constr; kwd_of; product} = value in
let {constr; args} = value in
print_constr constr;
print_token kwd_of "of";
print_cartesian product
match args with
None -> ()
| Some (kwd_of, product) ->
print_token kwd_of "of";
print_cartesian product
and print_sum_type {value; _} =
print_nsepseq "|" print_variant value

View File

@ -14,7 +14,7 @@ entrypoint contribute (storage store : store;
// const s : list (int) = list [1; 2; 3]
//const t : set (int) = set []
begin
if now > store.deadline then
if now (Unit) > store.deadline then
fail "Deadline passed";
else
case store.backers[sender] of [

View File

@ -58,8 +58,13 @@ let rec simpl_type_expression (t:Raw.type_expr) : type_expression result =
ok @@ T_record m
| TSum s ->
let aux (v:Raw.variant Raw.reg) =
let args =
match v.value.args with
None -> []
| Some (_, product) ->
npsseq_to_list product.value in
let%bind te = simpl_list_type_expression
@@ npseq_to_list v.value.product.value in
@@ args in
ok (v.value.constr.value, te)
in
let%bind lst = bind_list