Merge branch 'dev' of gitlab.com:ligolang/ligo into feature/doc-pascaligo-loop

This commit is contained in:
Christian Rinderknecht 2020-02-08 10:16:34 +01:00
commit e6dc4371ee
127 changed files with 47408 additions and 162 deletions

View File

@ -3,9 +3,11 @@ variables:
GIT_SUBMODULE_STRATEGY: recursive
build_binary_script: "./scripts/distribution/generic/build.sh"
package_binary_script: "./scripts/distribution/generic/package.sh"
LIGO_REGISTRY_IMAGE_BASE_NAME: "${CI_PROJECT_PATH}/${CI_PROJECT_NAME}"
stages:
- test
- ide
- build_and_package_binaries
- build_docker
- build_and_deploy_docker
@ -75,9 +77,9 @@ dont-merge-to-master:
- public
.docker: &docker
image: docker:1.11
image: docker:19
services:
- docker:dind
- docker:19-dind
.before_script: &before_script
@ -130,7 +132,7 @@ build-and-publish-latest-docker-image:
- sh scripts/build_docker_image.sh
- sh scripts/test_cli.sh
- docker login -u $LIGO_REGISTRY_USER -p $LIGO_REGISTRY_PASSWORD
- docker push $LIGO_REGISTRY_IMAGE:next
- docker push ${LIGO_REGISTRY_IMAGE_BUILD:-ligolang/ligo}:next
only:
- dev
@ -140,7 +142,7 @@ build-and-package-debian-9:
<<: *docker
# To run in sequence and save CPU usage, use stage: build_and_package_binaries
stage: test
variables:
variables:
target_os_family: "debian"
target_os: "debian"
target_os_version: "9"
@ -152,7 +154,7 @@ build-and-package-debian-10:
<<: *docker
# To run in sequence and save CPU usage, use stage: build_and_package_binaries
stage: test
variables:
variables:
target_os_family: "debian"
target_os: "debian"
target_os_version: "10"
@ -168,7 +170,7 @@ build-and-package-ubuntu-18-04:
<<: *docker
# To run in sequence and save CPU usage, use stage: build_and_package_binaries
stage: test
variables:
variables:
target_os_family: "debian"
target_os: "ubuntu"
target_os_version: "18.04"
@ -180,7 +182,7 @@ build-and-package-ubuntu-19-04:
<<: *docker
# To run in sequence and save CPU usage, use stage: build_and_package_binaries
stage: test
variables:
variables:
target_os_family: "debian"
target_os: "ubuntu"
target_os_version: "19.04"
@ -188,6 +190,13 @@ build-and-package-ubuntu-19-04:
only:
- dev
trigger-webide:
stage: ide
trigger:
include: tools/webide/webide-ci.yml
# Pages are deployed from dev, be careful not to override 'next'
# in case something gets merged into 'dev' while releasing.
pages:

View File

@ -0,0 +1,79 @@
---
id: string-reference
title: String
---
## String.size(s: string) : nat
Get the size of a string. [Michelson only supports ASCII strings](http://tezos.gitlab.io/whitedoc/michelson.html#constants)
so for now you can assume that each character takes one byte of storage.
<!--DOCUSAURUS_CODE_TABS-->
<!--PascaLIGO-->
```pascaligo
function string_size (const s: string) : nat is size(s)
```
<!--CameLIGO-->
```cameligo
let size_op (s: string) : nat = String.size s
```
<!--ReasonLIGO-->
```reasonligo
let size_op = (s: string): nat => String.size(s);
```
<!--END_DOCUSAURUS_CODE_TABS-->
## String.length(s: string) : nat
Alias for `String.size`.
## String.slice(pos1: nat, pos2: nat, s: string) : string
Get the substring of `s` between `pos1` inclusive and `pos2` inclusive. For example
the string "tata" given to the function below would return "at".
<!--DOCUSAURUS_CODE_TABS-->
<!--PascaLIGO-->
```pascaligo
function slice_op (const s : string) : string is string_slice(1n , 2n , s)
```
<!--CameLIGO-->
```cameligo
let slice_op (s: string) : string = String.slice 1n 2n s
```
<!--ReasonLIGO-->
```reasonligo
let slice_op = (s: string): string => String.slice(1n, 2n, s);
```
<!--END_DOCUSAURUS_CODE_TABS-->
## String.sub(pos1: nat, pos2: nat, s: string) : string
Alias for `String.slice`.
## String.concat(s1: string, s2: string) : string
Concatenate two strings and return the result.
<!--DOCUSAURUS_CODE_TABS-->
<!--PascaLIGO-->
```pascaligo
function concat_op (const s : string) : string is s ^ "toto"
```
<!--CameLIGO-->
```cameligo
let concat_syntax (s: string) = s ^ "test_literal"
```
<!--ReasonLIGO-->
```reasonligo
let concat_syntax = (s: string) => s ++ "test_literal";
```
<!--END_DOCUSAURUS_CODE_TABS-->

View File

@ -7,13 +7,13 @@ let bad_contract basename =
let%expect_test _ =
run_ligo_good [ "measure-contract" ; contract "coase.ligo" ; "main" ] ;
[%expect {| 2066 bytes |}] ;
[%expect {| 2062 bytes |}] ;
run_ligo_good [ "measure-contract" ; contract "multisig.ligo" ; "main" ] ;
[%expect {| 1093 bytes |}] ;
run_ligo_good [ "measure-contract" ; contract "multisig-v2.ligo" ; "main" ] ;
[%expect {| 2717 bytes |}] ;
[%expect {| 2713 bytes |}] ;
run_ligo_good [ "measure-contract" ; contract "vote.mligo" ; "main" ] ;
[%expect {| 642 bytes |}] ;
@ -86,7 +86,7 @@ let%expect_test _ =
SWAP ;
DIP { DUP ; CAR ; CAR } ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
DUP ;
CAR ;
DIP { DUP ; CDR ; PUSH nat 1 ; ADD } ;
@ -168,7 +168,7 @@ let%expect_test _ =
SWAP ;
DIP { DUP ; CAR ; CDR } ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
DUP ;
CAR ;
SOURCE ;
@ -182,7 +182,7 @@ let%expect_test _ =
CDR ;
DIP { DIP { DUP } ; SWAP ; CAR ; CAR } ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
DUP ;
CDR ;
PUSH nat 1 ;
@ -262,7 +262,7 @@ let%expect_test _ =
CAR ;
DIP { DUP } ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
DUP ;
CAR ;
SOURCE ;
@ -488,7 +488,7 @@ let%expect_test _ =
CAR ;
SENDER ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
PUSH nat 1 ;
ADD ;
SOME ;
@ -529,7 +529,7 @@ let%expect_test _ =
CAR ;
SENDER ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
PUSH nat 1 ;
ADD ;
SOME ;
@ -572,7 +572,7 @@ let%expect_test _ =
CAR ;
SENDER ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
DUP ;
DIP { DIP 4 { DUP } ; DIG 4 ; CAR ; CDR ; CAR } ;
COMPARE ;
@ -768,7 +768,7 @@ let%expect_test _ =
CAR ;
SENDER ;
GET ;
IF_NONE { PUSH string "GET_FORCE" ; FAILWITH } {} ;
IF_NONE { PUSH string "MAP FIND" ; FAILWITH } {} ;
PUSH nat 1 ;
SWAP ;
SUB ;

View File

@ -0,0 +1,36 @@
open Cli_expect
let contract basename =
"../../test/contracts/" ^ basename
let bad_contract basename =
"../../test/contracts/negative/" ^ basename
let%expect_test _ =
run_ligo_good [ "run-function" ; contract "failwith.ligo" ; "failer" ; "1" ] ;
[%expect {|
failwith("some_string") |}];
run_ligo_good [ "run-function" ; contract "failwith.ligo" ; "failer" ; "1" ; "--format=json" ] ;
[%expect {|
{"status":"ok","content":"failwith(\"some_string\")"} |}];
run_ligo_good [ "dry-run" ; contract "subtle_nontail_fail.mligo" ; "main" ; "()" ; "()" ] ;
[%expect {|
failwith("This contract always fails") |}];
run_ligo_good [ "interpret" ; "assert(1=1)" ; "--syntax=pascaligo" ] ;
[%expect {|
Unit |}];
run_ligo_good [ "interpret" ; "assert(1=2)" ; "--syntax=pascaligo" ] ;
[%expect {|
failwith("failed assertion") |}];
run_ligo_good [ "interpret" ; "assert(1=1)" ; "--syntax=cameligo" ] ;
[%expect {|
Unit |}];
run_ligo_good [ "interpret" ; "assert(1=2)" ; "--syntax=cameligo" ] ;
[%expect {|
failwith("failed assertion") |}];

View File

@ -346,6 +346,16 @@ module Wrap = struct
P_variable unification_body]))
] @ arg' @ body' , whole_expr
(* This is pretty much a wrapper for an n-ary function. *)
let constant : O.type_value -> T.type_value list -> (constraints * T.type_variable) =
fun f args ->
let whole_expr = Core.fresh_type_variable () in
let args' = List.map type_expression_to_type_value args in
let args_tuple = O.P_constant (C_tuple , args') in
O.[
C_equation (f , P_constant (C_arrow , [args_tuple ; P_variable whole_expr]))
] , whole_expr
end
(* begin unionfind *)
@ -727,47 +737,7 @@ let selector_break_ctor : (type_constraint_simpl, output_break_ctor) selector =
| SC_Poly _ -> WasNotSelected (* TODO: ??? (beware: symmetry) *)
| SC_Typeclass _ -> WasNotSelected
let propagator_break_ctor : output_break_ctor propagator =
fun selected dbs ->
let () = ignore (dbs) in (* this propagator doesn't need to use the dbs *)
let a = selected.a_k_var in
let b = selected.a_k'_var' in
(* produce constraints: *)
(* a.tv = b.tv *)
let eq1 = C_equation (P_variable a.tv, P_variable b.tv) in
(* a.c_tag = b.c_tag *)
if a.c_tag <> b.c_tag then
failwith "type error: incompatible types, not same ctor"
else
(* a.tv_list = b.tv_list *)
if List.length a.tv_list <> List.length b.tv_list then
failwith "type error: incompatible types, not same length"
else
let eqs3 = List.map2 (fun aa bb -> C_equation (P_variable aa, P_variable bb)) a.tv_list b.tv_list in
let eqs = eq1 :: eqs3 in
(eqs , []) (* no new assignments *)
(* TODO : with our selectors, the selection depends on the order in which the constraints are added :-( :-( :-( :-(
We need to return a lazy stream of constraints. *)
type output_specialize1 = { poly : c_poly_simpl ; a_k_var : c_constructor_simpl }
let (<?) ca cb =
if ca = 0 then cb () else ca
let rec compare_list f = function
| hd1::tl1 -> (function
[] -> 1
| hd2::tl2 ->
f hd1 hd2 <? fun () ->
compare_list f tl1 tl2)
| [] -> (function [] -> 0 | _::_ -> -1) (* This follows the behaviour of Pervasives.compare for lists of different length *)
let compare_type_variable a b =
Var.compare a b
let compare_label = function
| L_int a -> (function L_int b -> Int.compare a b | L_string _ -> -1)
| L_string a -> (function L_int _ -> 1 | L_string b -> String.compare a b)
(* TODO: move this to a more appropriate place and/or auto-generate it. *)
let compare_simple_c_constant = function
| C_arrow -> (function
(* N/A -> 1 *)
@ -866,6 +836,83 @@ let compare_simple_c_constant = function
| C_chain_id -> 0
(* N/A -> -1 *)
)
(* Using a pretty-printer from the PP.ml module creates a dependency
loop, so the one that we need temporarily for debugging purposes
has been copied here. *)
let debug_pp_constant : _ -> constant_tag -> unit = fun ppf c_tag ->
let ct = match c_tag with
| Core.C_arrow -> "arrow"
| Core.C_option -> "option"
| Core.C_tuple -> "tuple"
| Core.C_record -> failwith "record"
| Core.C_variant -> failwith "variant"
| Core.C_map -> "map"
| Core.C_big_map -> "big_map"
| Core.C_list -> "list"
| Core.C_set -> "set"
| Core.C_unit -> "unit"
| Core.C_bool -> "bool"
| Core.C_string -> "string"
| Core.C_nat -> "nat"
| Core.C_mutez -> "mutez"
| Core.C_timestamp -> "timestamp"
| Core.C_int -> "int"
| Core.C_address -> "address"
| Core.C_bytes -> "bytes"
| Core.C_key_hash -> "key_hash"
| Core.C_key -> "key"
| Core.C_signature -> "signature"
| Core.C_operation -> "operation"
| Core.C_contract -> "contract"
| Core.C_chain_id -> "chain_id"
in
Format.fprintf ppf "%s" ct
let debug_pp_c_constructor_simpl ppf { tv; c_tag; tv_list } =
Format.fprintf ppf "CTOR %a %a(%a)" Var.pp tv debug_pp_constant c_tag PP_helpers.(list_sep Var.pp (const " , ")) tv_list
let propagator_break_ctor : output_break_ctor propagator =
fun selected dbs ->
let () = ignore (dbs) in (* this propagator doesn't need to use the dbs *)
let a = selected.a_k_var in
let b = selected.a_k'_var' in
(* produce constraints: *)
(* a.tv = b.tv *)
let eq1 = C_equation (P_variable a.tv, P_variable b.tv) in
(* a.c_tag = b.c_tag *)
if (compare_simple_c_constant a.c_tag b.c_tag) <> 0 then
failwith (Format.asprintf "type error: incompatible types, not same ctor %a vs. %a (compare returns %d)" debug_pp_c_constructor_simpl a debug_pp_c_constructor_simpl b (compare_simple_c_constant a.c_tag b.c_tag))
else
(* a.tv_list = b.tv_list *)
if List.length a.tv_list <> List.length b.tv_list then
failwith "type error: incompatible types, not same length"
else
let eqs3 = List.map2 (fun aa bb -> C_equation (P_variable aa, P_variable bb)) a.tv_list b.tv_list in
let eqs = eq1 :: eqs3 in
(eqs , []) (* no new assignments *)
(* TODO : with our selectors, the selection depends on the order in which the constraints are added :-( :-( :-( :-(
We need to return a lazy stream of constraints. *)
type output_specialize1 = { poly : c_poly_simpl ; a_k_var : c_constructor_simpl }
let (<?) ca cb =
if ca = 0 then cb () else ca
let rec compare_list f = function
| hd1::tl1 -> (function
[] -> 1
| hd2::tl2 ->
f hd1 hd2 <? fun () ->
compare_list f tl1 tl2)
| [] -> (function [] -> 0 | _::_ -> -1) (* This follows the behaviour of Pervasives.compare for lists of different length *)
let compare_type_variable a b =
Var.compare a b
let compare_label = function
| L_int a -> (function L_int b -> Int.compare a b | L_string _ -> -1)
| L_string a -> (function L_int _ -> 1 | L_string b -> String.compare a b)
let rec compare_typeclass a b = compare_list (compare_list compare_type_value) a b
and compare_type_value = function
| P_forall { binder=a1; constraints=a2; body=a3 } -> (function

View File

@ -889,6 +889,7 @@ and type_expression : environment -> Solver.state -> ?tv_opt:O.type_value -> I.e
let e' = Environment.add_ez_binder (fst binder) fresh e in
let%bind (result , state') = type_expression e' state result in
let () = Printf.printf "this does not make use of the typed body, this code sounds buggy." in
let wrapped = Wrap.lambda fresh input_type' output_type' in
return_wrapped
(E_lambda {binder = fst binder; body=result}) (* TODO: is the type of the entire lambda enough to access the input_type=fresh; ? *)
@ -897,8 +898,17 @@ and type_expression : environment -> Solver.state -> ?tv_opt:O.type_value -> I.e
| E_constant (name, lst) ->
let () = ignore (name , lst) in
let _t = Operators.Typer.Operators_types.constant_type name in
Pervasives.failwith (Format.asprintf "TODO: E_constant (%a(%a))" Stage_common.PP.constant name (Format.pp_print_list Ast_simplified.PP.expression) lst)
let%bind t = Operators.Typer.Operators_types.constant_type name in
let aux acc expr =
let (lst , state) = acc in
let%bind (expr, state') = type_expression e state expr in
ok (expr::lst , state') in
let%bind (lst , state') = bind_fold_list aux ([], state) lst in
let lst_annot = List.map (fun (x : O.value) -> x.type_annotation) lst in
let wrapped = Wrap.constant t lst_annot in
return_wrapped
(E_constant (name, lst))
state' wrapped
(*
let%bind lst' = bind_list @@ List.map (type_expression e) lst in
let tv_lst = List.map get_type_annotation lst' in

View File

@ -490,7 +490,7 @@ and transpile_annotated_expression (ae:AST.annotated_expression) : expression re
)
| E_look_up dsi -> (
let%bind (ds', i') = bind_map_pair f dsi in
return @@ E_constant (C_MAP_GET, [i' ; ds'])
return @@ E_constant (C_MAP_FIND_OPT, [i' ; ds'])
)
| E_sequence (a , b) -> (
let%bind a' = transpile_annotated_expression a in

View File

@ -23,7 +23,7 @@ let is_pure_constant : constant -> bool =
| C_NEG | C_OR | C_AND | C_XOR | C_NOT
| C_EQ | C_NEQ | C_LT | C_LE | C_GT | C_GE
| C_SOME
| C_UPDATE | C_MAP_GET | C_MAP_FIND_OPT | C_MAP_ADD | C_MAP_UPDATE
| C_UPDATE | C_MAP_FIND_OPT | C_MAP_ADD | C_MAP_UPDATE
| C_INT | C_ABS | C_IS_NAT
| C_BALANCE | C_AMOUNT | C_ADDRESS | C_NOW | C_SOURCE | C_SENDER | C_CHAIN_ID
| C_SET_MEM | C_SET_ADD | C_SET_REMOVE | C_SLICE
@ -31,10 +31,10 @@ let is_pure_constant : constant -> bool =
| C_HASH_KEY | C_BYTES_PACK | C_CONCAT
-> true
(* unfortunately impure: *)
| C_ADD | C_SUB |C_MUL|C_DIV|C_MOD
| C_ADD | C_SUB |C_MUL|C_DIV|C_MOD | C_LSL | C_LSR
(* impure: *)
| C_ASSERTION | C_ASSERT_INFERRED
| C_MAP_GET_FORCE | C_MAP_FIND
| C_MAP_FIND
| C_FOLD_WHILE
| C_CALL
(* TODO... *)

View File

@ -66,7 +66,7 @@ module Simplify = struct
module Pascaligo = struct
let constants = function
| "get_force" -> ok C_MAP_GET_FORCE
| "assert" -> ok C_ASSERTION
| "get_chain_id" -> ok C_CHAIN_ID
| "transaction" -> ok C_CALL
| "get_contract" -> ok C_CONTRACT
@ -87,6 +87,8 @@ module Simplify = struct
| "bitwise_or" -> ok C_OR
| "bitwise_and" -> ok C_AND
| "bitwise_xor" -> ok C_XOR
| "bitwise_lsl" -> ok C_LSL
| "bitwise_lsr" -> ok C_LSR
| "string_concat" -> ok C_CONCAT
| "string_slice" -> ok C_SLICE
| "crypto_check" -> ok C_CHECK_SIGNATURE
@ -104,12 +106,13 @@ module Simplify = struct
| "list_iter" -> ok C_LIST_ITER
| "list_fold" -> ok C_LIST_FOLD
| "list_map" -> ok C_LIST_MAP
| "get_force" -> ok C_MAP_FIND
| "map_iter" -> ok C_MAP_ITER
| "map_map" -> ok C_MAP_MAP
| "map_fold" -> ok C_MAP_FOLD
| "map_remove" -> ok C_MAP_REMOVE
| "map_update" -> ok C_MAP_UPDATE
| "map_get" -> ok C_MAP_GET
| "map_get" -> ok C_MAP_FIND_OPT
| "map_mem" -> ok C_MAP_MEM
| "sha_256" -> ok C_SHA256
| "sha_512" -> ok C_SHA512
@ -163,7 +166,6 @@ module Simplify = struct
| "Current.failwith" -> ok C_FAILWITH
| "failwith" -> ok C_FAILWITH
| "Crypto.hash" -> ok C_HASH
| "Crypto.blake2b" -> ok C_BLAKE2b
| "Crypto.sha256" -> ok C_SHA256
| "Crypto.sha512" -> ok C_SHA512
@ -210,6 +212,8 @@ module Simplify = struct
| "Bitwise.lor" -> ok C_OR
| "Bitwise.land" -> ok C_AND
| "Bitwise.lxor" -> ok C_XOR
| "Bitwise.shift_left" -> ok C_LSL
| "Bitwise.shift_right" -> ok C_LSR
| "String.length" -> ok C_SIZE
| "String.size" -> ok C_SIZE
@ -324,51 +328,52 @@ module Typer = struct
let tc_addargs a b c = tc [a;b;c] [ (*TODO…*) ]
let t_none = forall "a" @@ fun a -> option a
let t_sub = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_subarg a b c] => a --> b --> c (* TYPECLASS *)
let t_sub = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_subarg a b c] => tuple2 a b --> c (* TYPECLASS *)
let t_some = forall "a" @@ fun a -> a --> option a
let t_map_remove = forall2 "src" "dst" @@ fun src dst -> src --> map src dst --> map src dst
let t_map_add = forall2 "src" "dst" @@ fun src dst -> src --> dst --> map src dst --> map src dst
let t_map_update = forall2 "src" "dst" @@ fun src dst -> src --> option dst --> map src dst --> map src dst
let t_map_mem = forall2 "src" "dst" @@ fun src dst -> src --> map src dst --> bool
let t_map_find = forall2 "src" "dst" @@ fun src dst -> src --> map src dst --> dst
let t_map_find_opt = forall2 "src" "dst" @@ fun src dst -> src --> map src dst --> option dst
let t_map_fold = forall3 "src" "dst" "acc" @@ fun src dst acc -> ( ( (src * dst) * acc ) --> acc ) --> map src dst --> acc --> acc
let t_map_map = forall3 "k" "v" "result" @@ fun k v result -> ((k * v) --> result) --> map k v --> map k result
let t_map_remove = forall2 "src" "dst" @@ fun src dst -> tuple2 src (map src dst) --> map src dst
let t_map_add = forall2 "src" "dst" @@ fun src dst -> tuple3 src dst (map src dst) --> map src dst
let t_map_update = forall2 "src" "dst" @@ fun src dst -> tuple3 src (option dst) (map src dst) --> map src dst
let t_map_mem = forall2 "src" "dst" @@ fun src dst -> tuple2 src (map src dst) --> bool
let t_map_find = forall2 "src" "dst" @@ fun src dst -> tuple2 src (map src dst) --> dst
let t_map_find_opt = forall2 "src" "dst" @@ fun src dst -> tuple2 src (map src dst) --> option dst
let t_map_fold = forall3 "src" "dst" "acc" @@ fun src dst acc -> tuple3 ( ( (src * dst) * acc ) --> acc ) (map src dst) acc --> acc
let t_map_map = forall3 "k" "v" "result" @@ fun k v result -> tuple2 ((k * v) --> result) (map k v) --> map k result
(* TODO: the type of map_map_fold might be wrong, check it. *)
let t_map_map_fold = forall4 "k" "v" "acc" "dst" @@ fun k v acc dst -> ( ((k * v) * acc) --> acc * dst ) --> map k v --> (k * v) --> (map k dst * acc)
let t_map_iter = forall2 "k" "v" @@ fun k v -> ( (k * v) --> unit ) --> map k v --> unit
let t_size = forall_tc "c" @@ fun c -> [tc_sizearg c] => c --> nat (* TYPECLASS *)
let t_slice = nat --> nat --> string --> string
let t_failwith = string --> unit
let t_get_force = forall2 "src" "dst" @@ fun src dst -> src --> map src dst --> dst
let t_int = nat --> int
let t_bytes_pack = forall_tc "a" @@ fun a -> [tc_packable a] => a --> bytes (* TYPECLASS *)
let t_bytes_unpack = forall_tc "a" @@ fun a -> [tc_packable a] => bytes --> a (* TYPECLASS *)
let t_hash256 = bytes --> bytes
let t_hash512 = bytes --> bytes
let t_blake2b = bytes --> bytes
let t_hash_key = key --> key_hash
let t_check_signature = key --> signature --> bytes --> bool
let t_sender = address
let t_source = address
let t_unit = unit
let t_amount = mutez
let t_address = address
let t_now = timestamp
let t_transaction = forall "a" @@ fun a -> a --> mutez --> contract a --> operation
let t_get_contract = forall "a" @@ fun a -> contract a
let t_abs = int --> nat
let t_cons = forall "a" @@ fun a -> a --> list a --> list a
let t_assertion = bool --> unit
let t_times = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_timargs a b c] => a --> b --> c (* TYPECLASS *)
let t_div = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_divargs a b c] => a --> b --> c (* TYPECLASS *)
let t_mod = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_modargs a b c] => a --> b --> c (* TYPECLASS *)
let t_add = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_addargs a b c] => a --> b --> c (* TYPECLASS *)
let t_set_mem = forall "a" @@ fun a -> a --> set a --> bool
let t_set_add = forall "a" @@ fun a -> a --> set a --> set a
let t_set_remove = forall "a" @@ fun a -> a --> set a --> set a
let t_not = bool --> bool
let t_map_map_fold = forall4 "k" "v" "acc" "dst" @@ fun k v acc dst -> tuple3 ( ((k * v) * acc) --> acc * dst ) (map k v) (k * v) --> (map k dst * acc)
let t_map_iter = forall2 "k" "v" @@ fun k v -> tuple2 ( (k * v) --> unit ) (map k v) --> unit
let t_size = forall_tc "c" @@ fun c -> [tc_sizearg c] => tuple1 c --> nat (* TYPECLASS *)
let t_slice = tuple3 nat nat string --> string
let t_failwith = tuple1 string --> unit
let t_get_force = forall2 "src" "dst" @@ fun src dst -> tuple2 src (map src dst) --> dst
let t_int = tuple1 nat --> int
let t_bytes_pack = forall_tc "a" @@ fun a -> [tc_packable a] => tuple1 a --> bytes (* TYPECLASS *)
let t_bytes_unpack = forall_tc "a" @@ fun a -> [tc_packable a] => tuple1 bytes --> a (* TYPECLASS *)
let t_hash256 = tuple1 bytes --> bytes
let t_hash512 = tuple1 bytes --> bytes
let t_blake2b = tuple1 bytes --> bytes
let t_hash_key = tuple1 key --> key_hash
let t_check_signature = tuple3 key signature bytes --> bool
let t_chain_id = tuple0 --> chain_id
let t_sender = tuple0 --> address
let t_source = tuple0 --> address
let t_unit = tuple0 --> unit
let t_amount = tuple0 --> mutez
let t_address = tuple0 --> address
let t_now = tuple0 --> timestamp
let t_transaction = forall "a" @@ fun a -> tuple3 a mutez (contract a) --> operation
let t_get_contract = forall "a" @@ fun a -> tuple0 --> contract a
let t_abs = tuple1 int --> nat
let t_cons = forall "a" @@ fun a -> a --> tuple1 (list a) --> list a
let t_assertion = tuple1 bool --> unit
let t_times = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_timargs a b c] => tuple2 a b --> c (* TYPECLASS *)
let t_div = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_divargs a b c] => tuple2 a b --> c (* TYPECLASS *)
let t_mod = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_modargs a b c] => tuple2 a b --> c (* TYPECLASS *)
let t_add = forall3_tc "a" "b" "c" @@ fun a b c -> [tc_addargs a b c] => tuple2 a b --> c (* TYPECLASS *)
let t_set_mem = forall "a" @@ fun a -> tuple2 a (set a) --> bool
let t_set_add = forall "a" @@ fun a -> tuple2 a (set a) --> set a
let t_set_remove = forall "a" @@ fun a -> tuple2 a (set a) --> set a
let t_not = tuple1 bool --> bool
let constant_type : constant -> Typesystem.Core.type_value result = function
| C_INT -> ok @@ t_int ;
@ -396,6 +401,8 @@ module Typer = struct
| C_AND -> ok @@ failwith "t_and" ;
| C_OR -> ok @@ failwith "t_or" ;
| C_XOR -> ok @@ failwith "t_xor" ;
| C_LSL -> ok @@ failwith "t_lsl" ;
| C_LSR -> ok @@ failwith "t_lsr" ;
(* COMPARATOR *)
| C_EQ -> ok @@ failwith "t_comparator EQ" ;
| C_NEQ -> ok @@ failwith "t_comparator NEQ" ;
@ -424,8 +431,6 @@ module Typer = struct
| C_LIST_FOLD -> ok @@ failwith "t_list_fold" ;
| C_LIST_CONS -> ok @@ failwith "t_list_cons" ;
(* MAP *)
| C_MAP_GET -> ok @@ failwith "t_map_get" ;
| C_MAP_GET_FORCE -> ok @@ failwith "t_map_get_force" ;
| C_MAP_ADD -> ok @@ t_map_add ;
| C_MAP_REMOVE -> ok @@ t_map_remove ;
| C_MAP_UPDATE -> ok @@ t_map_update ;
@ -442,7 +447,7 @@ module Typer = struct
| C_BLAKE2b -> ok @@ t_blake2b ;
| C_HASH_KEY -> ok @@ t_hash_key ;
| C_CHECK_SIGNATURE -> ok @@ t_check_signature ;
| C_CHAIN_ID -> ok @@ failwith "t_chain_id" ;
| C_CHAIN_ID -> ok @@ t_chain_id ;
(*BLOCKCHAIN *)
| C_CONTRACT -> ok @@ t_get_contract ;
| C_CONTRACT_ENTRYPOINT -> ok @@ failwith "t_get_entrypoint" ;
@ -562,16 +567,6 @@ module Typer = struct
let default = t_unit () in
ok @@ Simple_utils.Option.unopt ~default opt
let map_get_force = typer_2 "MAP_GET_FORCE" @@ fun i m ->
let%bind (src, dst) = bind_map_or (get_t_map , get_t_big_map) m in
let%bind _ = assert_type_value_eq (src, i) in
ok dst
let map_get = typer_2 "MAP_GET" @@ fun i m ->
let%bind (src, dst) = bind_map_or (get_t_map , get_t_big_map) m in
let%bind _ = assert_type_value_eq (src, i) in
ok @@ t_option dst ()
let int : typer = typer_1 "INT" @@ fun t ->
let%bind () = assert_t_nat t in
ok @@ t_int ()
@ -1006,6 +1001,8 @@ module Typer = struct
| C_AND -> ok @@ and_ ;
| C_OR -> ok @@ or_ ;
| C_XOR -> ok @@ xor ;
| C_LSL -> ok @@ lsl_;
| C_LSR -> ok @@ lsr_;
(* COMPARATOR *)
| C_EQ -> ok @@ comparator "EQ" ;
| C_NEQ -> ok @@ comparator "NEQ" ;
@ -1034,8 +1031,6 @@ module Typer = struct
| C_LIST_FOLD -> ok @@ list_fold ;
| C_LIST_CONS -> ok @@ list_cons ;
(* MAP *)
| C_MAP_GET -> ok @@ map_get ;
| C_MAP_GET_FORCE -> ok @@ map_get_force ;
| C_MAP_ADD -> ok @@ map_add ;
| C_MAP_REMOVE -> ok @@ map_remove ;
| C_MAP_UPDATE -> ok @@ map_update ;
@ -1103,6 +1098,8 @@ module Compiler = struct
| C_OR -> ok @@ simple_binary @@ prim I_OR
| C_AND -> ok @@ simple_binary @@ prim I_AND
| C_XOR -> ok @@ simple_binary @@ prim I_XOR
| C_LSL -> ok @@ simple_binary @@ prim I_LSL
| C_LSR -> ok @@ simple_binary @@ prim I_LSR
| C_NOT -> ok @@ simple_unary @@ prim I_NOT
| C_PAIR -> ok @@ simple_binary @@ prim I_PAIR
| C_CAR -> ok @@ simple_unary @@ prim I_CAR
@ -1115,9 +1112,7 @@ module Compiler = struct
| C_GE -> ok @@ simple_binary @@ seq [prim I_COMPARE ; prim I_GE]
| C_UPDATE -> ok @@ simple_ternary @@ prim I_UPDATE
| C_SOME -> ok @@ simple_unary @@ prim I_SOME
| C_MAP_GET_FORCE -> ok @@ simple_binary @@ seq [prim I_GET ; i_assert_some_msg (i_push_string "GET_FORCE")]
| C_MAP_FIND -> ok @@ simple_binary @@ seq [prim I_GET ; i_assert_some_msg (i_push_string "MAP FIND")]
| C_MAP_GET -> ok @@ simple_binary @@ prim I_GET
| C_MAP_MEM -> ok @@ simple_binary @@ prim I_MEM
| C_MAP_FIND_OPT -> ok @@ simple_binary @@ prim I_GET
| C_MAP_ADD -> ok @@ simple_ternary @@ seq [dip (i_some) ; prim I_UPDATE]
@ -1128,7 +1123,7 @@ module Compiler = struct
| C_SIZE -> ok @@ simple_unary @@ prim I_SIZE
| C_FAILWITH -> ok @@ simple_unary @@ prim I_FAILWITH
| C_ASSERT_INFERRED -> ok @@ simple_binary @@ i_if (seq [i_failwith]) (seq [i_drop ; i_push_unit])
| C_ASSERTION -> ok @@ simple_unary @@ i_if (seq [i_push_unit]) (seq [i_push_unit ; i_failwith])
| C_ASSERTION -> ok @@ simple_unary @@ i_if (seq [i_push_unit]) (seq [i_push_string "failed assertion" ; i_failwith])
| C_INT -> ok @@ simple_unary @@ prim I_INT
| C_ABS -> ok @@ simple_unary @@ prim I_ABS
| C_IS_NAT -> ok @@ simple_unary @@ prim I_ISNAT

View File

@ -45,6 +45,8 @@ let constant ppf : constant -> unit = function
| C_AND -> fprintf ppf "AND"
| C_OR -> fprintf ppf "OR"
| C_XOR -> fprintf ppf "XOR"
| C_LSL -> fprintf ppf "LSL"
| C_LSR -> fprintf ppf "LSR"
(* COMPARATOR *)
| C_EQ -> fprintf ppf "EQ"
| C_NEQ -> fprintf ppf "NEQ"
@ -82,8 +84,6 @@ let constant ppf : constant -> unit = function
| C_MAP -> fprintf ppf "MAP"
| C_MAP_EMPTY -> fprintf ppf "MAP_EMPTY"
| C_MAP_LITERAL -> fprintf ppf "MAP_LITERAL"
| C_MAP_GET -> fprintf ppf "MAP_GET"
| C_MAP_GET_FORCE -> fprintf ppf "MAP_GET_FORCE"
| C_MAP_ADD -> fprintf ppf "MAP_ADD"
| C_MAP_REMOVE -> fprintf ppf "MAP_REMOVE"
| C_MAP_UPDATE -> fprintf ppf "MAP_UPDATE"
@ -101,7 +101,6 @@ let constant ppf : constant -> unit = function
| C_SHA256 -> fprintf ppf "SHA256"
| C_SHA512 -> fprintf ppf "SHA512"
| C_BLAKE2b -> fprintf ppf "BLAKE2b"
| C_HASH -> fprintf ppf "HASH"
| C_HASH_KEY -> fprintf ppf "HASH_KEY"
| C_CHECK_SIGNATURE -> fprintf ppf "CHECK_SIGNATURE"
| C_CHAIN_ID -> fprintf ppf "CHAIN_ID"

View File

@ -162,6 +162,8 @@ type constant =
| C_AND
| C_OR
| C_XOR
| C_LSL
| C_LSR
(* COMPARATOR *)
| C_EQ
| C_NEQ
@ -199,8 +201,6 @@ type constant =
| C_MAP
| C_MAP_EMPTY
| C_MAP_LITERAL
| C_MAP_GET
| C_MAP_GET_FORCE
| C_MAP_ADD
| C_MAP_REMOVE
| C_MAP_UPDATE
@ -218,7 +218,6 @@ type constant =
| C_SHA256
| C_SHA512
| C_BLAKE2b
| C_HASH
| C_HASH_KEY
| C_CHECK_SIGNATURE
| C_CHAIN_ID

View File

@ -54,6 +54,7 @@ let mutez = P_constant (C_mutez , [])
let timestamp = P_constant (C_timestamp , [])
let int = P_constant (C_int , [])
let address = P_constant (C_address , [])
let chain_id = P_constant (C_chain_id , [])
let bytes = P_constant (C_bytes , [])
let key = P_constant (C_key , [])
let key_hash = P_constant (C_key_hash , [])
@ -61,3 +62,9 @@ let signature = P_constant (C_signature , [])
let operation = P_constant (C_operation , [])
let contract t = P_constant (C_contract , [t])
let ( * ) a b = pair a b
(* These are used temporarily to de-curry functions that correspond to Michelson operators *)
let tuple0 = P_constant (C_tuple , [])
let tuple1 a = P_constant (C_tuple , [a])
let tuple2 a b = P_constant (C_tuple , [a; b])
let tuple3 a b c = P_constant (C_tuple , [a; b; c])

View File

@ -1,10 +1,16 @@
// Test PascaLIGO bitwise operators
function or_op (const n : nat) : nat is
begin skip end with bitwise_or(n , 4n)
bitwise_or(n , 4n)
function and_op (const n : nat) : nat is
begin skip end with bitwise_and(n , 7n)
bitwise_and(n , 7n)
function xor_op (const n : nat) : nat is
begin skip end with bitwise_xor(n , 7n)
bitwise_xor(n , 7n)
function lsl_op (const n : nat) : nat is
bitwise_lsl(n , 7n)
function lsr_op (const n : nat) : nat is
bitwise_lsr(n , 7n)

View File

@ -3,3 +3,5 @@
let or_op (n: nat) : nat = Bitwise.lor n 4n
let and_op (n: nat) : nat = Bitwise.land n 7n
let xor_op (n: nat) : nat = Bitwise.lxor n 7n
let lsl_op (n: nat) : nat = Bitwise.shift_left n 7n
let lsr_op (n: nat) : nat = Bitwise.shift_right n 7n

View File

@ -3,3 +3,5 @@
let or_op = (n: nat): nat => Bitwise.lor(n, 4n);
let and_op = (n: nat): nat => Bitwise.land(n, 7n);
let xor_op = (n: nat): nat => Bitwise.lxor(n, 7n);
let lsl_op = (n: nat) : nat => Bitwise.shift_left(n, 7n);
let lsr_op = (n: nat) : nat => Bitwise.shift_right(n, 7n);

View File

@ -1,19 +0,0 @@
type storage = {
challenge : string;
}
type param = {
new_challenge : string;
attempt : bytes;
}
let attempt (p: param) storage =
if Crypto.hash (Bytes.pack p.attempt) <> Bytes.pack storage.challenge
then failwith "Failed challenge"
else
let contract : unit contract =
Operation.get_contract sender in
let transfer : operation =
Operation.transaction (unit, contract, 10tz) in
let storage : storage = {challenge = p.new_challenge}
in ([] : operation list), storage

View File

@ -348,6 +348,8 @@ let bitwise_arithmetic () : unit result =
let%bind () = expect_eq program "and_op" (e_nat 10) (e_nat 2) in
let%bind () = expect_eq program "xor_op" (e_nat 0) (e_nat 7) in
let%bind () = expect_eq program "xor_op" (e_nat 7) (e_nat 0) in
let%bind () = expect_eq program "lsl_op" (e_nat 1000) (e_nat 128000) in
let%bind () = expect_eq program "lsr_op" (e_nat 128000) (e_nat 1000) in
ok ()
let bitwise_arithmetic_mligo () : unit result =
@ -364,6 +366,8 @@ let bitwise_arithmetic_mligo () : unit result =
let%bind () = expect_eq program "and_op" (e_nat 10) (e_nat 2) in
let%bind () = expect_eq program "xor_op" (e_nat 0) (e_nat 7) in
let%bind () = expect_eq program "xor_op" (e_nat 7) (e_nat 0) in
let%bind () = expect_eq program "lsl_op" (e_nat 1000) (e_nat 128000) in
let%bind () = expect_eq program "lsr_op" (e_nat 128000) (e_nat 1000) in
ok ()
let bitwise_arithmetic_religo () : unit result =
@ -380,6 +384,8 @@ let bitwise_arithmetic_religo () : unit result =
let%bind () = expect_eq program "and_op" (e_nat 10) (e_nat 2) in
let%bind () = expect_eq program "xor_op" (e_nat 0) (e_nat 7) in
let%bind () = expect_eq program "xor_op" (e_nat 7) (e_nat 0) in
let%bind () = expect_eq program "lsl_op" (e_nat 1000) (e_nat 128000) in
let%bind () = expect_eq program "lsr_op" (e_nat 128000) (e_nat 1000) in
ok ()
let string_arithmetic () : unit result =
@ -1458,12 +1464,6 @@ let assert_religo () : unit result =
let%bind _ = expect_eq program "main" (make_input true) make_expected in
ok ()
let guess_the_hash_mligo () : unit result =
let%bind program = mtype_file "./contracts/new-syntax.mligo" in
let make_input = fun n-> e_pair (e_int n) (e_int 42) in
let make_expected = fun n -> e_pair (e_typed_list [] t_operation) (e_int (42 + n)) in
expect_eq_n program "main" make_input make_expected
let guess_string_mligo () : unit result =
let%bind program = type_file "./contracts/guess_string.mligo" in
let make_input = fun n -> e_pair (e_int n) (e_int 42) in
@ -2314,7 +2314,6 @@ let main = test_suite "Integration (End to End)" [
(* test "list matching (mligo)" mligo_list ; *)
test "list matching (mligo)" mligo_list ;
test "list matching (religo)" religo_list ;
(* test "guess the hash mligo" guess_the_hash_mligo ; WIP? *)
test "failwith ligo" failwith_ligo ;
test "failwith mligo" failwith_mligo ;
test "assert mligo" assert_mligo ;

View File

@ -122,6 +122,7 @@ let md_files = [
"/gitlab-pages/docs/advanced/timestamps-addresses.md";
"/gitlab-pages/docs/api/cli-commands.md";
"/gitlab-pages/docs/api/cheat-sheet.md";
"/gitlab-pages/docs/reference/string.md";
]
let md_root = "../../gitlab-pages/docs/language-basics/"

View File

@ -0,0 +1,10 @@
root = true
[*]
insert_final_newline = true
end_of_line = lf
[*.{js,ts,tsx,json,css}]
indent_style = space
indent_size = 2
quote_type = single

4
tools/webide/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
tmp/
dist
*.log

32
tools/webide/Dockerfile Normal file
View File

@ -0,0 +1,32 @@
FROM node:12-alpine as builder
WORKDIR /app
COPY package.json package.json
COPY yarn.lock yarn.lock
COPY packages/client packages/client
COPY packages/server packages/server
RUN yarn install
COPY tsconfig.json tsconfig.json
RUN yarn workspaces run build
FROM node:12-buster
WORKDIR /app
RUN apt-get update && apt-get -y install libev-dev perl pkg-config libgmp-dev libhidapi-dev m4 libcap-dev bubblewrap rsync
COPY ligo_deb10.deb /tmp/ligo_deb10.deb
RUN dpkg -i /tmp/ligo_deb10.deb && rm /tmp/ligo_deb10.deb
COPY --from=builder /app/packages/client/build /app/client/build
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/packages/server/dist/src /app/server/dist
ENV STATIC_ASSETS /app/client
ENV LIGO_CMD /bin/ligo
ENTRYPOINT [ "node", "server/dist/index.js" ]

12
tools/webide/README.md Normal file
View File

@ -0,0 +1,12 @@
# Quick Start
Install `yarn`.
Run `yarn` to install dependencies.
## Server
See the README under the `packages/server/` for information about how to get started on the server development.
## Client
See the README under the `packages/client/` for information about how to get started on the client development.

17
tools/webide/package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "ligo-editor",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
],
"description": "",
"scripts": {
"prestart": "cd packages/client && npm run build",
"start": ""
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com:ligolang/ligo-web-ide.git"
}
}

23
tools/webide/packages/client/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,44 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

View File

@ -0,0 +1,37 @@
(*_*
name: Cameligo Contract
language: cameligo
compile:
entrypoint: main
dryRun:
entrypoint: main
parameters: Increment 1
storage: 0
deploy:
entrypoint: main
storage: 0
evaluateValue:
entrypoint: ""
evaluateFunction:
entrypoint: add
parameters: 5, 6
*_*)
type storage = int
(* variant defining pseudo multi-entrypoint actions *)
type action =
| Increment of int
| Decrement of int
let add (a,b: int * int) : int = a + b
let sub (a,b: int * int) : int = a - b
(* real entrypoint that re-routes the flow based on the action provided *)
let main (p,s: action * storage) =
let storage =
match p with
| Increment n -> add (s, n)
| Decrement n -> sub (s, n)
in ([] : operation list), storage

View File

@ -0,0 +1,38 @@
(*_*
name: Pascaligo Contract
language: pascaligo
compile:
entrypoint: main
dryRun:
entrypoint: main
parameters: Increment (1)
storage: 0
deploy:
entrypoint: main
storage: 0
evaluateValue:
entrypoint: ""
evaluateFunction:
entrypoint: add
parameters: (5, 6)
*_*)
// variant defining pseudo multi-entrypoint actions
type action is
| Increment of int
| Decrement of int
function add (const a : int ; const b : int) : int is
block { skip } with a + b
function subtract (const a : int ; const b : int) : int is
block { skip } with a - b
// real entrypoint that re-routes the flow based
// on the action provided
function main (const p : action ; const s : int) :
(list(operation) * int) is
block { skip } with ((nil : list(operation)),
case p of
| Increment(n) -> add(s, n)
| Decrement(n) -> subtract(s, n)
end)

View File

@ -0,0 +1,39 @@
(*_*
name: Reasonligo Contract
language: reasonligo
compile:
entrypoint: main
dryRun:
entrypoint: main
parameters: Increment (1)
storage: 0
deploy:
entrypoint: main
storage: 0
evaluateValue:
entrypoint: ""
evaluateFunction:
entrypoint: add
parameters: (5, 6)
*_*)
type storage = int;
/* variant defining pseudo multi-entrypoint actions */
type action =
| Increment(int)
| Decrement(int);
let add = ((a,b): (int, int)): int => a + b;
let sub = ((a,b): (int, int)): int => a - b;
/* real entrypoint that re-routes the flow based on the action provided */
let main = ((p,storage): (action, storage)) => {
let storage =
switch (p) {
| Increment(n) => add((storage, n))
| Decrement(n) => sub((storage, n))
};
([]: list(operation), storage);
};

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="ligo_run.svg"
id="svg4550"
version="1.1"
viewBox="0 0 193.35434 193.35434"
height="193.35434"
width="193.35434"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
style="fill:none">
<metadata
id="metadata4554">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1400"
id="namedview4552"
showgrid="false"
inkscape:zoom="1.9624573"
inkscape:cx="24.54412"
inkscape:cy="104.17717"
inkscape:window-x="-12"
inkscape:window-y="-12"
inkscape:window-maximized="1"
inkscape:current-layer="svg4550"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<circle
cx="96.67717"
cy="96.67717"
r="74"
id="circle4541"
style="stroke:url(#paint0_linear);stroke-width:45.35433197;stroke-miterlimit:4;stroke-dasharray:none" />
<defs
id="defs4548">
<linearGradient
id="paint0_linear"
x1="100"
y1="54"
x2="100"
y2="254"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-3.322834,-57.322834)">
<stop
stop-color="#3AA0FF"
id="stop4543" />
<stop
offset="1"
stop-color="#0072DC"
id="stop4545" />
</linearGradient>
</defs>
<path
d="M 137.61977,80.627036 58.899178,68.296666 88.24752,141.00143 137.6208,80.624736 Z"
id="path4537"
inkscape:connector-curvature="0"
style="fill:#fc683a;stroke-width:2.53620434" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,118 @@
const createHash = require('crypto').createHash;
const glob = require('glob');
const join = require('path').join;
const fs = require('fs');
const YAML = require('yamljs');
function urlFriendlyHash(content) {
const hash = createHash('md5');
hash.update(content);
return hash
.digest('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
function convertToJson(content, path) {
const METADATA_REGEX = /\(\*_\*([^]*?)\*_\*\)\s*/;
const match = content.match(METADATA_REGEX);
if (!match || !match[1]) {
throw new Error(`Unable to find compiler configuration in ${path}.`);
}
try {
const config = YAML.parse(match[1]);
config.editor = {
language: config.language,
code: content.replace(METADATA_REGEX, '')
};
delete config.language;
return config;
} catch (ex) {
throw new Error(`${path} doesn't contain valid metadata. ${ex}`);
}
}
function findFiles(pattern, dir) {
return new Promise((resolve, reject) => {
glob(pattern, { cwd: dir }, (error, files) => {
if (error) {
reject(error);
} else {
resolve(files);
}
});
});
}
function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (error, content) => {
if (error) {
reject(error);
} else {
resolve(content);
}
});
});
}
function writeFile(path, config) {
return new Promise((resolve, reject) => {
fs.writeFile(path, JSON.stringify(config), error => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async function processExample(srcDir, file, destDir) {
const path = join(srcDir, file);
console.log(`Processing ${path}`);
const content = await readFile(path);
const config = convertToJson(content, path);
const id = urlFriendlyHash(file);
config.id = id;
await writeFile(join(destDir, id), config);
return { id: id, name: config.name };
}
function processExamples(srcDir, files, destDir) {
return Promise.all(files.map(file => processExample(srcDir, file, destDir)));
}
async function main() {
process.on('unhandledRejection', error => {
throw error;
});
const EXAMPLES_DEST_DIR = join(process.cwd(), 'build', 'static', 'examples');
const EXAMPLES_DIR = join(process.cwd(), 'examples');
const EXAMPLES_GLOB = '**/*.ligo';
const EXAMPLES_LIST_FILE = 'list';
fs.mkdirSync(EXAMPLES_DEST_DIR, { recursive: true });
const files = await findFiles(EXAMPLES_GLOB, EXAMPLES_DIR);
const examples = await processExamples(
EXAMPLES_DIR,
files,
EXAMPLES_DEST_DIR
);
await writeFile(join(EXAMPLES_DEST_DIR, EXAMPLES_LIST_FILE), examples);
}
main();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.25",
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.6",
"@taquito/taquito": "^5.1.0-beta.1",
"@taquito/tezbridge-signer": "^5.1.0-beta.1",
"@types/jest": "24.0.18",
"@types/node": "12.7.12",
"@types/react": "16.9.5",
"@types/react-dom": "16.9.1",
"@types/react-outside-click-handler": "^1.2.0",
"axios": "^0.19.0",
"http-proxy-middleware": "^0.20.0",
"monaco-editor": "npm:@ligolang/monaco-editor@0.18.1",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"react-outside-click-handler": "^1.3.0",
"react-redux": "^7.1.1",
"react-scripts": "3.2.0",
"react-spinners-kit": "^1.9.0",
"redux": "^4.0.4",
"redux-devtools": "^3.5.0",
"redux-thunk": "^2.3.0",
"styled-components": "^4.4.0",
"typescript": "3.6.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"postbuild": "node package-examples.js",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/react-redux": "^7.1.4",
"@types/styled-components": "^4.1.19",
"glob": "^7.1.6",
"node-sass": "^4.12.0",
"yamljs": "^0.3.0"
}
}

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="The LIGO Playground for learning LIGO" />
<link rel="apple-touch-icon" href="logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=UA-153751765-1"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "UA-153751765-1");
</script>
<script src="https://www.tezbridge.com/plugin.js"></script>
<title>LIGO Playground</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "LIGO Playground",
"name": "The LIGO Playground for learning LIGO",
"icons": [
{
"src": "logo.svg",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/svg+xml"
},
{
"src": "logo.svg",
"type": "image/svg+xml",
"sizes": "192x192"
},
{
"src": "logo.svg",
"type": "image/svg+xml",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@ -0,0 +1,55 @@
import React from 'react';
import { Provider } from 'react-redux';
import styled from 'styled-components';
import { EditorComponent } from './components/editor';
import { Examples } from './components/examples';
import { FloatButtonComponent } from './components/float-button';
import { HeaderComponent } from './components/header';
import { TabsPanelComponent } from './components/tabs-panel';
import configureStore from './configure-store';
const store = configureStore();
const Container = styled.div`
display: flex;
padding: 0.5em 1em;
`;
const FeedbackContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
right: 1em;
bottom: 1em;
position: absolute;
`;
const App: React.FC = () => {
return (
<Provider store={store}>
<HeaderComponent></HeaderComponent>
<Container>
<Examples></Examples>
<EditorComponent></EditorComponent>
<TabsPanelComponent></TabsPanelComponent>
</Container>
<FeedbackContainer>
<FloatButtonComponent
tooltip="Report an issue"
text="!"
href="https://gitlab.com/ligolang/ligo-web-ide/issues"
></FloatButtonComponent>
<FloatButtonComponent
tooltip="Ask a question"
text="?"
href="https://discord.gg/9rhYaEt"
></FloatButtonComponent>
</FeedbackContainer>
</Provider>
);
};
export default App;

View File

@ -0,0 +1,58 @@
import { faCheck } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState } from 'react';
import styled, { css } from 'styled-components';
const Container = styled.div<{ checked: boolean }>`
display: flex;
justify-content: center;
align-items: center;
height: 2.5em;
width: 2.5em;
background: var(--blue_trans2);
cursor: pointer;
`;
const CheckIcon = ({ visible, ...props }: { visible: boolean }) => (
<FontAwesomeIcon {...props} size="2x" icon={faCheck}></FontAwesomeIcon>
);
const Check = styled(CheckIcon)`
pointer-events: none;
opacity: 1;
transform: scale(1);
transition: transform 0.2s ease-in;
color: var(--orange);
${props =>
!props.visible &&
css`
transition: scale(1);
opacity: 0;
`}
`;
export const CheckboxComponent = (props: {
checked: boolean;
onChanged: (value: boolean) => void;
className?: string;
}) => {
const [isChecked, setChecked] = useState(props.checked);
return (
<Container
className={props.className}
checked={isChecked}
onClick={() => {
const newState = !isChecked;
setChecked(newState);
props.onChanged(newState);
}}
>
<Check visible={isChecked}></Check>
</Container>
);
};

View File

@ -0,0 +1,146 @@
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState } from 'react';
import OutsideClickHandler from 'react-outside-click-handler';
import styled, { css } from 'styled-components';
import { Command } from '../redux/types';
const Container = styled.div`
flex: 2;
display: flex;
position: relative;
min-width: 8em;
z-index: 2;
`;
const Header = styled.div`
cursor: pointer;
user-select: none;
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 2em;
padding: 0 0.5em;
border: 1px solid var(--blue_trans1);
`;
const ArrowIcon = ({ rotate, ...props }: { rotate: boolean }) => (
<FontAwesomeIcon {...props} icon={faCaretDown} size="lg"></FontAwesomeIcon>
);
const Arrow = styled(ArrowIcon)`
pointer-events: none;
color: var(--blue_trans1);
transition: transform 0.15s ease-in;
${(props: { rotate: boolean }) =>
props.rotate &&
css`
transform: rotate(180deg);
`};
`;
const List = styled.ul`
position: absolute;
list-style-type: none;
background-color: white;
width: 100%;
margin: 0;
padding: 0;
box-shadow: 1px 3px 10px 0px rgba(153, 153, 153, 0.4);
border-radius: 3px;
visibility: hidden;
opacity: 0;
transition: opacity 0.15s ease-in;
${(props: { visible: boolean }) =>
props.visible &&
css`
visibility: visible;
opacity: 1;
`}
`;
const Option = styled.li`
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
justify-content: space-between;
height: 2em;
padding: 0 0.5em;
&:first-child {
border-radius: 3px 3px 0 0;
}
&:last-child {
border-radius: 0 0 3px 3px;
}
&:hover {
background-color: var(--blue_trans2);
font-weight: 600;
}
`;
export const CommandSelectComponent = (props: {
selected: Command;
onChange?: (value: Command) => void;
}) => {
const OPTIONS = {
[Command.Compile]: 'Compile',
[Command.Deploy]: 'Deploy',
[Command.DryRun]: 'Dry Run',
[Command.EvaluateFunction]: 'Evaluate Function',
[Command.EvaluateValue]: 'Evaluate Value'
};
const moveOptionToTop = (option: Command) => {
return Object.keys(OPTIONS).reduce((list, entry) => {
if (entry === option) {
list.unshift(entry);
} else {
list.push(entry as Command);
}
return list;
}, [] as Command[]);
};
const [opened, open] = useState(false);
const selectOption = (option: Command) => {
if (props.selected !== option && props.onChange) {
props.onChange(option);
}
open(false);
};
return (
<Container>
<OutsideClickHandler onOutsideClick={() => open(false)}>
<List visible={opened}>
{moveOptionToTop(props.selected).map(option => (
<Option
id={option}
key={option}
onClick={() => selectOption(option)}
>
<span>{OPTIONS[option]}</span>
</Option>
))}
</List>
</OutsideClickHandler>
<Header id="command-select" onClick={() => open(true)}>
<span>{OPTIONS[props.selected]}</span>
<Arrow rotate={opened}></Arrow>
</Header>
</Container>
);
};

View File

@ -0,0 +1,31 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeEntrypointAction, CompileState } from '../redux/compile';
import { Group, Input, Label } from './inputs';
const Container = styled.div``;
export const CompilePaneComponent = () => {
const dispatch = useDispatch();
const entrypoint = useSelector<AppState, CompileState['entrypoint']>(
state => state.compile.entrypoint
);
return (
<Container>
<Group>
<Label htmlFor="entrypoint">Entrypoint</Label>
<Input
id="entrypoint"
value={entrypoint}
onChange={ev =>
dispatch({ ...new ChangeEntrypointAction(ev.target.value) })
}
></Input>
</Group>
</Container>
);
};

View File

@ -0,0 +1,142 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled, { css } from 'styled-components';
import { CompileAction } from '../redux/actions/compile';
import { DeployAction } from '../redux/actions/deploy';
import { DryRunAction } from '../redux/actions/dry-run';
import { EvaluateFunctionAction } from '../redux/actions/evaluate-function';
import { EvaluateValueAction } from '../redux/actions/evaluate-value';
import { AppState } from '../redux/app';
import { ChangeDispatchedAction, ChangeSelectedAction, CommandState } from '../redux/command';
import { Command } from '../redux/types';
import { CommandSelectComponent } from './command-select';
import { CompilePaneComponent } from './compile-pane';
import { DeployPaneComponent } from './deploy-pane';
import { DryRunPaneComponent } from './dry-run-pane';
import { EvaluateFunctionPaneComponent } from './evaluate-function-pane';
import { EvaluateValuePaneComponent } from './evaluate-value-pane';
const Container = styled.div<{ visible?: boolean }>`
position: absolute;
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 1em 1em 0 1em;
display: flex;
flex-direction: column;
transform: translateX(-100%);
transition: transform 0.2s ease-in;
${props =>
props.visible &&
css`
transform: translateX(0px);
`}
`;
const CommonActionsGroup = styled.div`
display: flex;
align-items: center;
`;
const RunButton = styled.div`
cursor: pointer;
user-select: none;
display: flex;
justify-content: center;
align-items: center;
flex: 1;
min-height: 2em;
min-width: 3em;
margin-left: 1em;
color: white;
background-color: var(--orange);
`;
const CommandPaneContainer = styled.div`
padding-top: 1em;
`;
function createAction(command: Command) {
switch (command) {
case Command.Compile:
return new CompileAction();
case Command.DryRun:
return new DryRunAction();
case Command.Deploy:
return new DeployAction();
case Command.EvaluateValue:
return new EvaluateValueAction();
case Command.EvaluateFunction:
return new EvaluateFunctionAction();
default:
throw new Error('Unsupported command');
}
}
export const ConfigureTabComponent = (props: {
selected?: boolean;
onRun?: () => void;
}) => {
const dispatchedAction = useSelector<
AppState,
CommandState['dispatchedAction']
>(state => state.command.dispatchedAction);
const command = useSelector<AppState, CommandState['selected']>(
state => state.command.selected
);
const dispatch = useDispatch();
return (
<Container visible={props.selected}>
<CommonActionsGroup>
<CommandSelectComponent
selected={command}
onChange={command => {
dispatch({ ...new ChangeSelectedAction(command) });
}}
></CommandSelectComponent>
<RunButton
id="run"
onClick={() => {
if (dispatchedAction) {
dispatchedAction.cancel();
}
const newAction = createAction(command);
dispatch(newAction.getAction());
dispatch({ ...new ChangeDispatchedAction(newAction) });
props.onRun!();
}}
>
Run
</RunButton>
</CommonActionsGroup>
<CommandPaneContainer>
{(command === Command.Compile && (
<CompilePaneComponent></CompilePaneComponent>
)) ||
(command === Command.DryRun && (
<DryRunPaneComponent></DryRunPaneComponent>
)) ||
(command === Command.Deploy && (
<DeployPaneComponent></DeployPaneComponent>
)) ||
(command === Command.EvaluateFunction && (
<EvaluateFunctionPaneComponent></EvaluateFunctionPaneComponent>
)) ||
(command === Command.EvaluateValue && (
<EvaluateValuePaneComponent></EvaluateValuePaneComponent>
))}
</CommandPaneContainer>
</Container>
);
};

View File

@ -0,0 +1,69 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeEntrypointAction, ChangeStorageAction, DeployState, UseTezBridgeAction } from '../redux/deploy';
import { CheckboxComponent } from './checkbox';
import { Group, HGroup, Input, Label, Textarea } from './inputs';
const Container = styled.div``;
const Checkbox = styled(CheckboxComponent)`
margin-right: 0.3em;
`;
const Hint = styled.span`
font-style: italic;
font-size: 0.8em;
`;
export const DeployPaneComponent = () => {
const dispatch = useDispatch();
const entrypoint = useSelector<AppState, DeployState['entrypoint']>(
state => state.deploy.entrypoint
);
const storage = useSelector<AppState, DeployState['storage']>(
state => state.deploy.storage
);
const useTezBridge = useSelector<AppState, DeployState['useTezBridge']>(
state => state.deploy.useTezBridge
);
return (
<Container>
<Group>
<Label htmlFor="entrypoint">Entrypoint</Label>
<Input
id="entrypoint"
value={entrypoint}
onChange={ev =>
dispatch({ ...new ChangeEntrypointAction(ev.target.value) })
}
></Input>
</Group>
<Group>
<Label htmlFor="storage">Storage</Label>
<Textarea
id="storage"
rows={9}
value={storage}
onChange={ev =>
dispatch({ ...new ChangeStorageAction(ev.target.value) })
}
></Textarea>
</Group>
<HGroup>
<Checkbox
checked={!useTezBridge}
onChanged={value => dispatch({ ...new UseTezBridgeAction(!value) })}
></Checkbox>
<Label htmlFor="tezbridge">
We'll sign for you
<br />
<Hint>Got your own key? Deselect to sign with TezBridge</Hint>
</Label>
</HGroup>
</Container>
);
};

View File

@ -0,0 +1,59 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeEntrypointAction, ChangeParametersAction, ChangeStorageAction, DryRunState } from '../redux/dry-run';
import { Group, Input, Label, Textarea } from './inputs';
const Container = styled.div``;
export const DryRunPaneComponent = () => {
const dispatch = useDispatch();
const entrypoint = useSelector<AppState, DryRunState['entrypoint']>(
state => state.dryRun.entrypoint
);
const parameters = useSelector<AppState, DryRunState['parameters']>(
state => state.dryRun.parameters
);
const storage = useSelector<AppState, DryRunState['storage']>(
state => state.dryRun.storage
);
return (
<Container>
<Group>
<Label htmlFor="entrypoint">Entrypoint</Label>
<Input
id="entrypoint"
value={entrypoint}
onChange={ev =>
dispatch({ ...new ChangeEntrypointAction(ev.target.value) })
}
></Input>
</Group>
<Group>
<Label htmlFor="parameters">Parameters</Label>
<Textarea
id="parameters"
rows={9}
value={parameters}
onChange={ev =>
dispatch({ ...new ChangeParametersAction(ev.target.value) })
}
></Textarea>
</Group>
<Group>
<Label htmlFor="storage">Storage</Label>
<Textarea
id="storage"
rows={9}
value={storage}
onChange={ev =>
dispatch({ ...new ChangeStorageAction(ev.target.value) })
}
></Textarea>
</Group>
</Container>
);
};

View File

@ -0,0 +1,32 @@
import React from 'react';
import styled from 'styled-components';
import { MonacoComponent } from './monaco';
import { ShareComponent } from './share';
import { SyntaxSelectComponent } from './syntax-select';
const Container = styled.div`
flex: 2;
`;
const Header = styled.div`
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 2.5em;
border-bottom: 5px solid var(--blue_trans1);
`;
export const EditorComponent = () => {
return (
<Container>
<Header>
<SyntaxSelectComponent></SyntaxSelectComponent>
<ShareComponent></ShareComponent>
</Header>
<MonacoComponent></MonacoComponent>
</Container>
);
};

View File

@ -0,0 +1,45 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeEntrypointAction, ChangeParametersAction, EvaluateFunctionState } from '../redux/evaluate-function';
import { Group, Input, Label, Textarea } from './inputs';
const Container = styled.div``;
export const EvaluateFunctionPaneComponent = () => {
const dispatch = useDispatch();
const entrypoint = useSelector<AppState, EvaluateFunctionState['entrypoint']>(
state => state.evaluateFunction.entrypoint
);
const parameters = useSelector<AppState, EvaluateFunctionState['parameters']>(
state => state.evaluateFunction.parameters
);
return (
<Container>
<Group>
<Label htmlFor="entrypoint">Entrypoint</Label>
<Input
id="entrypoint"
value={entrypoint}
onChange={ev =>
dispatch({ ...new ChangeEntrypointAction(ev.target.value) })
}
></Input>
</Group>
<Group>
<Label htmlFor="parameters">Parameters</Label>
<Textarea
id="parameters"
rows={9}
value={parameters}
onChange={ev =>
dispatch({ ...new ChangeParametersAction(ev.target.value) })
}
></Textarea>
</Group>
</Container>
);
};

View File

@ -0,0 +1,31 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeEntrypointAction, EvaluateValueState } from '../redux/evaluate-value';
import { Group, Input, Label } from './inputs';
const Container = styled.div``;
export const EvaluateValuePaneComponent = () => {
const dispatch = useDispatch();
const entrypoint = useSelector<AppState, EvaluateValueState['entrypoint']>(
state => state.evaluateValue.entrypoint
);
return (
<Container>
<Group>
<Label htmlFor="entrypoint">Entrypoint</Label>
<Input
id="entrypoint"
value={entrypoint}
onChange={ev =>
dispatch({ ...new ChangeEntrypointAction(ev.target.value) })
}
></Input>
</Group>
</Container>
);
};

View File

@ -0,0 +1,107 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeSelectedAction, ExamplesState } from '../redux/examples';
import { getExample } from '../services/api';
const bgColor = 'transparent';
const borderSize = '5px';
const verticalPadding = '0.8em';
const Container = styled.div`
flex: 0.5;
display: flex;
flex-direction: column;
`;
const MenuItem = styled.div<{ selected: boolean }>`
padding: ${verticalPadding} 0 ${verticalPadding} 1em;
height: 1.5em;
display: flex;
align-items: center;
cursor: pointer;
background-color: ${props =>
props.selected ? 'var(--blue_trans1)' : bgColor};
border-left: ${`${borderSize} solid ${bgColor}`};
border-left-color: ${props => (props.selected ? 'var(--blue)' : bgColor)};
:first-child {
margin-top: ${props => (props.selected ? '0' : `-${borderSize}`)};
}
:hover {
background-color: ${props =>
props.selected ? 'var(--blue_trans1)' : 'var(--blue_trans2)'};
border-left: ${`${borderSize} solid ${bgColor}`};
border-left-color: ${props =>
props.selected ? 'var(--blue)' : 'transparent'};
:first-child {
margin-top: ${props => (props.selected ? '0' : `-${borderSize}`)};
padding-top: ${props =>
props.selected
? `${verticalPadding}`
: `calc(${verticalPadding} - ${borderSize})`};
border-top: ${props =>
props.selected ? '' : `${borderSize} solid var(--blue_opaque1)`};
}
}
`;
const MenuContainer = styled.div`
display: flex;
flex-direction: column;
overflow-y: auto;
height: var(--content_height);
box-sizing: border-box;
`;
const Header = styled.div<{ firstChildSelected: boolean }>`
border-bottom: ${props =>
props.firstChildSelected ? '' : '5px solid var(--blue_trans1)'};
min-height: 2.5em;
padding: 0 10px;
display: flex;
align-items: center;
`;
export const Examples = () => {
const examples = useSelector<AppState, ExamplesState['list']>(
(state: AppState) => state.examples.list
);
const selectedExample = useSelector<AppState, ExamplesState['selected']>(
(state: AppState) => state.examples.selected
);
const dispatch = useDispatch();
return (
<Container>
<Header
firstChildSelected={
!!selectedExample && examples[0].id === selectedExample.id
}
>
<span>Examples</span>
</Header>
<MenuContainer>
{examples.map(example => {
return (
<MenuItem
id={example.id}
key={example.id}
selected={!!selectedExample && example.id === selectedExample.id}
onClick={async () => {
const response = await getExample(example.id);
dispatch({ ...new ChangeSelectedAction(response) });
}}
>
{example.name}
</MenuItem>
);
})}
</MenuContainer>
</Container>
);
};

View File

@ -0,0 +1,83 @@
import React, { useState } from 'react';
import styled, { css } from 'styled-components';
const Container = styled.div`
display: flex;
align-items: center;
justify-content: center;
`;
const Button = styled.a`
margin: 0.1em;
width: 1.5em;
height: 1.5em;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.5em;
font-weight: bolder;
text-decoration: none;
color: rgba(255, 255, 255, 0.85);
background-color: var(--button_float);
box-shadow: 1px 3px 15px 0px rgba(153, 153, 153, 0.4);
cursor: pointer;
user-select: none;
transform-origin: center center;
transition: all 0.2s ease;
&:hover {
box-shadow: var(--box-shadow);
background-color: var(--blue);
color: rgb(255, 255, 255);
transform: scale(1.2);
}
`;
const Tooltip = styled.div<{ visible?: boolean }>`
position: absolute;
pointer-events: none;
z-index: 3;
white-space: nowrap;
transform: translateX(-6.5em);
font-size: var(--font_sub_size);
color: var(--tooltip_foreground);
background-color: var(--tooltip_background);
border-radius: 6px;
padding: 5px 10px;
opacity: 0;
transition: opacity 0.2s ease 0.2s;
${props =>
props.visible &&
css`
opacity: 1;
`}
`;
export const FloatButtonComponent = (props: {
tooltip: string;
text: string;
href: string;
className?: string;
}) => {
const [isTooltipShowing, setShowTooltip] = useState(false);
return (
<Container className={props.className}>
<Tooltip visible={isTooltipShowing}>{props.tooltip}</Tooltip>
<Button
onMouseOver={() => setShowTooltip(true)}
onMouseOut={() => setShowTooltip(false)}
href={props.href}
target="_blank"
rel="noopener noreferrer"
>
{props.text}
</Button>
</Container>
);
};

View File

@ -0,0 +1,68 @@
import React from 'react';
import styled, { css } from 'styled-components';
const Container = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5em 1em;
font-family: 'DM Sans', 'Open Sans', sans-serif;
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.3);
`;
const Group = styled.div`
display: flex;
align-items: center;
`;
const Logo = styled.div`
font-size: 1.25em;
`;
const Link = styled.a`
text-decoration: none;
color: black;
padding: 0.5em 1em;
&:hover {
color: #3aa0ff;
}
${(props: { versionStyle?: boolean }) =>
props.versionStyle &&
css`
background-color: #efefef;
font-weight: 600;
margin-left: 3em;
&:hover {
color: black;
}
`}
`;
export const HeaderComponent = () => {
return (
<Container>
<Group>
<Link href="https://ligolang.org">
<Logo>LIGO</Logo>
</Link>
<Link versionStyle href="https://ligolang.org/versions">
next
</Link>
</Group>
<Group>
<Link href="https://ligolang.org/docs/intro/installation">Docs</Link>
<Link href="https://ligolang.org/docs/tutorials/get-started/tezos-taco-shop-smart-contract">
Tutorials
</Link>
<Link href="https://ligolang.org/blog">Blog</Link>
<Link href="https://ligolang.org/docs/contributors/origin">
Contribute
</Link>
</Group>
</Container>
);
};

View File

@ -0,0 +1,47 @@
import styled from 'styled-components';
export const Group = styled.div`
display: flex;
flex-direction: column;
`;
export const HGroup = styled.div`
display: flex;
align-items: center;
`;
export const Label = styled.label`
font-size: 1em;
color: rgba(153, 153, 153, 1);
`;
export const Input = styled.input`
margin: 0.3em 0 0.7em 0;
background-color: #eff7ff;
border-style: none;
border-bottom: 5px solid #e1f1ff;
padding: 0.5em;
font-size: 1em;
font-family: Menlo, Monaco, 'Courier New', monospace;
outline: none;
&:focus {
background-color: #e1f1ff;
}
`;
export const Textarea = styled.textarea`
resize: vertical;
margin: 0.3em 0 0.7em 0;
background-color: #eff7ff;
border-style: none;
border-bottom: 5px solid #e1f1ff;
padding: 0.5em;
font-size: 1em;
font-family: Menlo, Monaco, 'Courier New', monospace;
outline: none;
&:focus {
background-color: #e1f1ff;
}
`;

View File

@ -0,0 +1,86 @@
import * as monaco from 'monaco-editor';
import React, { useEffect, useRef } from 'react';
import { useDispatch, useStore } from 'react-redux';
import styled from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeCodeAction } from '../redux/editor';
const Container = styled.div`
height: var(--content_height);
/* This font size is used to calcuate code font size */
font-size: 0.8em;
`;
export const MonacoComponent = () => {
let containerRef = useRef(null);
const store = useStore();
const dispatch = useDispatch();
useEffect(() => {
const cleanupFunc: Array<() => void> = [];
const { editor: editorState } = store.getState();
const model = monaco.editor.createModel(
editorState.code,
editorState.language
);
monaco.editor.defineTheme('ligoTheme', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#eff7ff',
'editor.lineHighlightBackground': '#cee3ff',
'editorLineNumber.foreground': '#888'
}
});
monaco.editor.setTheme('ligoTheme');
const htmlElement = (containerRef.current as unknown) as HTMLElement;
const fontSize = window
.getComputedStyle(htmlElement, null)
.getPropertyValue('font-size');
const editor = monaco.editor.create(htmlElement, {
fontSize: parseFloat(fontSize),
model: model,
automaticLayout: true,
minimap: {
enabled: false
}
});
const { dispose } = editor.onDidChangeModelContent(() => {
dispatch({ ...new ChangeCodeAction(editor.getValue()) });
});
cleanupFunc.push(dispose);
cleanupFunc.push(
store.subscribe(() => {
const { editor: editorState }: AppState = store.getState();
if (editorState.code !== editor.getValue()) {
editor.setValue(editorState.code);
}
if (editorState.language !== model.getModeId()) {
if (editorState.language === 'reasonligo') {
monaco.editor.setModelLanguage(model, 'javascript');
} else {
monaco.editor.setModelLanguage(model, editorState.language);
}
}
})
);
return function cleanUp() {
cleanupFunc.forEach(f => f());
};
}, [store, dispatch]);
return <Container id="editor" ref={containerRef}></Container>;
};

View File

@ -0,0 +1,149 @@
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { PushSpinner } from 'react-spinners-kit';
import styled, { css } from 'styled-components';
import { AppState } from '../redux/app';
import { CommandState } from '../redux/command';
import { DoneLoadingAction, LoadingState } from '../redux/loading';
import { ResultState } from '../redux/result';
const Container = styled.div<{ visible?: boolean }>`
position: absolute;
box-sizing: border-box;
width: 100%;
height: 100%;
font-family: Menlo, Monaco, 'Courier New', monospace;
overflow: scroll;
display: flex;
transform: translateX(100%);
transition: transform 0.2s ease-in;
${props =>
props.visible &&
css`
transform: translateX(0px);
`}
`;
const CancelButton = styled.div`
display: flex;
justify-content: center;
align-items: center;
color: white;
background-color: #fc683a;
cursor: pointer;
user-select: none;
margin: 1em;
padding: 0.5em 1em;
`;
const Output = styled.div`
flex: 1;
padding: 0.8em;
display: flex;
/* This font size is used to calcuate spinner size */
font-size: 1em;
`;
const LoadingContainer = styled.div`
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const LoadingMessage = styled.div`
padding: 1em 0;
`;
const Pre = styled.pre`
margin: 0;
`;
export const OutputTabComponent = (props: {
selected?: boolean;
onCancel?: () => void;
}) => {
const output = useSelector<AppState, ResultState['output']>(
state => state.result.output
);
const contract = useSelector<AppState, ResultState['contract']>(
state => state.result.contract
);
const loading = useSelector<AppState, LoadingState>(state => state.loading);
const dispatchedAction = useSelector<
AppState,
CommandState['dispatchedAction']
>(state => state.command.dispatchedAction);
const dispatch = useDispatch();
const outputRef = useRef(null);
const [spinnerSize, setSpinnerSize] = useState(50);
useEffect(() => {
const htmlElement = (outputRef.current as unknown) as HTMLElement;
const fontSize = window
.getComputedStyle(htmlElement, null)
.getPropertyValue('font-size');
setSpinnerSize(parseFloat(fontSize) * 3);
}, [setSpinnerSize]);
return (
<Container visible={props.selected}>
<Output id="output" ref={outputRef}>
{loading.loading && (
<LoadingContainer>
<PushSpinner size={spinnerSize} color="#fedace" />
<LoadingMessage>{loading.message}</LoadingMessage>
<CancelButton
onClick={() => {
if (dispatchedAction) {
dispatchedAction.cancel();
}
dispatch({ ...new DoneLoadingAction() });
if (props.onCancel) {
props.onCancel();
}
}}
>
Cancel
</CancelButton>
</LoadingContainer>
)}
{!loading.loading &&
((output.length !== 0 && <Pre>{output}</Pre>) ||
(contract.length !== 0 && (
<span>
The contract was successfully deployed to the babylonnet test
network.
<br />
<br />
The address of your new contract is: <i>{contract}</i>
<br />
<br />
View your new contract using{' '}
<a
target="_blank"
rel="noopener noreferrer"
href={`https://better-call.dev/babylon/${contract}`}
>
Better Call Dev
</a>
!
</span>
)))}
</Output>
</Container>
);
};

View File

@ -0,0 +1,200 @@
import { faCopy } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import styled, { css } from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeShareLinkAction, ShareState } from '../redux/share';
import { share } from '../services/api';
const Container = styled.div`
display: flex;
justify-content: flex-end;
align-items: center;
`;
const Button = styled.div<{ clicked?: boolean }>`
cursor: pointer;
user-select: none;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
height: 2em;
width: 6em;
color: var(--blue);
background-color: white;
border-radius: 1em;
transition: width 0.3s ease-in;
${props =>
props.clicked &&
css`
width: 2em;
background-color: white;
`}
&:hover {
background-color: var(--blue_opaque1);
}
`;
const Label = styled.span<{ visible?: boolean }>`
pointer-events: none;
opacity: 1;
transition: opacity 0.3s ease-in;
${props =>
!props.visible &&
css`
opacity: 0;
`}
`;
const CopyIcon = ({ visible, ...props }: { visible: boolean }) => (
<FontAwesomeIcon {...props} icon={faCopy}></FontAwesomeIcon>
);
const Copy = styled(CopyIcon)`
position: absolute;
pointer-events: none;
opacity: 1;
transition: opacity 0.3s ease-in;
${props =>
!props.visible &&
css`
opacity: 0;
`}
`;
const Input = styled.input<{ visible?: boolean }>`
position: absolute;
background-color: var(--blue);
border-radius: 1em;
opacity: 0;
height: 2em;
width: 2em;
transform: translateX(-0.3em);
border: none;
padding: 0 1em;
font-size: 1em;
color: white;
transition: width 0.3s ease-in;
outline: none;
${props =>
props.visible &&
css`
opacity: 1;
width: 25em;
`}
`;
const Tooltip = styled.div<{ visible?: boolean }>`
position: absolute;
pointer-events: none;
z-index: 3;
transform: translateY(2.5em);
font-size: var(--font_sub_size);
color: var(--tooltip_foreground);
background-color: var(--tooltip_background);
border-radius: 6px;
padding: 5px 10px;
opacity: 0;
transition: opacity 0.2s ease 0.2s;
${props =>
props.visible &&
css`
opacity: 1;
`}
`;
const shareAction = () => {
return async function(dispatch: Dispatch, getState: () => AppState) {
try {
const { hash } = await share(getState());
dispatch({ ...new ChangeShareLinkAction(hash) });
} catch (ex) {}
};
};
function copy(element: HTMLInputElement): boolean {
element.select();
element.setSelectionRange(0, 99999);
return document.execCommand('copy');
}
export const ShareComponent = () => {
const inputEl = useRef<HTMLInputElement>(null);
const dispatch = useDispatch();
const shareLink = useSelector<AppState, ShareState['link']>(
state => state.share.link
);
const [clicked, setClicked] = useState(false);
const [isTooltipShowing, setShowTooltip] = useState(false);
const SHARE_TOOLTIP = 'Share code';
const COPY_TOOLTIP = 'Copy link';
const COPIED_TOOLTIP = 'Copied!';
const [tooltipMessage, setTooltipMessage] = useState(SHARE_TOOLTIP);
useEffect(() => {
if (shareLink) {
if (inputEl.current && copy(inputEl.current)) {
setTooltipMessage(COPIED_TOOLTIP);
setShowTooltip(true);
} else {
setClicked(true);
setTooltipMessage(COPY_TOOLTIP);
}
} else {
setClicked(false);
setShowTooltip(false);
setTooltipMessage(SHARE_TOOLTIP);
}
}, [shareLink]);
return (
<Container>
<Input
id="share-link"
visible={!!shareLink}
readOnly
ref={inputEl}
value={shareLink ? `${window.location.origin}/p/${shareLink}` : ''}
></Input>
<Button
id="share"
clicked={clicked}
onMouseOver={() => {
if (tooltipMessage === COPIED_TOOLTIP) {
setTooltipMessage(COPY_TOOLTIP);
}
setShowTooltip(true);
}}
onMouseOut={() => setShowTooltip(false)}
onClick={() => {
if (!shareLink) {
dispatch(shareAction());
setClicked(true);
setTooltipMessage(COPY_TOOLTIP);
} else if (inputEl.current) {
copy(inputEl.current);
setTooltipMessage(COPIED_TOOLTIP);
}
}}
>
<Label visible={!clicked}>Share</Label>
<Copy visible={clicked}></Copy>
<Tooltip visible={isTooltipShowing}>{tooltipMessage}</Tooltip>
</Button>
</Container>
);
};

View File

@ -0,0 +1,148 @@
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState } from 'react';
import OutsideClickHandler from 'react-outside-click-handler';
import { useDispatch, useSelector } from 'react-redux';
import styled, { css } from 'styled-components';
import { AppState } from '../redux/app';
import { ChangeLanguageAction, EditorState } from '../redux/editor';
import { Language } from '../redux/types';
const Container = styled.div`
display: flex;
position: relative;
z-index: 2;
min-width: 10em;
`;
const Header = styled.div`
cursor: pointer;
user-select: none;
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
height: 2em;
padding: 0 0.5em;
border: 1px solid var(--blue_trans1);
`;
const ArrowIcon = ({ rotate, ...props }: { rotate: boolean }) => (
<FontAwesomeIcon {...props} icon={faCaretDown} size="lg"></FontAwesomeIcon>
);
const Arrow = styled(ArrowIcon)`
margin-left: 0.5em;
pointer-events: none;
color: var(--blue_trans1);
transition: transform 0.15s ease-in;
${(props: { rotate: boolean }) =>
props.rotate &&
css`
transform: rotate(180deg);
`};
`;
const List = styled.ul`
position: absolute;
list-style-type: none;
background-color: white;
width: 100%;
margin: 0;
padding: 0;
box-shadow: 1px 3px 10px 0px rgba(153, 153, 153, 0.4);
border-radius: 3px;
visibility: hidden;
opacity: 0;
transition: opacity 0.15s ease-in;
${(props: { visible: boolean }) =>
props.visible &&
css`
visibility: visible;
opacity: 1;
`}
`;
const Option = styled.li`
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
justify-content: space-between;
height: 2em;
padding: 0 0.5em;
&:first-child {
border-radius: 3px 3px 0 0;
}
&:last-child {
border-radius: 0 0 3px 3px;
}
&:hover {
background-color: var(--blue_trans2);
font-weight: 600;
}
`;
export const SyntaxSelectComponent = () => {
const OPTIONS = {
[Language.PascaLigo]: 'PascaLIGO',
[Language.CameLigo]: 'CameLIGO',
[Language.ReasonLIGO]: 'ReasonLIGO'
};
const moveOptionToTop = (option: Language) => {
return Object.keys(OPTIONS).reduce((list, entry) => {
if (entry === option) {
list.unshift(entry);
} else {
list.push(entry as Language);
}
return list;
}, [] as Language[]);
};
const language = useSelector<AppState, EditorState['language']>(
state => state.editor.language
);
const dispatch = useDispatch();
const [opened, open] = useState(false);
const selectOption = (option: Language) => {
if (language !== option) {
dispatch({ ...new ChangeLanguageAction(option) });
}
open(false);
};
return (
<Container>
<OutsideClickHandler onOutsideClick={() => open(false)}>
<List visible={opened}>
{moveOptionToTop(language).map(option => (
<Option
id={option}
key={option}
onClick={() => selectOption(option)}
>
<span>{OPTIONS[option]}</span>
</Option>
))}
</List>
</OutsideClickHandler>
<Header id="syntax-select" onClick={() => open(true)}>
<span>{OPTIONS[language]}</span>
<Arrow rotate={opened}></Arrow>
</Header>
</Container>
);
};

View File

@ -0,0 +1,94 @@
import React, { useState } from 'react';
import styled, { css } from 'styled-components';
import { ConfigureTabComponent } from './configure-tab';
import { OutputTabComponent } from './output-tab';
const Container = styled.div`
flex: 1;
display: flex;
flex-direction: column;
`;
const Header = styled.div`
display: flex;
border-bottom: 5px solid var(--blue_trans1);
min-height: 2.5em;
`;
const Label = styled.span`
cursor: pointer;
user-select: none;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
&:hover {
color: var(--orange);
}
`;
const Tab = styled.div<{ selected?: boolean }>`
flex: 1;
display: flex;
flex-direction: column;
`;
const Underline = styled.div<{ selectedTab: number }>`
position: relative;
top: -5px;
background-color: var(--orange);
height: 5px;
margin-bottom: -5px;
width: calc(100% / 2);
transition: transform 0.2s ease-in;
${props =>
css`
transform: translateX(calc(${props.selectedTab} * 100%));
`}
`;
const Content = styled.div`
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
`;
export const TabsPanelComponent = () => {
const TABS = [
{ index: 0, label: 'Configure', id: 'configure-tab' },
{ index: 1, label: 'Output', id: 'output-tab' }
];
const [selectedTab, selectTab] = useState(TABS[0]);
return (
<Container>
<Header>
{TABS.map(tab => (
<Tab id={tab.id} selected={selectedTab.index === tab.index}>
<Label onClick={() => selectTab(tab)}>{tab.label}</Label>
</Tab>
))}
</Header>
<Underline selectedTab={selectedTab.index}></Underline>
<Content>
<ConfigureTabComponent
selected={selectedTab.index === 0}
onRun={() => {
selectTab(TABS[1]);
}}
></ConfigureTabComponent>
<OutputTabComponent
selected={selectedTab.index === 1}
onCancel={() => {
selectTab(TABS[0]);
}}
></OutputTabComponent>
</Content>
</Container>
);
};

View File

@ -0,0 +1,83 @@
import { faCheck } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState } from 'react';
import styled, { css } from 'styled-components';
const Container = styled.div<{ checked: boolean }>`
position: relative;
height: 2em;
width: 3.5em;
border-radius: 1em;
background-color: var(--blue_trans1);
border: 1px solid var(--blue);
transition: background-color 0.2s ease-in;
${props =>
props.checked &&
css`
background-color: var(--blue);
`};
`;
const Button = styled.div<{ checked: boolean }>`
display: flex;
justify-content: center;
align-items: center;
position: absolute;
height: 2em;
width: 2em;
background-color: white;
border-radius: 50%;
cursor: pointer;
right: calc(1.5em);
transition: right 0.2s ease-in;
${props =>
props.checked &&
css`
right: 0;
`};
`;
const CheckIcon = ({ visible, ...props }: { visible: boolean }) => (
<FontAwesomeIcon {...props} icon={faCheck}></FontAwesomeIcon>
);
const Check = styled(CheckIcon)`
position: absolute;
pointer-events: none;
opacity: 1;
transition: opacity 0.2s ease-in;
color: var(--blue);
${props =>
!props.visible &&
css`
opacity: 0;
`}
`;
export const ToggleComponent = (props: {
checked: boolean;
onChanged: (value: boolean) => void;
className?: string;
}) => {
const [isChecked, setChecked] = useState(props.checked);
return (
<Container className={props.className} checked={isChecked}>
<Button
checked={isChecked}
onClick={() => {
const newState = !isChecked;
setChecked(newState);
props.onChanged(newState);
}}
>
<Check visible={isChecked}></Check>
</Button>
</Container>
);
};

View File

@ -0,0 +1,31 @@
import { applyMiddleware, createStore, Middleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import rootReducer, { AppState } from './redux/app';
declare var defaultServerState: AppState | undefined;
export default function configureStore() {
const store = createStore(
rootReducer,
{
...(typeof defaultServerState === 'undefined' ? {} : defaultServerState)
},
applyMiddleware(ReduxThunk, cleanRouteOnAction)
);
return store;
}
const cleanRouteOnAction: Middleware = store => next => action => {
const { share } = store.getState();
next(action);
const state = store.getState();
if (
share.link !== undefined &&
state.share.link === undefined &&
window.location.pathname !== '/'
) {
window.history.replaceState({}, document.title, '/');
}
};

View File

@ -0,0 +1,79 @@
:root {
/* Note: the LIGO header should be ripped from the main ligolang.org homepage. Specs not included here :-) */
/* width of all colored bands: 4px */
--orange: #fc683a;
--orange_trans: #fedace;
--blue: rgba(14, 116, 255, 1);
--button_float: rgba(14, 116, 255, 0.85);
--blue_trans1: rgba(14, 116, 255, 0.15); /* #e1f1ff; */
--blue_opaque1: #dbeaff;
--blue_trans2: rgba(14, 116, 255, 0.08); /* #eff7ff; */
--grey: #888;
--box-shadow: 1px 3px 10px 0px rgba(153, 153, 153, 0.4); /* or #999999 */
--border_radius: 3px;
/* text, where 1rem = 16px */
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
--font_weight: 400;
--font_menu_hover: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
--font_menu_size: 1rem;
--font_menu_color: rgba(0, 0, 0, 1);
--font_sub_size: 0.8em;
--font_sub_color: rgba(51, 51, 51, 1); /* or #333333; */
--font_label: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
--font_label_size: 0.8rem;
--font_label_color: rgba(153, 153, 153, 1); /* or #999999 */
--font_code: Consolas, source-code-pro, Menlo, Monaco, 'Courier New',
monospace;
--font_code_size: 0.8rem;
--font_code_color: rgba(51, 51, 51, 1); /* or #333333; */
/* filler text for empty panel */
--font_ghost: 2rem;
--font_ghost_weight: 700;
--font_ghost_color: rgba(153, 153, 153, 0.5); /* or #CFCFCF */
--content_height: 85vh;
--tooltip_foreground: white;
--tooltip_background: rgba(0, 0, 0, 0.75) /*#404040*/;
}
body {
margin: 0;
font-size: 1.1vw;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.monaco-editor .current-line ~ .line-numbers {
color: var(--orange);
border-left: 4px solid var(--blue);
}
.monaco-editor .margin-view-overlays .current-line,
.monaco-editor .view-overlays .current-line {
background-color: var(--blue_trans1);
color: var(--blue);
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,13 @@
export abstract class CancellableAction {
private cancelled = false;
cancel() {
this.cancelled = true;
}
isCancelled() {
return this.cancelled;
}
abstract getAction(): any;
}

View File

@ -0,0 +1,39 @@
import { Dispatch } from 'redux';
import { compileContract, getErrorMessage } from '../../services/api';
import { AppState } from '../app';
import { DoneLoadingAction, UpdateLoadingAction } from '../loading';
import { ChangeOutputAction } from '../result';
import { CancellableAction } from './cancellable';
export class CompileAction extends CancellableAction {
getAction() {
return async (dispatch: Dispatch, getState: () => AppState) => {
dispatch({ ...new UpdateLoadingAction('Compiling contract...') });
try {
const { editor, compile: compileState } = getState();
const michelsonCode = await compileContract(
editor.language,
editor.code,
compileState.entrypoint
);
if (this.isCancelled()) {
return;
}
dispatch({ ...new ChangeOutputAction(michelsonCode.result) });
} catch (ex) {
if (this.isCancelled()) {
return;
}
dispatch({
...new ChangeOutputAction(`Error: ${getErrorMessage(ex)}`)
});
}
dispatch({ ...new DoneLoadingAction() });
};
}
}

View File

@ -0,0 +1,99 @@
import { Tezos } from '@taquito/taquito';
import { TezBridgeSigner } from '@taquito/tezbridge-signer';
import { Dispatch } from 'redux';
import { compileContract, compileExpression, deploy, getErrorMessage } from '../../services/api';
import { AppState } from '../app';
import { MichelsonFormat } from '../compile';
import { DoneLoadingAction, UpdateLoadingAction } from '../loading';
import { ChangeContractAction, ChangeOutputAction } from '../result';
import { CancellableAction } from './cancellable';
Tezos.setProvider({
rpc: 'https://api.tez.ie/rpc/babylonnet',
signer: new TezBridgeSigner()
});
export class DeployAction extends CancellableAction {
async deployWithTezBridge(dispatch: Dispatch, getState: () => AppState) {
dispatch({ ...new UpdateLoadingAction('Compiling contract...') });
const { editor: editorState, deploy: deployState } = getState();
const michelsonCode = await compileContract(
editorState.language,
editorState.code,
deployState.entrypoint,
MichelsonFormat.Json
);
if (this.isCancelled()) {
return;
}
dispatch({ ...new UpdateLoadingAction('Compiling storage...') });
const michelsonStorage = await compileExpression(
editorState.language,
deployState.storage,
MichelsonFormat.Json
);
if (this.isCancelled()) {
return;
}
dispatch({ ...new UpdateLoadingAction('Waiting for TezBridge signer...') });
const op = await Tezos.contract.originate({
code: JSON.parse(michelsonCode.result),
init: JSON.parse(michelsonStorage.result)
});
if (this.isCancelled()) {
return;
}
dispatch({ ...new UpdateLoadingAction('Deploying to babylon network...') });
return await op.contract();
}
async deployOnServerSide(dispatch: Dispatch, getState: () => AppState) {
dispatch({ ...new UpdateLoadingAction('Deploying to babylon network...') });
const { editor: editorState, deploy: deployState } = getState();
return await deploy(
editorState.language,
editorState.code,
deployState.entrypoint,
deployState.storage
);
}
getAction() {
return async (dispatch: Dispatch, getState: () => AppState) => {
const { deploy } = getState();
try {
const contract = deploy.useTezBridge
? await this.deployWithTezBridge(dispatch, getState)
: await this.deployOnServerSide(dispatch, getState);
if (!contract || this.isCancelled()) {
return;
}
dispatch({ ...new ChangeContractAction(contract.address) });
} catch (ex) {
if (this.isCancelled()) {
return;
}
dispatch({
...new ChangeOutputAction(`Error: ${getErrorMessage(ex)}`)
});
}
dispatch({ ...new DoneLoadingAction() });
};
}
}

View File

@ -0,0 +1,41 @@
import { Dispatch } from 'redux';
import { dryRun, getErrorMessage } from '../../services/api';
import { AppState } from '../app';
import { DoneLoadingAction, UpdateLoadingAction } from '../loading';
import { ChangeOutputAction } from '../result';
import { CancellableAction } from './cancellable';
export class DryRunAction extends CancellableAction {
getAction() {
return async (dispatch: Dispatch, getState: () => AppState) => {
dispatch({
...new UpdateLoadingAction('Waiting for dry run results...')
});
try {
const { editor, dryRun: dryRunState } = getState();
const result = await dryRun(
editor.language,
editor.code,
dryRunState.entrypoint,
dryRunState.parameters,
dryRunState.storage
);
if (this.isCancelled()) {
return;
}
dispatch({ ...new ChangeOutputAction(result.output) });
} catch (ex) {
if (this.isCancelled()) {
return;
}
dispatch({
...new ChangeOutputAction(`Error: ${getErrorMessage(ex)}`)
});
}
dispatch({ ...new DoneLoadingAction() });
};
}
}

View File

@ -0,0 +1,43 @@
import { Dispatch } from 'redux';
import { getErrorMessage, runFunction } from '../../services/api';
import { AppState } from '../app';
import { DoneLoadingAction, UpdateLoadingAction } from '../loading';
import { ChangeOutputAction } from '../result';
import { CancellableAction } from './cancellable';
export class EvaluateFunctionAction extends CancellableAction {
getAction() {
return async (dispatch: Dispatch, getState: () => AppState) => {
const { editor, evaluateFunction: evaluateFunctionState } = getState();
dispatch({
...new UpdateLoadingAction(
`Evaluating ${evaluateFunctionState.entrypoint} ${evaluateFunctionState.parameters}...`
)
});
try {
const result = await runFunction(
editor.language,
editor.code,
evaluateFunctionState.entrypoint,
evaluateFunctionState.parameters
);
if (this.isCancelled()) {
return;
}
dispatch({ ...new ChangeOutputAction(result.output) });
} catch (ex) {
if (this.isCancelled()) {
return;
}
dispatch({
...new ChangeOutputAction(`Error: ${getErrorMessage(ex)}`)
});
}
dispatch({ ...new DoneLoadingAction() });
};
}
}

View File

@ -0,0 +1,44 @@
import { Dispatch } from 'redux';
import { evaluateValue, getErrorMessage } from '../../services/api';
import { AppState } from '../app';
import { DoneLoadingAction, UpdateLoadingAction } from '../loading';
import { ChangeOutputAction } from '../result';
import { CancellableAction } from './cancellable';
export class EvaluateValueAction extends CancellableAction {
getAction() {
return async (dispatch: Dispatch, getState: () => AppState) => {
const { editor, evaluateValue: evaluateValueState } = getState();
dispatch({
...new UpdateLoadingAction(
`Evaluating "${evaluateValueState.entrypoint}" entrypoint...`
)
});
try {
const result = await evaluateValue(
editor.language,
editor.code,
evaluateValueState.entrypoint
);
if (this.isCancelled()) {
return;
}
dispatch({ ...new ChangeOutputAction(result.code) });
} catch (ex) {
if (this.isCancelled()) {
return;
}
dispatch({
...new ChangeOutputAction(`Error: ${getErrorMessage(ex)}`)
});
}
dispatch({ ...new DoneLoadingAction() });
};
}
}

View File

@ -0,0 +1,41 @@
import { combineReducers } from 'redux';
import command, { CommandState } from './command';
import compile, { CompileState } from './compile';
import deploy, { DeployState } from './deploy';
import dryRun, { DryRunState } from './dry-run';
import editor, { EditorState } from './editor';
import evaluateFunction, { EvaluateFunctionState } from './evaluate-function';
import evaluateValue, { EvaluateValueState } from './evaluate-value';
import examples, { ExamplesState } from './examples';
import loading, { LoadingState } from './loading';
import result, { ResultState } from './result';
import share, { ShareState } from './share';
export interface AppState {
editor: EditorState;
share: ShareState;
compile: CompileState;
dryRun: DryRunState;
deploy: DeployState;
evaluateFunction: EvaluateFunctionState;
evaluateValue: EvaluateValueState;
result: ResultState;
command: CommandState;
examples: ExamplesState;
loading: LoadingState;
}
export default combineReducers({
editor,
share,
compile,
dryRun,
deploy,
evaluateFunction,
evaluateValue,
result,
command,
examples,
loading
});

View File

@ -0,0 +1,45 @@
import { CancellableAction } from './actions/cancellable';
import { Command } from './types';
export enum ActionType {
ChangeSelected = 'command-change-selected',
ChangeDispatchedAction = 'command-change-dispatched-action'
}
export interface CommandState {
selected: Command;
dispatchedAction: CancellableAction | null;
}
export class ChangeSelectedAction {
public readonly type = ActionType.ChangeSelected;
constructor(public payload: CommandState['selected']) {}
}
export class ChangeDispatchedAction {
public readonly type = ActionType.ChangeDispatchedAction;
constructor(public payload: CommandState['dispatchedAction']) {}
}
type Action = ChangeSelectedAction | ChangeDispatchedAction;
const DEFAULT_STATE: CommandState = {
selected: Command.Compile,
dispatchedAction: null
};
export default (state = DEFAULT_STATE, action: Action): CommandState => {
switch (action.type) {
case ActionType.ChangeSelected:
return {
...state,
selected: action.payload
};
case ActionType.ChangeDispatchedAction:
return {
...state,
dispatchedAction: action.payload
};
}
return state;
};

View File

@ -0,0 +1,41 @@
import { ActionType as ExamplesActionType, ChangeSelectedAction as ChangeSelectedExampleAction } from './examples';
export enum MichelsonFormat {
Text = 'text',
Json = 'json'
}
export enum ActionType {
ChangeEntrypoint = 'compile-change-entrypoint'
}
export interface CompileState {
entrypoint: string;
}
export class ChangeEntrypointAction {
public readonly type = ActionType.ChangeEntrypoint;
constructor(public payload: CompileState['entrypoint']) {}
}
type Action = ChangeEntrypointAction | ChangeSelectedExampleAction;
const DEFAULT_STATE: CompileState = {
entrypoint: ''
};
export default (state = DEFAULT_STATE, action: Action): CompileState => {
switch (action.type) {
case ExamplesActionType.ChangeSelected:
return {
...state,
...(!action.payload ? DEFAULT_STATE : action.payload.compile)
};
case ActionType.ChangeEntrypoint:
return {
...state,
entrypoint: action.payload
};
}
return state;
};

View File

@ -0,0 +1,66 @@
import { ActionType as ExamplesActionType, ChangeSelectedAction as ChangeSelectedExampleAction } from './examples';
export enum ActionType {
ChangeEntrypoint = 'deploy-change-entrypoint',
ChangeStorage = 'deploy-change-storage',
UseTezBridge = 'deploy-use-tezbridge'
}
export interface DeployState {
entrypoint: string;
storage: string;
useTezBridge: boolean;
}
export class ChangeEntrypointAction {
public readonly type = ActionType.ChangeEntrypoint;
constructor(public payload: DeployState['entrypoint']) {}
}
export class ChangeStorageAction {
public readonly type = ActionType.ChangeStorage;
constructor(public payload: DeployState['storage']) {}
}
export class UseTezBridgeAction {
public readonly type = ActionType.UseTezBridge;
constructor(public payload: DeployState['useTezBridge']) {}
}
type Action =
| ChangeEntrypointAction
| ChangeStorageAction
| UseTezBridgeAction
| ChangeSelectedExampleAction;
const DEFAULT_STATE: DeployState = {
entrypoint: '',
storage: '',
useTezBridge: false
};
export default (state = DEFAULT_STATE, action: Action): DeployState => {
switch (action.type) {
case ExamplesActionType.ChangeSelected:
return {
...state,
...(!action.payload ? DEFAULT_STATE : action.payload.deploy)
};
case ActionType.ChangeEntrypoint:
return {
...state,
entrypoint: action.payload
};
case ActionType.ChangeStorage:
return {
...state,
storage: action.payload
};
case ActionType.UseTezBridge:
return {
...state,
useTezBridge: action.payload
};
}
return state;
};

View File

@ -0,0 +1,66 @@
import { ActionType as ExamplesActionType, ChangeSelectedAction as ChangeSelectedExampleAction } from './examples';
export enum ActionType {
ChangeEntrypoint = 'dry-run-change-entrypoint',
ChangeParameters = 'dry-run-change-parameters',
ChangeStorage = 'dry-run-change-storage'
}
export interface DryRunState {
entrypoint: string;
parameters: string;
storage: string;
}
export class ChangeEntrypointAction {
public readonly type = ActionType.ChangeEntrypoint;
constructor(public payload: DryRunState['entrypoint']) {}
}
export class ChangeParametersAction {
public readonly type = ActionType.ChangeParameters;
constructor(public payload: DryRunState['parameters']) {}
}
export class ChangeStorageAction {
public readonly type = ActionType.ChangeStorage;
constructor(public payload: DryRunState['storage']) {}
}
type Action =
| ChangeEntrypointAction
| ChangeParametersAction
| ChangeStorageAction
| ChangeSelectedExampleAction;
const DEFAULT_STATE: DryRunState = {
entrypoint: '',
parameters: '',
storage: ''
};
export default (state = DEFAULT_STATE, action: Action): DryRunState => {
switch (action.type) {
case ExamplesActionType.ChangeSelected:
return {
...state,
...(!action.payload ? DEFAULT_STATE : action.payload.dryRun)
};
case ActionType.ChangeEntrypoint:
return {
...state,
entrypoint: action.payload
};
case ActionType.ChangeParameters:
return {
...state,
parameters: action.payload
};
case ActionType.ChangeStorage:
return {
...state,
storage: action.payload
};
}
return state;
};

View File

@ -0,0 +1,53 @@
import { ActionType as ExamplesActionType, ChangeSelectedAction as ChangeSelectedExampleAction } from './examples';
import { Language } from './types';
export enum ActionType {
ChangeLanguage = 'editor-change-language',
ChangeCode = 'editor-change-code'
}
export interface EditorState {
language: Language;
code: string;
}
export class ChangeLanguageAction {
public readonly type = ActionType.ChangeLanguage;
constructor(public payload: EditorState['language']) {}
}
export class ChangeCodeAction {
public readonly type = ActionType.ChangeCode;
constructor(public payload: EditorState['code']) {}
}
type Action =
| ChangeCodeAction
| ChangeLanguageAction
| ChangeSelectedExampleAction;
const DEFAULT_STATE: EditorState = {
language: Language.CameLigo,
code: ''
};
export default (state = DEFAULT_STATE, action: Action): EditorState => {
switch (action.type) {
case ExamplesActionType.ChangeSelected:
return {
...state,
...(!action.payload ? DEFAULT_STATE : action.payload.editor)
};
case ActionType.ChangeLanguage:
return {
...state,
language: action.payload
};
case ActionType.ChangeCode:
return {
...state,
code: action.payload
};
}
return state;
};

View File

@ -0,0 +1,55 @@
import { ActionType as ExamplesActionType, ChangeSelectedAction as ChangeSelectedExampleAction } from './examples';
export enum ActionType {
ChangeEntrypoint = 'evaluate-function-change-entrypoint',
ChangeParameters = 'evaluate-function-change-parameters'
}
export interface EvaluateFunctionState {
entrypoint: string;
parameters: string;
}
export class ChangeEntrypointAction {
public readonly type = ActionType.ChangeEntrypoint;
constructor(public payload: EvaluateFunctionState['entrypoint']) {}
}
export class ChangeParametersAction {
public readonly type = ActionType.ChangeParameters;
constructor(public payload: EvaluateFunctionState['parameters']) {}
}
type Action =
| ChangeEntrypointAction
| ChangeParametersAction
| ChangeSelectedExampleAction;
const DEFAULT_STATE: EvaluateFunctionState = {
entrypoint: '',
parameters: ''
};
export default (
state = DEFAULT_STATE,
action: Action
): EvaluateFunctionState => {
switch (action.type) {
case ExamplesActionType.ChangeSelected:
return {
...state,
...(!action.payload ? DEFAULT_STATE : action.payload.evaluateFunction)
};
case ActionType.ChangeEntrypoint:
return {
...state,
entrypoint: action.payload
};
case ActionType.ChangeParameters:
return {
...state,
parameters: action.payload
};
}
return state;
};

View File

@ -0,0 +1,36 @@
import { ActionType as ExamplesActionType, ChangeSelectedAction as ChangeSelectedExampleAction } from './examples';
export enum ActionType {
ChangeEntrypoint = 'evaluate-value-change-entrypoint'
}
export interface EvaluateValueState {
entrypoint: string;
}
export class ChangeEntrypointAction {
public readonly type = ActionType.ChangeEntrypoint;
constructor(public payload: EvaluateValueState['entrypoint']) {}
}
type Action = ChangeEntrypointAction | ChangeSelectedExampleAction;
const DEFAULT_STATE: EvaluateValueState = {
entrypoint: ''
};
export default (state = DEFAULT_STATE, action: Action): EvaluateValueState => {
switch (action.type) {
case ExamplesActionType.ChangeSelected:
return {
...state,
...(!action.payload ? DEFAULT_STATE : action.payload.evaluateValue)
};
case ActionType.ChangeEntrypoint:
return {
...state,
entrypoint: action.payload
};
}
return state;
};

View File

@ -0,0 +1,17 @@
import { CompileState } from './compile';
import { DeployState } from './deploy';
import { DryRunState } from './dry-run';
import { EditorState } from './editor';
import { EvaluateFunctionState } from './evaluate-function';
import { EvaluateValueState } from './evaluate-value';
export interface ExampleState {
id: string;
name: string;
editor: EditorState;
compile: CompileState;
dryRun: DryRunState;
deploy: DeployState;
evaluateFunction: EvaluateFunctionState;
evaluateValue: EvaluateValueState;
}

View File

@ -0,0 +1,38 @@
import { ExampleState } from './example';
export enum ActionType {
ChangeSelected = 'examples-change-selected'
}
export interface ExampleItem {
id: string;
name: string;
}
export interface ExamplesState {
selected: ExampleState | null;
list: ExampleItem[];
}
export class ChangeSelectedAction {
public readonly type = ActionType.ChangeSelected;
constructor(public payload: ExamplesState['selected']) {}
}
type Action = ChangeSelectedAction;
export const DEFAULT_STATE: ExamplesState = {
selected: null,
list: []
};
export default (state = DEFAULT_STATE, action: Action): ExamplesState => {
switch (action.type) {
case ActionType.ChangeSelected:
return {
...state,
selected: action.payload
};
}
return state;
};

View File

@ -0,0 +1,42 @@
export enum ActionType {
UpdateLoading = 'loading-update-loading',
DoneLoading = 'loading-done-loading'
}
export interface LoadingState {
loading: boolean;
message: string;
}
export class UpdateLoadingAction {
public readonly type = ActionType.UpdateLoading;
constructor(public payload: LoadingState['message']) {}
}
export class DoneLoadingAction {
public readonly type = ActionType.DoneLoading;
}
type Action = UpdateLoadingAction | DoneLoadingAction;
export const DEFAULT_STATE: LoadingState = {
loading: false,
message: ''
};
export default (state = DEFAULT_STATE, action: Action): LoadingState => {
switch (action.type) {
case ActionType.UpdateLoading:
return {
...state,
loading: true,
message: action.payload
};
case ActionType.DoneLoading:
return {
...state,
...DEFAULT_STATE
};
}
return state;
};

View File

@ -0,0 +1,43 @@
export enum ActionType {
ChangeOutput = 'result-change-output',
ChangeContract = 'result-change-contract'
}
export interface ResultState {
output: string;
contract: string;
}
export class ChangeOutputAction {
public readonly type = ActionType.ChangeOutput;
constructor(public payload: ResultState['output']) {}
}
export class ChangeContractAction {
public readonly type = ActionType.ChangeContract;
constructor(public payload: ResultState['contract']) {}
}
type Action = ChangeOutputAction | ChangeContractAction;
const DEFAULT_STATE: ResultState = {
output: '',
contract: ''
};
export default (state = DEFAULT_STATE, action: Action): ResultState => {
switch (action.type) {
case ActionType.ChangeOutput:
return {
...state,
output: action.payload
};
case ActionType.ChangeContract:
return {
...state,
output: DEFAULT_STATE.output,
contract: action.payload
};
}
return state;
};

View File

@ -0,0 +1,82 @@
import { ActionType as CompileActionType, ChangeEntrypointAction as ChangeCompileEntrypointAction } from './compile';
import {
ActionType as DeployActionType,
ChangeEntrypointAction as ChangeDeployEntrypointAction,
ChangeStorageAction as ChangeDeployStorageAction,
UseTezBridgeAction,
} from './deploy';
import {
ActionType as DryRunActionType,
ChangeEntrypointAction as ChangeDryRunEntrypointAction,
ChangeParametersAction as ChangeDryRunParametersAction,
ChangeStorageAction as ChangeDryRunStorageAction,
} from './dry-run';
import { ActionType as EditorActionType, ChangeCodeAction, ChangeLanguageAction } from './editor';
import {
ActionType as EvaluateFunctionActionType,
ChangeEntrypointAction as ChangeEvaluateFunctionEntrypointAction,
ChangeParametersAction as ChangeEvaluateFunctionParametersAction,
} from './evaluate-function';
import {
ActionType as EvaluateValueActionType,
ChangeEntrypointAction as ChangeEvaluateValueEntrypointAction,
} from './evaluate-value';
export enum ActionType {
ChangeShareLink = 'share-change-link'
}
export interface ShareState {
link: string;
}
export class ChangeShareLinkAction {
public readonly type = ActionType.ChangeShareLink;
constructor(public payload: ShareState['link']) {}
}
type Action =
| ChangeShareLinkAction
| ChangeCodeAction
| ChangeLanguageAction
| ChangeCompileEntrypointAction
| ChangeDeployEntrypointAction
| ChangeDeployStorageAction
| UseTezBridgeAction
| ChangeDryRunEntrypointAction
| ChangeDryRunParametersAction
| ChangeDryRunStorageAction
| ChangeEvaluateFunctionEntrypointAction
| ChangeEvaluateFunctionParametersAction
| ChangeEvaluateValueEntrypointAction;
const DEFAULT_STATE: ShareState = {
link: ''
};
export default (state = DEFAULT_STATE, action: Action): ShareState => {
switch (action.type) {
case EditorActionType.ChangeCode:
case EditorActionType.ChangeLanguage:
case CompileActionType.ChangeEntrypoint:
case DeployActionType.ChangeEntrypoint:
case DeployActionType.ChangeStorage:
case DeployActionType.UseTezBridge:
case DryRunActionType.ChangeEntrypoint:
case DryRunActionType.ChangeParameters:
case DryRunActionType.ChangeStorage:
case EvaluateFunctionActionType.ChangeEntrypoint:
case EvaluateFunctionActionType.ChangeParameters:
case EvaluateValueActionType.ChangeEntrypoint:
return {
...state,
...DEFAULT_STATE
};
case ActionType.ChangeShareLink:
return {
...state,
link: action.payload
};
}
return state;
};

View File

@ -0,0 +1,13 @@
export enum Language {
PascaLigo = 'pascaligo',
CameLigo = 'cameligo',
ReasonLIGO = 'reasonligo'
}
export enum Command {
Compile = 'compile',
DryRun = 'dry-run',
EvaluateValue = 'evaluate-value',
EvaluateFunction = 'evaluate-function',
Deploy = 'deploy'
}

View File

@ -0,0 +1,143 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

View File

@ -0,0 +1,132 @@
import axios from 'axios';
import { AppState } from '../redux/app';
import { Language } from '../redux/types';
export async function getExample(id: string) {
const response = await axios.get(`/static/examples/${id}`);
return response.data;
}
export async function compileContract(
syntax: Language,
code: string,
entrypoint: string,
format?: string
) {
const response = await axios.post('/api/compile-contract', {
syntax,
code,
entrypoint,
format
});
return response.data;
}
export async function compileExpression(
syntax: Language,
expression: string,
format?: string
) {
const response = await axios.post('/api/compile-expression', {
syntax,
expression,
format
});
return response.data;
}
export async function dryRun(
syntax: Language,
code: string,
entrypoint: string,
parameters: string,
storage: string
) {
// For whatever reason, storage set by examples is not treated as a string. So we convert it here.
storage = `${storage}`;
const response = await axios.post('/api/dry-run', {
syntax,
code,
entrypoint,
parameters,
storage
});
return response.data;
}
export async function share({
editor,
compile,
dryRun,
deploy,
evaluateValue,
evaluateFunction
}: Partial<AppState>) {
const response = await axios.post('/api/share', {
editor,
compile,
dryRun,
deploy,
evaluateValue,
evaluateFunction
});
return response.data;
}
export async function deploy(
syntax: Language,
code: string,
entrypoint: string,
storage: string
) {
// For whatever reason, storage set by examples is not treated as a string. So we convert it here.
storage = `${storage}`;
const response = await axios.post('/api/deploy', {
syntax,
code,
entrypoint,
storage
});
return response.data;
}
export async function evaluateValue(
syntax: Language,
code: string,
entrypoint: string
) {
const response = await axios.post('/api/evaluate-value', {
syntax,
code,
entrypoint
});
return response.data;
}
export async function runFunction(
syntax: Language,
code: string,
entrypoint: string,
parameters: string
) {
const response = await axios.post('/api/run-function', {
syntax,
code,
entrypoint,
parameters
});
return response.data;
}
export function getErrorMessage(ex: any): string {
if (ex.response && ex.response.data) {
return ex.response.data.error;
} else if (ex instanceof Error) {
return ex.message;
}
return JSON.stringify(ex);
}

View File

@ -0,0 +1,11 @@
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
proxy({
target: 'http://localhost:8080',
changeOrigin: true
})
);
};

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}

View File

@ -0,0 +1,12 @@
FROM alekzonder/puppeteer:latest as puppeteer
WORKDIR /app
COPY package.json package.json
COPY package-lock.json package-lock.json
COPY jest-puppeteer.config.js jest-puppeteer.config.js
COPY test test
RUN npm ci
ENTRYPOINT [ "npm", "run", "test" ]

View File

@ -0,0 +1,18 @@
version: '3'
services:
webide:
image: "${WEBIDE_IMAGE}"
environment:
- DATA_DIR=/tmp
volumes:
- /tmp:/tmp
logging:
driver: none
e2e:
build: .
environment:
- API_HOST=http://webide:8080
volumes:
- /tmp:/tmp
depends_on:
- webide

View File

@ -0,0 +1,13 @@
module.exports = {
launch: {
args: [
'--no-sandbox',
'--disable-setuid-sandbox'
],
defaultViewport: {
width: 1920,
height: 1080
},
headless: true
}
};

4820
tools/webide/packages/e2e/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"name": "e2e",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest --runInBand"
},
"jest": {
"preset": "jest-puppeteer"
},
"author": "",
"license": "ISC",
"dependencies": {
"jest": "^24.9.0",
"jest-puppeteer": "^4.3.0",
"puppeteer": "^1.20.0"
},
"devDependencies": {
"node-fetch": "^2.6.0"
}
}

View File

@ -0,0 +1,112 @@
const fetch = require('node-fetch');
//
// Generic utils
//
exports.sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time));
};
exports.clearText = async keyboard => {
await keyboard.down('Shift');
for (let i = 0; i < 100; i++) {
await keyboard.press('ArrowUp');
}
await keyboard.up('Shift');
await keyboard.press('Backspace');
await keyboard.down('Shift');
for (let i = 0; i < 100; i++) {
await keyboard.press('ArrowDown');
}
await keyboard.up('Shift');
await keyboard.press('Backspace');
};
exports.createResponseCallback = (page, url) => {
return new Promise(resolve => {
page.on('response', function callback(response) {
if (response.url() === url) {
resolve(response);
page.removeListener('response', callback);
}
});
});
};
exports.getInnerText = id => {
let element = document.getElementById(id);
return element && element.textContent;
};
exports.getInputValue = id => {
let element = document.getElementById(id);
return element && element.value;
};
//
// Application specific utils
//
exports.API_HOST = process.env['API_HOST'] || 'http://127.0.0.1:8080';
exports.API_ROOT = `${exports.API_HOST}/api`;
exports.fetchExamples = async () => (await fetch(`${exports.API_HOST}/static/examples/list`)).json();
exports.runCommandAndGetOutputFor = async (command, endpoint) => {
await page.click('#configure-tab');
await exports.sleep(1000);
await page.click('#command-select');
await page.click(`#${command}`);
// Gotta create response callback before clicking run because some responses are too fast
const responseCallback = exports.createResponseCallback(page, `${exports.API_ROOT}/${endpoint}`);
await page.click('#run');
await responseCallback;
return page.evaluate(exports.getInnerText, 'output');
};
exports.verifyAllExamples = async (action, done) => {
const examples = await exports.fetchExamples();
for (example of examples) {
await page.click(`#${example.id}`);
expect(await action()).not.toContain('Error: ');
}
done();
};
exports.verifyWithBlankParameter = async (command, parameter, action, done) => {
await page.click('#command-select');
await page.click(`#${command}`);
await page.click(`#${parameter}`);
await exports.clearText(page.keyboard);
expect(await action()).toEqual(`Error: "${parameter}" is not allowed to be empty`);
done();
}
exports.verifyEntrypointBlank = async (command, action, done) => {
exports.verifyWithBlankParameter(command, 'entrypoint', action, done);
}
exports.verifyParametersBlank = async (command, action, done) => {
exports.verifyWithBlankParameter(command, 'parameters', action, done);
}
exports.verifyStorageBlank = async (command, action, done) => {
exports.verifyWithBlankParameter(command, 'storage', action, done);
}
exports.verifyWithCompilationError = async (action, done) => {
await page.click('#editor');
await page.keyboard.type('asdf');
expect(await action()).toContain('Error: ');
done();
};

View File

@ -0,0 +1,34 @@
const commonUtils = require('./common-utils');
const API_HOST = commonUtils.API_HOST;
const runCommandAndGetOutputFor = commonUtils.runCommandAndGetOutputFor;
const verifyEntrypointBlank = commonUtils.verifyEntrypointBlank;
const verifyAllExamples = commonUtils.verifyAllExamples;
const verifyWithCompilationError = commonUtils.verifyWithCompilationError;
const COMMAND = 'compile';
const COMMAND_ENDPOINT = 'compile-contract';
async function action() {
return await runCommandAndGetOutputFor(COMMAND, COMMAND_ENDPOINT);
}
describe('Compile contract', () => {
beforeAll(() => jest.setTimeout(60000));
beforeEach(async () => await page.goto(API_HOST));
it('should compile for each examples', async done => {
verifyAllExamples(action, done);
});
it('should return an error when entrypoint is blank', async done => {
verifyEntrypointBlank(COMMAND, action, done);
});
it('should return an error when code has compilation error', async done => {
verifyWithCompilationError(action, done);
});
});

View File

@ -0,0 +1,44 @@
const commonUtils = require('./common-utils');
const API_HOST = commonUtils.API_HOST;
const runCommandAndGetOutputFor = commonUtils.runCommandAndGetOutputFor;
const verifyAllExamples = commonUtils.verifyAllExamples;
const verifyEntrypointBlank = commonUtils.verifyEntrypointBlank;
const verifyParametersBlank = commonUtils.verifyParametersBlank;
const verifyStorageBlank = commonUtils.verifyStorageBlank;
const verifyWithCompilationError = commonUtils.verifyWithCompilationError;
const COMMAND = 'dry-run';
const COMMAND_ENDPOINT = 'dry-run';
async function action() {
return await runCommandAndGetOutputFor(COMMAND, COMMAND_ENDPOINT);
}
describe('Dry run contract', () => {
beforeAll(() => jest.setTimeout(60000));
beforeEach(async () => await page.goto(API_HOST));
it('should dry run for examples', async done => {
verifyAllExamples(action, done);
});
it('should return an error when entrypoint is blank', async done => {
verifyEntrypointBlank(COMMAND, action, done);
});
it('should return an error when parameters is blank', async done => {
verifyParametersBlank(COMMAND, action, done);
});
it('should return an error when storage is blank', async done => {
verifyStorageBlank(COMMAND, action, done);
});
it('should return an error when code has compilation error', async done => {
verifyWithCompilationError(action, done);
});
});

View File

@ -0,0 +1,39 @@
const commonUtils = require('./common-utils');
const API_HOST = commonUtils.API_HOST;
const runCommandAndGetOutputFor = commonUtils.runCommandAndGetOutputFor;
const verifyAllExamples = commonUtils.verifyAllExamples;
const verifyEntrypointBlank = commonUtils.verifyEntrypointBlank;
const verifyParametersBlank = commonUtils.verifyParametersBlank;
const verifyWithCompilationError = commonUtils.verifyWithCompilationError;
const COMMAND = 'evaluate-function';
const COMMAND_ENDPOINT = 'run-function';
async function action() {
return await runCommandAndGetOutputFor(COMMAND, COMMAND_ENDPOINT);
}
describe('Evaluate function', () => {
beforeAll(() => jest.setTimeout(60000));
beforeEach(async () => await page.goto(API_HOST));
it('should evaluate function for each examples', async done => {
verifyAllExamples(action, done);
});
it('should return an error when entrypoint is blank', async done => {
verifyEntrypointBlank(COMMAND, action, done);
});
it('should return an error when parameters is blank', async done => {
verifyParametersBlank(COMMAND, action, done);
});
it('should return an error when code has compilation error', async done => {
verifyWithCompilationError(action, done);
});
});

View File

@ -0,0 +1,210 @@
const commonUtils = require('./common-utils');
const fs = require('fs');
const API_HOST = commonUtils.API_HOST;
const API_ROOT = commonUtils.API_ROOT;
const getInnerText = commonUtils.getInnerText;
const getInputValue = commonUtils.getInputValue;
const createResponseCallback = commonUtils.createResponseCallback;
const clearText = commonUtils.clearText;
describe('Share', () => {
beforeAll(() => jest.setTimeout(60000));
it('should generate a link', async done => {
await page.goto(API_HOST);
await page.click('#editor');
await clearText(page.keyboard);
await page.keyboard.type('asdf');
const responseCallback = createResponseCallback(page, `${API_ROOT}/share`);
await page.click('#share');
await responseCallback;
const actualShareLink = await page.evaluate(getInputValue, 'share-link');
const expectedShareLink = `${API_HOST}/p/sIpy-2D9ExpCojwuBNw_-g`;
expect(actualShareLink).toEqual(expectedShareLink);
done();
});
it('should work with v0 schema', async done => {
const id = 'v0-schema';
const expectedShareLink = `${API_HOST}/p/${id}`;
const v0State = {
language: 'cameligo',
code: 'somecode',
entrypoint: 'main',
parameters: '1',
storage: '2'
};
fs.writeFileSync(`/tmp/${id}.txt`, JSON.stringify(v0State));
await page.goto(expectedShareLink);
// Check share link is correct
const actualShareLink = await page.evaluate(getInputValue, 'share-link');
expect(actualShareLink).toEqual(expectedShareLink);
// Check the code is correct. Note, because we are getting inner text we will get
// a line number as well. Therefore the expected value has a '1' prefix
const actualCode = await page.evaluate(getInnerText, 'editor');
expect(actualCode).toContain(`1${v0State.code}`);
// Check compile configuration
await page.click('#command-select');
await page.click('#compile');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v0State.entrypoint
);
// Check dry run configuration
await page.click('#command-select');
await page.click('#dry-run');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v0State.entrypoint
);
expect(await page.evaluate(getInputValue, 'parameters')).toEqual(
v0State.parameters
);
expect(await page.evaluate(getInputValue, 'storage')).toEqual(
v0State.storage
);
// Check deploy configuration
await page.click('#command-select');
await page.click('#deploy');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v0State.entrypoint
);
expect(await page.evaluate(getInputValue, 'storage')).toEqual(
v0State.storage
);
// Check evaluate function configuration
await page.click('#command-select');
await page.click('#evaluate-function');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v0State.entrypoint
);
expect(await page.evaluate(getInputValue, 'parameters')).toEqual(
v0State.parameters
);
// Check evaluate value configuration
await page.click('#command-select');
await page.click('#evaluate-value');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v0State.entrypoint
);
done();
});
it('should work with v1 schema', async done => {
const id = 'v1-schema';
const expectedShareLink = `${API_HOST}/p/${id}`;
const v1State = {
version: 'v1',
state: {
editor: {
language: 'cameligo',
code: 'somecode'
},
compile: {
entrypoint: 'main'
},
dryRun: {
entrypoint: 'main',
parameters: '1',
storage: '2'
},
deploy: {
entrypoint: 'main',
storage: '3',
useTezBridge: false
},
evaluateFunction: {
entrypoint: 'add',
parameters: '(1, 2)'
},
evaluateValue: {
entrypoint: 'a'
}
}
};
fs.writeFileSync(`/tmp/${id}.txt`, JSON.stringify(v1State));
await page.goto(expectedShareLink);
// Check share link is correct
const actualShareLink = await page.evaluate(getInputValue, 'share-link');
expect(actualShareLink).toEqual(expectedShareLink);
// Check the code is correct. Note, because we are getting inner text we will get
// a line number as well. Therefore the expected value has a '1' prefix
const actualCode = await page.evaluate(getInnerText, 'editor');
expect(actualCode).toContain(`1${v1State.state.editor.code}`);
// Check compile configuration
await page.click('#command-select');
await page.click('#compile');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v1State.state.compile.entrypoint
);
// Check dry run configuration
await page.click('#command-select');
await page.click('#dry-run');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v1State.state.dryRun.entrypoint
);
expect(await page.evaluate(getInputValue, 'parameters')).toEqual(
v1State.state.dryRun.parameters
);
expect(await page.evaluate(getInputValue, 'storage')).toEqual(
v1State.state.dryRun.storage
);
// Check deploy configuration
await page.click('#command-select');
await page.click('#deploy');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v1State.state.deploy.entrypoint
);
expect(await page.evaluate(getInputValue, 'storage')).toEqual(
v1State.state.deploy.storage
);
// Check evaluate function configuration
await page.click('#command-select');
await page.click('#evaluate-function');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v1State.state.evaluateFunction.entrypoint
);
expect(await page.evaluate(getInputValue, 'parameters')).toEqual(
v1State.state.evaluateFunction.parameters
);
// Check evaluate value configuration
await page.click('#command-select');
await page.click('#evaluate-value');
expect(await page.evaluate(getInputValue, 'entrypoint')).toEqual(
v1State.state.evaluateValue.entrypoint
);
done();
});
});

View File

@ -0,0 +1,22 @@
# Quick Start
```sh
yarn start
open http://localhost:8080
```
# Available Scripts
In the project directory, you can run:
## `yarn start`
Runs the server in development mode. This will also start the client.
## `yarn test`
Runs tests.
## `yarn build`
Builds the application for production to the `dist` folder.

View File

@ -0,0 +1,7 @@
module.exports = {
roots: ['test'],
testMatch: ['**/?(*.)+(spec|test).+(ts|tsx|js)'],
transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest'
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"prestart": "cd ../client && npm run build",
"start": "nodemon -r @ts-tools/node/r -r tsconfig-paths/register ./src/index.ts",
"build": "tsc",
"test": "jest"
},
"devDependencies": {
"@ts-tools/node": "^1.0.0",
"@types/express": "^4.17.1",
"@types/express-winston": "^3.0.4",
"@types/hapi__joi": "^16.0.1",
"@types/jest": "^24.0.23",
"@types/joi": "^14.3.3",
"@types/node": "10",
"@types/tmp": "^0.1.0",
"@types/winston": "^2.4.4",
"jest": "^24.9.0",
"nodemon": "^1.19.3",
"ts-jest": "^24.1.0",
"ts-node": "^8.4.1",
"tsconfig-paths": "^3.9.0",
"typescript": "~3.6.3"
},
"author": "",
"license": "ISC",
"dependencies": {
"@google-cloud/storage": "^4.0.0",
"@hapi/joi": "^16.1.7",
"@taquito/taquito": "^5.1.0-beta.1",
"@types/node-fetch": "^2.5.4",
"body-parser": "^1.19.0",
"escape-html": "^1.0.3",
"express": "^4.17.1",
"express-winston": "^4.0.1",
"node-fetch": "^2.6.0",
"sanitize-html": "^1.20.1",
"tmp": "^0.1.0",
"winston": "^3.2.1"
}
}

View File

@ -0,0 +1,49 @@
import joi from '@hapi/joi';
import { Request, Response } from 'express';
import { CompilerError, LigoCompiler } from '../ligo-compiler';
import { logger } from '../logger';
interface CompileBody {
syntax: string;
code: string;
entrypoint: string;
format?: string;
}
const validateRequest = (body: any): { value: CompileBody; error: any } => {
return joi
.object({
syntax: joi.string().required(),
code: joi.string().required(),
entrypoint: joi.string().required(),
format: joi.string().optional()
})
.validate(body);
};
export async function compileContractHandler(req: Request, res: Response) {
const { error, value: body } = validateRequest(req.body);
if (error) {
res.status(400).json({ error: error.message });
} else {
try {
const michelsonCode = await new LigoCompiler().compileContract(
body.syntax,
body.code,
body.entrypoint,
body.format || 'text'
);
res.send({ result: michelsonCode });
} catch (ex) {
if (ex instanceof CompilerError) {
res.status(400).json({ error: ex.message });
} else {
logger.error(ex);
res.sendStatus(500);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More