├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── include └── ensemble.hrl ├── priv └── test.ens ├── rebar.config ├── rebar.lock ├── rebar3 ├── src ├── ensemble.app.src ├── ensemble.erl ├── ensemble_app.erl ├── ensemble_interpreter.erl ├── ensemble_lexer.xrl ├── ensemble_parser.yrl ├── ensemble_repl.erl └── ensemble_sup.erl ├── test └── ensemble_SUITE.erl └── tools.mk /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | TEST-* 3 | data/ 4 | log/ 5 | src/ensemble_lexer.erl 6 | src/ensemble_parser.erl 7 | _checkouts/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 18.1 4 | install: 5 | - make 6 | - ./rebar3 update 7 | script: 8 | - make test 9 | - make xref 10 | - make dialyzer 11 | - make lint 12 | notifications: 13 | email: christopher.meiklejohn@gmail.com 14 | irc: "irc.freenode.org#lasp-lang" 15 | sudo: false 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE ?= ensemble 2 | VERSION ?= $(shell git describe --tags) 3 | BASE_DIR = $(shell pwd) 4 | ERLANG_BIN = $(shell dirname $(shell which erl)) 5 | REBAR = $(shell pwd)/rebar3 6 | MAKE = make 7 | 8 | .PHONY: rel deps test eqc plots 9 | 10 | all: compile 11 | 12 | ## 13 | ## Compilation targets 14 | ## 15 | 16 | compile: 17 | $(REBAR) compile 18 | 19 | clean: packageclean 20 | $(REBAR) clean 21 | 22 | packageclean: 23 | rm -fr *.deb 24 | rm -fr *.tar.gz 25 | 26 | ## 27 | ## Test targets 28 | ## 29 | 30 | check: test xref dialyzer lint 31 | 32 | test: eunit ct 33 | 34 | lint: 35 | ${REBAR} as lint lint 36 | 37 | eqc: 38 | ${REBAR} as test eqc 39 | 40 | eunit: 41 | ${REBAR} as test eunit 42 | 43 | ct: 44 | ${REBAR} as test ct 45 | 46 | ## 47 | ## Release targets 48 | ## 49 | 50 | rel: 51 | ${REBAR} release 52 | 53 | stage: 54 | ${REBAR} release -d 55 | 56 | shell: 57 | ${REBAR} shell --apps ensemble 58 | 59 | DIALYZER_APPS = kernel stdlib erts sasl eunit syntax_tools compiler crypto 60 | 61 | include tools.mk 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ensemble 2 | 3 | Ensemble is a prototype of a distributed, convergent, set-based 4 | programming language inspired by APL. Ensemble is build on top of the 5 | [Lasp](http://github.com/lasp-lang/lasp) epidemic broadcast runtime 6 | system for CRDTs. 7 | 8 | 9 | 10 | ## Running a REPL 11 | 12 | You can start a Read-Eval-Print-Loop fairly easily. 13 | 14 | ``` 15 | $ make shell 16 | [Erlang startup messages] 17 | 1> ensemble_repl:start() 18 | Ensemble-(1)> 19 | ``` 20 | 21 | ## Operations 22 | 23 | Assign a set of values to a variable. 24 | 25 | ``` 26 | > A <- 1 2 3 4 27 | {1 2 3 4} 28 | ``` 29 | 30 | Print the value of a variable. 31 | 32 | ``` 33 | > A 34 | {1 2 3 4} 35 | ``` 36 | 37 | Map the set contents using a binary function like addition with a value 38 | and assign the results to another variable. 39 | 40 | ``` 41 | > B <- A+1 42 | {2 3 4 5} 43 | ``` 44 | 45 | Or, try multiplication. 46 | 47 | ``` 48 | > C <- A*2 49 | {2 4 6 8} 50 | ``` 51 | 52 | You don't need to assign it either, it can evaluate like any other 53 | expression in the language. 54 | 55 | ``` 56 | > A*2 57 | {2 4 6 8} 58 | ``` 59 | 60 | Create a new set using the `iota` operation that will generate a series 61 | of values; here, the set of values is from 1 to 10. 62 | 63 | ``` 64 | > D <- i10 65 | {1 2 3 4 5 6 7 8 9 10} 66 | ``` 67 | 68 | Semi-colons can also be used to seperate statements on the same line. 69 | 70 | ``` 71 | > A <- i10; A 72 | {1 2 3 4 5 6 7 8 9 10} 73 | ``` 74 | 75 | Maybe try computing the Cartesian product. 76 | 77 | ``` 78 | > A <- i2; B <- i2; A*B 79 | { (1, 1) (1, 2) (2, 1) (2, 2) } 80 | ``` 81 | 82 | Then, try the intersection. 83 | 84 | ``` 85 | > A <- i2; B <- i10; A&B 86 | { 1 2 } 87 | ``` 88 | 89 | Or, try the union. 90 | 91 | ``` 92 | > A <- i2; B <- i5; A|B 93 | { 1 2 3 4 5 } 94 | ``` 95 | 96 | How about a fold? 97 | 98 | ``` 99 | > A <- i5; +/A 100 | 15 101 | ``` 102 | 103 | ## Coming Soon 104 | 105 | * compliment 106 | 107 | ## Copyright 108 | 109 | Copyright 2016 (c) Christopher S. Meiklejohn. 110 | -------------------------------------------------------------------------------- /include/ensemble.hrl: -------------------------------------------------------------------------------- 1 | -define(LEXER, ensemble_lexer). 2 | -define(PARSER, ensemble_parser). 3 | -define(INTERPRETER, ensemble_interpreter). 4 | 5 | -define(SET, lasp_orset). 6 | -define(COUNTER, lasp_pncounter). 7 | -------------------------------------------------------------------------------- /priv/test.ens: -------------------------------------------------------------------------------- 1 | A <- 1 2 3 4 2 | A 3 | B <- A+1 4 | B 5 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {lager, {git, "https://github.com/basho/lager", {tag, "3.1.0"}}}, 3 | {lasp, {git, "https://github.com/lasp-lang/lasp", {branch, "unstable"}}} 4 | ]}. 5 | 6 | %% leex options 7 | {xrl_opts, []}. 8 | %% leex files to compile first 9 | {xrl_first_files, ["src/ensemble_lexer.xrl"]}. 10 | 11 | %% yecc options 12 | {yrl_opts, []}. 13 | %% yecc files to compile first 14 | {yrl_first_files, ["src/ensemble_parser.yrl"]}. 15 | 16 | {cover_enabled, true}. 17 | {plugins, [{pc, {git, "https://github.com/blt/port_compiler.git", {branch, "master"}}}, 18 | rebar3_auto]}. 19 | 20 | {erl_opts, [debug_info, 21 | warnings_as_errors, 22 | {platform_define, "^[0-9]+", namespaced_types}, 23 | {parse_transform, lager_transform}]}. 24 | {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. 25 | {edoc_opts, [{preprocess, true}]}. 26 | 27 | {xref_checks, []}. 28 | {xref_queries, [{"(XC - UC) || (XU - X - B - \"(dtrace)\" : Mod)", []}]}. 29 | 30 | {profiles, [ 31 | {package, [ 32 | {plugins, [rebar3_hex]} 33 | ]}, 34 | {test, [ 35 | {plugins, [{rebar3_eqc, {git, "https://github.com/kellymclaughlin/rebar3-eqc-plugin.git", {branch, "master"}}}]} 36 | ]}, 37 | {lint, [ 38 | {plugins, [{rebar3_lint, {git, "https://github.com/project-fifo/rebar3_lint.git", {branch, "master"}}}]} 39 | ]}, 40 | {docs, [ 41 | {deps, [{edown, ".*", {git, "https://github.com/uwiger/edown.git", {branch, "master"}}}]} 42 | ]} 43 | ]}. 44 | 45 | {overrides, 46 | [{override, sext, [{{deps,default}, []}]} 47 | ]}. 48 | 49 | {elvis, 50 | [#{dirs => ["src"], 51 | filter => "*.erl", 52 | ignore => [ensemble_lexer, ensemble_parser], 53 | rules => [ 54 | %% {elvis_style, line_length, 55 | %% #{ignore => [], 56 | %% limit => 80, 57 | %% skip_comments => false}}, 58 | {elvis_style, no_tabs}, 59 | %% {elvis_style, no_trailing_whitespace}, 60 | {elvis_style, macro_names, #{ignore => []}}, 61 | %% {elvis_style, macro_module_names}, 62 | %% {elvis_style, operator_spaces, #{rules => [{right, ","}, 63 | %% {right, "++"}, 64 | %% {left, "++"}]}}, 65 | %% {elvis_style, nesting_level, #{level => 3}}, 66 | {elvis_style, god_modules, 67 | #{limit => 25, 68 | ignore => []}}, 69 | {elvis_style, no_if_expression}, 70 | %% {elvis_style, invalid_dynamic_call, #{ignore => []}}, 71 | %% {elvis_style, used_ignored_variable}, 72 | {elvis_style, no_behavior_info}, 73 | { 74 | elvis_style, 75 | module_naming_convention, 76 | #{regex => "^[a-z]([a-z0-9]*_?)*(_SUITE)?$", 77 | ignore => []} 78 | }, 79 | %% { 80 | %% elvis_style, 81 | %% function_naming_convention, 82 | %% #{regex => "^([a-z][a-z0-9]*_?)*$"} 83 | %% }, 84 | {elvis_style, state_record_and_type}, 85 | {elvis_style, no_spec_with_records} 86 | %% {elvis_style, dont_repeat_yourself, #{min_complexity => 10}} 87 | %% {elvis_style, no_debug_call, #{ignore => []}} 88 | ] 89 | }, 90 | #{dirs => ["."], 91 | filter => "Makefile", 92 | rules => [{elvis_project, no_deps_master_erlang_mk, #{ignore => []}}, 93 | {elvis_project, protocol_for_deps_erlang_mk, #{ignore => []}}] 94 | }, 95 | #{dirs => ["."], 96 | filter => "rebar.config", 97 | rules => [{elvis_project, no_deps_master_rebar, #{ignore => []}}, 98 | {elvis_project, protocol_for_deps_rebar, #{ignore => []}}] 99 | } 100 | ] 101 | }. 102 | 103 | {relx, [{release, {ensemble, "0.0.1"}, [ensemble]}, 104 | {extended_start_script, true}, 105 | 106 | {dev_mode, true}, 107 | {include_erts, false}, 108 | 109 | {sys_config, "config/sys.config"}, 110 | {vm_args, "config/vm.args"}, 111 | 112 | {overlay, [{mkdir, "log/sasl"}, 113 | {mkdir, "data/"}, 114 | {copy, "bin/env", "bin"}]}]}. 115 | 116 | {dialyzer, [ 117 | {warnings, [no_return, 118 | no_unused, 119 | no_improper_lists, 120 | no_fun_app, 121 | no_match, 122 | no_opaque, 123 | no_fail_call, 124 | no_contracts, 125 | no_behaviours, 126 | no_undefined_callbacks, 127 | %% unmatched_returns, 128 | error_handling, 129 | race_conditions 130 | %% overspecs, 131 | %% underspecs, 132 | %% specdiffs 133 | ]}]}. 134 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | [{<<"gb_trees_ext">>, 2 | {git,"https://github.com/lasp-lang/gb_trees_ext.git", 3 | {ref,"dba0bbc4bc55d4f062e0bf5c0b807a4eb915e934"}}, 4 | 1}, 5 | {<<"gen_flow">>, 6 | {git,"https://github.com/lasp-lang/gen_flow.git", 7 | {ref,"1308d72e02dfe85e55fffcd73f3cb1138a222d1e"}}, 8 | 1}, 9 | {<<"goldrush">>, 10 | {git,"git://github.com/DeadZen/goldrush.git", 11 | {ref,"212299233c7e7eb63a97be2777e1c05ebaa58dbe"}}, 12 | 1}, 13 | {<<"jsx">>, 14 | {git,"https://github.com/talentdeficit/jsx.git", 15 | {ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, 16 | 1}, 17 | {<<"lager">>, 18 | {git,"https://github.com/basho/lager", 19 | {ref,"b2cb2735713e3021e0761623ff595d53a545438e"}}, 20 | 0}, 21 | {<<"lasp_support">>, 22 | {git,"https://github.com/lasp-lang/lasp_support.git", 23 | {ref,"f5b72230e04a16a511f30cef22cdda1204cd7585"}}, 24 | 1}, 25 | {<<"mochiweb">>, 26 | {git,"https://github.com/mochi/mochiweb.git", 27 | {ref,"c3fb6aed018d47a9c313daa966657a157d88dbc6"}}, 28 | 1}, 29 | {<<"plumtree">>, 30 | {git,"https://github.com/lasp-lang/plumtree", 31 | {ref,"472f4343c7713d619c82e823c8d5f6307034ff19"}}, 32 | 1}, 33 | {<<"riak_dt">>, 34 | {git,"https://github.com/lasp-lang/riak_dt", 35 | {ref,"a2986bccd1cc42facdfe739495c6d13762ae0f37"}}, 36 | 1}, 37 | {<<"sext">>, 38 | {git,"https://github.com/uwiger/sext.git", 39 | {ref,"35520daea8ddc48569249ad19637f4cc75fbd03d"}}, 40 | 1}, 41 | {<<"time_compat">>, 42 | {git,"https://github.com/lasp-lang/time_compat.git", 43 | {ref,"6007f68892104ebb6fa2366cebf9d928d8856273"}}, 44 | 1}, 45 | {<<"webmachine">>, 46 | {git,"https://github.com/webmachine/webmachine.git", 47 | {ref,"03d243937d16bf8cbd7feddfe0ad830808494732"}}, 48 | 1}]. 49 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmeiklejohn/ensemble/9471b07c0ce18d7a58fef683fceca99992579ff3/rebar3 -------------------------------------------------------------------------------- /src/ensemble.app.src: -------------------------------------------------------------------------------- 1 | {application,ensemble, 2 | [{description, 3 | "Distributed programming lanauge for sets."}, 4 | {vsn,"0.0.1"}, 5 | {maintainers,["Christopher S. Meiklejohn"]}, 6 | {links,[{"Github","https://github.com/cmeiklejohn/ensemble"}]}, 7 | {licenses,["Apache"]}, 8 | {registered,[]}, 9 | {applications, 10 | [kernel,stdlib,lager,lasp]}, 11 | {mod,{ensemble_app,[]}}, 12 | {env,[]}]}. 13 | -------------------------------------------------------------------------------- /src/ensemble.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher S. Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(ensemble). 22 | -author("Christopher S. Meiklejohn "). 23 | 24 | -include("ensemble.hrl"). 25 | -------------------------------------------------------------------------------- /src/ensemble_app.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher S. Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(ensemble_app). 22 | 23 | -behaviour(application). 24 | 25 | %% Application callbacks 26 | -export([start/2, stop/1]). 27 | 28 | %% =================================================================== 29 | %% Application callbacks 30 | %% =================================================================== 31 | 32 | start(_StartType, _StartArgs) -> 33 | case ensemble_sup:start_link() of 34 | {ok, Pid} -> 35 | {ok, Pid}; 36 | {error, Reason} -> 37 | {error, Reason} 38 | end. 39 | 40 | stop(_State) -> 41 | ok. 42 | -------------------------------------------------------------------------------- /src/ensemble_interpreter.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher S. Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(ensemble_interpreter). 22 | -author("Christopher S. Meiklejohn "). 23 | 24 | -include("ensemble.hrl"). 25 | 26 | -ifdef(TEST). 27 | -include_lib("eunit/include/eunit.hrl"). 28 | -endif. 29 | 30 | -export([eval/1]). 31 | 32 | -record(state, 33 | { 34 | actor :: binary() 35 | }). 36 | 37 | %% @doc Evaluate a program. 38 | eval(Program) -> 39 | %% First, apply lexical analysis. 40 | {ok, Tokens, _EndLine} = ?LEXER:string(Program), 41 | 42 | %% Next, parse into an AST. 43 | {ok, ParseTree} = ?PARSER:parse(Tokens), 44 | 45 | %% Generate an actor identifier for execution of this application. 46 | Actor = term_to_binary(node()), 47 | 48 | %% Finally, evaluate the program. 49 | eval(ParseTree, #state{actor=Actor}). 50 | 51 | %% @private 52 | eval('$undefined', _State) -> 53 | %% If the parse tree produced nothing, evaluation returns the empty 54 | %% set, I think? 55 | pp([]); 56 | eval([Stmt|[]], State0) -> 57 | %% Final call, so ignore returned state. 58 | {Result, _State} = statement(Stmt, State0), 59 | pp(Result); 60 | eval([Stmt|Stmts], State0) -> 61 | %% Ignore any intermediate results. 62 | {_Result, State} = statement(Stmt, State0), 63 | eval(Stmts, State). 64 | 65 | %% @private 66 | %% 67 | %% Update is an assignment statement, where the expression on the rhs is 68 | %% evaluated and assigned to the variable on the lhs; the variable on 69 | %% the lhs may not exist, so the variable needs to first be delcared. 70 | %% 71 | statement({update, {var, Line, Variable}, Expression}, 72 | #state{actor=Actor}=State0) -> 73 | %% Create a new variable. 74 | {ok, _} = lasp:declare(Variable, ?SET), 75 | 76 | %% Evaluate the expression. 77 | case expression(Expression, State0) of 78 | %% We got back the identifier of a variable that contains the 79 | %% state that we want. 80 | {{var, _, TheirId}, State1} -> 81 | 82 | %% Bind our new variable directly to the shadow variable. 83 | ok = lasp:bind_to(Variable, TheirId), 84 | 85 | %% Return variable. 86 | {{var, Line, Variable}, State1}; 87 | 88 | %% Else, we got back an literal value that we can directly bind 89 | %% to the variable. 90 | {Literal, State1} -> 91 | 92 | %% Bind updated value. 93 | {ok, _} = lasp:update(Variable, {add_all, Literal}, Actor), 94 | 95 | %% Return variable. 96 | {{var, Line, Variable}, State1} 97 | 98 | end; 99 | %% Otherwise, the statement must be an expression that evaluates to a 100 | %% value that will be returned to the user. Attempt to evaluate this 101 | %% expression. 102 | statement(Stmt, State) -> 103 | expression(Stmt, State). 104 | 105 | %% @private 106 | %% 107 | %% When a process call is received, create a shadow variable to store 108 | %% the results of the map operation and return the identifier to the 109 | %% caller of map: it's their decision whether to query and return the 110 | %% result to the user, or assign the variable to another variable in the 111 | %% system. 112 | %% 113 | expression({process, 114 | {map, {var, Line, Source}, {function, {Function0, _}}, Val}}, 115 | State0) -> 116 | 117 | %% Generate an Erlang anonymous function. 118 | Function = case Function0 of 119 | '+' -> 120 | fun(X) -> X + Val end; 121 | '*' -> 122 | fun(X) -> X * Val end 123 | end, 124 | 125 | %% Create a shadow variable used to store the result of the fold 126 | %% operation; this will get an anonymous global variable. 127 | %% 128 | {ok, {Destination, _, _, _}} = lasp:declare(?SET), 129 | 130 | %% Execute the map operation. 131 | ok = lasp:map(Source, Function, Destination), 132 | 133 | %% Return variable. 134 | {{var, Line, Destination}, State0}; 135 | expression({process, 136 | {union, {var, Line, Left}, {var, Line, Right}}}, 137 | State0) -> 138 | 139 | %% Create a shadow variable used to store the result of the 140 | %% operation; this will get an anonymous global variable. 141 | %% 142 | {ok, {Union, _, _, _}} = lasp:declare(?SET), 143 | 144 | %% Execute the operation. 145 | ok = lasp:union(Left, Right, Union), 146 | 147 | %% Return variable. 148 | {{var, Line, Union}, State0}; 149 | expression({process, 150 | {intersection, {var, Line, Left}, {var, Line, Right}}}, 151 | State0) -> 152 | 153 | %% Create a shadow variable used to store the result of the 154 | %% operation; this will get an anonymous global variable. 155 | %% 156 | {ok, {Intersection, _, _, _}} = lasp:declare(?SET), 157 | 158 | %% Execute the operation. 159 | ok = lasp:intersection(Left, Right, Intersection), 160 | 161 | %% Return variable. 162 | {{var, Line, Intersection}, State0}; 163 | expression({process, 164 | {fold, {function, {Function0, Line}}, {var, Line, Source}}}, 165 | State0) -> 166 | 167 | %% Preprocess the function. 168 | {ok, Type, Function} = function(Function0), 169 | 170 | %% Create a shadow variable used to store the result of the fold 171 | %% operation; this will get an anonymous global variable. 172 | %% 173 | {ok, {Fold, _, _, _}} = lasp:declare(Type), 174 | 175 | %% Execute the operation. 176 | ok = lasp:fold(Source, Function, Fold), 177 | 178 | %% Return variable. 179 | {{var, Line, Type, Fold}, State0}; 180 | expression({process, 181 | {product, {var, Line, Left}, {var, Line, Right}}}, 182 | State0) -> 183 | 184 | %% Create a shadow variable used to store the result of the fold 185 | %% operation; this will get an anonymous global variable. 186 | %% 187 | {ok, {Product, _, _, _}} = lasp:declare(?SET), 188 | 189 | %% Execute the operation. 190 | ok = lasp:product(Left, Right, Product), 191 | 192 | %% Return variable. 193 | {{var, Line, Product}, State0}; 194 | expression(List0, State0) when is_list(List0) -> 195 | lists:foldl(fun(Expr, {List, State}) -> 196 | {V, S} = expression(Expr, State), 197 | {List ++ [V], S} 198 | end, {[], State0}, List0); 199 | expression(V, State) when is_integer(V) -> 200 | {V, State}; 201 | %% Do not evaluate variables any further; it's up to the pretty printer 202 | %% to do that. 203 | expression({var, Line, Variable}, State0) -> 204 | {{var, Line, Variable}, State0}; 205 | expression({iota, V}, State) -> 206 | {lists:seq(1, V), State}; 207 | expression(Expr, _State) -> 208 | lager:info("Expression not caught: ~p", [Expr]), 209 | exit(badarg). 210 | 211 | %% @private 212 | pp({var, Line, Variable}) -> 213 | pp({var, Line, ?SET, Variable}); 214 | pp({var, _, Type, Variable}) -> 215 | case lasp:read(Variable, {strict, undefined}) of 216 | {ok, {_, _, _, Value0}} -> 217 | Value = lasp_type:value(Type, Value0), 218 | pp(Value); 219 | {error, not_found} -> 220 | io:format("Variable ~p is not in scope.~n", [Variable]), 221 | exit(badarg) 222 | end; 223 | pp(List) when is_list(List) -> 224 | list_to_binary("{ " ++ [pp({element, Item}) || Item <- List] ++ "}"); 225 | pp({element, {A, B}}) -> 226 | io_lib:format("(~p, ~p) ", [A, B]); 227 | pp({element, X}) -> 228 | io_lib:format("~p ", [X]); 229 | pp(Value) -> 230 | list_to_binary(integer_to_list(Value)). 231 | 232 | %% @private 233 | function('+') -> 234 | %% Use a PN-Counter for the destination data type. 235 | Type = ?COUNTER, 236 | 237 | %% Convert the function to a proper functor from set to counter. 238 | Function = fun(X, _Acc) -> [{increment, X}] end, 239 | 240 | {ok, Type, Function}. 241 | -------------------------------------------------------------------------------- /src/ensemble_lexer.xrl: -------------------------------------------------------------------------------- 1 | Definitions. 2 | 3 | D = [0-9] 4 | V = [A-Z_][0-9a-zA-Z_]* 5 | L = [A-Za-z] 6 | CR = \r 7 | NL = \n 8 | WS = ([\000-\s]|%.*) 9 | 10 | Rules. 11 | 12 | <- : {token,{list_to_atom(TokenChars),TokenLine}}. 13 | \+ : {token,{list_to_atom(TokenChars),TokenLine}}. 14 | x : {token,{list_to_atom(TokenChars),TokenLine}}. 15 | \* : {token,{list_to_atom(TokenChars),TokenLine}}. 16 | \& : {token,{list_to_atom(TokenChars),TokenLine}}. 17 | i : {token,{list_to_atom(TokenChars),TokenLine}}. 18 | \/ : {token,{list_to_atom(TokenChars),TokenLine}}. 19 | \| : {token,{list_to_atom(TokenChars),TokenLine}}. 20 | {V}+ : {token,{var,TokenLine,list_to_atom(TokenChars)}}. 21 | {D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}. 22 | [(),] : {token,{list_to_atom(TokenChars),TokenLine}}. 23 | ({CR}|{NL}|{CR}{NL}|;) : {token,{nl,TokenLine}}. 24 | {WS}+ : skip_token. 25 | 26 | Erlang code. 27 | -------------------------------------------------------------------------------- /src/ensemble_parser.yrl: -------------------------------------------------------------------------------- 1 | Nonterminals 2 | statements statement expression int_list function product union 3 | intersection fold. 4 | 5 | Terminals 6 | 'x' '/' '&' '|' '<-' '+' '*' 'i' var integer nl. 7 | 8 | Rootsymbol 9 | statements. 10 | 11 | %% Expect 1 shift/reduce conflict from the integer list creation and 12 | %% newline handling. 13 | Expect 3. 14 | 15 | statements -> statement : ['$1']. 16 | statements -> statements nl statements : '$1' ++ '$3'. 17 | statements -> statements nl : '$1'. 18 | statements -> nl. 19 | 20 | %% Statements can be of three types: expressions, assignment statements 21 | %% that take expressions, and variables, which are the primary value 22 | %% type. 23 | statement -> var '<-' expression : {update, '$1', '$3'}. 24 | 25 | statement -> expression : '$1'. 26 | 27 | %% In our language, variables are not expressions, they are values. 28 | %% This is because we will expose too much temporaility in the language 29 | %% if things operate by value, therefore, we reduce to variables. 30 | statement -> var : '$1'. 31 | 32 | %% The iota operator performs set generation. 33 | expression -> 'i' integer : {iota, unwrap('$2')}. 34 | 35 | %% The map operation applies a function with the rhs argument as the 36 | %% evaluation of the provided expression. 37 | expression -> var function expression : {process, {map, '$1', '$2', '$3'}}. 38 | 39 | %% Cartesian product, computes the cross product between two variables. 40 | expression -> var product var : {process, {product, '$1', '$3'}}. 41 | 42 | %% Fold operation. 43 | expression -> function fold var : {process, {fold, '$1', '$3'}}. 44 | 45 | %% Union. 46 | expression -> var union var : {process, {union, '$1', '$3'}}. 47 | 48 | %% Intersection. 49 | expression -> var intersection var : {process, {intersection, '$1', '$3'}}. 50 | 51 | %% Lists of integers, the other primary value type. 52 | expression -> int_list : '$1'. 53 | expression -> integer : unwrap('$1'). 54 | 55 | int_list -> integer integer : [unwrap('$1'), unwrap('$2')]. 56 | int_list -> integer int_list : [unwrap('$1')] ++ '$2'. 57 | 58 | %% Function types. 59 | function -> '+' : {function, '$1'}. 60 | function -> '*' : {function, '$1'}. 61 | 62 | %% Product. 63 | product -> 'x' : '$1'. 64 | 65 | %% Union. 66 | union -> '|' : '$1'. 67 | 68 | %% Intersection. 69 | intersection -> '&' : '$1'. 70 | 71 | %% Fold. 72 | fold -> '/' : '$1'. 73 | 74 | Erlang code. 75 | 76 | unwrap({_, _, V}) -> V. 77 | -------------------------------------------------------------------------------- /src/ensemble_repl.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher S. Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(ensemble_repl). 22 | -author("Christopher S. Meiklejohn "). 23 | 24 | -export([start/0]). 25 | 26 | -include("ensemble.hrl"). 27 | 28 | -record(state, {counter :: non_neg_integer()}). 29 | 30 | %%%=================================================================== 31 | %%% API 32 | %%%=================================================================== 33 | 34 | %% @doc Initialize a repl. 35 | -spec start() -> no_return(). 36 | start() -> 37 | State = #state{counter=1}, 38 | loop(State). 39 | 40 | %% @private 41 | loop(#state{counter=Counter}=State) -> 42 | 43 | %% Generate a prompt with the current line number. 44 | Prompt = io_lib:format("Ensemble(~p)> ", [Counter]), 45 | 46 | %% Get input from the user. 47 | Data = io:get_line(Prompt), 48 | 49 | try 50 | %% Evaulate received input with the interpreter. 51 | Result = ?INTERPRETER:eval(Data), 52 | 53 | %% Output the result. 54 | io:format("~s~n", [binary_to_list(Result)]) 55 | catch 56 | _:Error -> 57 | io:format("~n", []), 58 | io:format("Error: ~p~n", [Error]), 59 | io:format("~n", []), 60 | io:format("Backtrace:~n~p~n", [erlang:get_stacktrace()]), 61 | io:format("~n", []) 62 | end, 63 | 64 | loop(State#state{counter=Counter+1}). 65 | -------------------------------------------------------------------------------- /src/ensemble_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher S. Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(ensemble_sup). 22 | -author("Christopher S. Meiklejohn "). 23 | 24 | -behaviour(supervisor). 25 | 26 | %% API 27 | -export([start_link/0]). 28 | 29 | %% Supervisor callbacks 30 | -export([init/1]). 31 | 32 | %% =================================================================== 33 | %% API functions 34 | %% =================================================================== 35 | 36 | start_link() -> 37 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 38 | 39 | %% =================================================================== 40 | %% Supervisor callbacks 41 | %% =================================================================== 42 | 43 | init(_Args) -> 44 | {ok, {{one_for_one, 5, 10}, []}}. 45 | -------------------------------------------------------------------------------- /test/ensemble_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher S. Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% 21 | 22 | -module(ensemble_SUITE). 23 | -author("Christopher S. Meiklejohn "). 24 | 25 | %% common_test callbacks 26 | -export([%% suite/0, 27 | init_per_suite/1, 28 | end_per_suite/1, 29 | init_per_testcase/2, 30 | end_per_testcase/2, 31 | all/0]). 32 | 33 | %% tests 34 | -compile([export_all]). 35 | 36 | -include_lib("common_test/include/ct.hrl"). 37 | 38 | %% =================================================================== 39 | %% common_test callbacks 40 | %% =================================================================== 41 | 42 | init_per_suite(_Config) -> 43 | application:ensure_all_started(lasp), 44 | _Config. 45 | 46 | end_per_suite(_Config) -> 47 | application:stop(lasp), 48 | _Config. 49 | 50 | init_per_testcase(_Case, Config) -> 51 | lasp:reset(), 52 | Config. 53 | 54 | end_per_testcase(_, _Config) -> 55 | ok. 56 | 57 | all() -> 58 | [ 59 | iota_test, 60 | map_plus_test, 61 | map_times_test, 62 | map_without_assignment_test, 63 | product_without_assignment_test, 64 | intersection_without_assignment_test, 65 | union_without_assignment_test, 66 | fold_plus_without_assignment_test, 67 | assignment_test, 68 | empty_test 69 | ]. 70 | 71 | %% =================================================================== 72 | %% tests 73 | %% =================================================================== 74 | 75 | -include("ensemble.hrl"). 76 | 77 | %% @doc Verify the iota behaviour. 78 | iota_test(_Config) -> 79 | Program = io_lib:format("A <- i10", []), 80 | <<"{ 1 2 3 4 5 6 7 8 9 10 }">> = ?INTERPRETER:eval(Program), 81 | Program2 = io_lib:format("A <- i10; A", []), 82 | <<"{ 1 2 3 4 5 6 7 8 9 10 }">> = ?INTERPRETER:eval(Program2), 83 | ok. 84 | 85 | %% @doc Verify the map behaviour. 86 | map_plus_test(_Config) -> 87 | Program = io_lib:format("A <- i10; B <- A + 1; B", []), 88 | <<"{ 2 3 4 5 6 7 8 9 10 11 }">> = ?INTERPRETER:eval(Program), 89 | ok. 90 | 91 | %% @doc Verify the map behaviour. 92 | map_times_test(_Config) -> 93 | Program2 = io_lib:format("A <- i5; B <- A * 2; B", []), 94 | <<"{ 2 4 6 8 10 }">> = ?INTERPRETER:eval(Program2), 95 | ok. 96 | 97 | %% @doc Verify the map behaviour. 98 | map_without_assignment_test(_Config) -> 99 | Program2 = io_lib:format("A <- i5; A * 2", []), 100 | <<"{ 2 4 6 8 10 }">> = ?INTERPRETER:eval(Program2), 101 | ok. 102 | 103 | %% @doc Verify the product behaviour. 104 | product_without_assignment_test(_Config) -> 105 | Program2 = io_lib:format("A <- i2; B <- i2; A x B", []), 106 | <<"{ (1, 1) (1, 2) (2, 1) (2, 2) }">> = ?INTERPRETER:eval(Program2), 107 | ok. 108 | 109 | %% @doc Verify the intersection behaviour. 110 | intersection_without_assignment_test(_Config) -> 111 | Program2 = io_lib:format("A <- i2; B <- i2; A & B", []), 112 | <<"{ 1 2 }">> = ?INTERPRETER:eval(Program2), 113 | ok. 114 | 115 | %% @doc Verify the union behaviour. 116 | union_without_assignment_test(_Config) -> 117 | Program2 = io_lib:format("A <- i2; B <- i2; A | B", []), 118 | <<"{ 1 2 }">> = ?INTERPRETER:eval(Program2), 119 | ok. 120 | 121 | %% @doc Verify the fold behaviour. 122 | fold_plus_without_assignment_test(_Config) -> 123 | Program2 = io_lib:format("A <- i5; +/A", []), 124 | <<"15">> = ?INTERPRETER:eval(Program2), 125 | ok. 126 | 127 | %% @doc Verify the assignment behaviour. 128 | assignment_test(_Config) -> 129 | Program = io_lib:format("A <- 1 2 3 4", []), 130 | <<"{ 1 2 3 4 }">> = ?INTERPRETER:eval(Program), 131 | ok. 132 | 133 | %% @doc Verify the empty behaviour. 134 | empty_test(_Config) -> 135 | Program = io_lib:format("\n", []), 136 | <<"{ }">> = ?INTERPRETER:eval(Program), 137 | ok. 138 | 139 | %% @todo Product, fold, intersection, and union all need to support 140 | %% literals. 141 | -------------------------------------------------------------------------------- /tools.mk: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # 3 | # Copyright (c) 2014 Basho Technologies, Inc. 4 | # 5 | # This file is provided to you under the Apache License, 6 | # Version 2.0 (the "License"); you may not use this file 7 | # except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | # ------------------------------------------------------------------- 20 | 21 | # ------------------------------------------------------------------- 22 | # NOTE: This file is is from https://github.com/basho/tools.mk. 23 | # It should not be edited in a project. It should simply be updated 24 | # wholesale when a new version of tools.mk is released. 25 | # ------------------------------------------------------------------- 26 | 27 | REBAR = $(shell pwd)/rebar3 28 | REVISION ?= $(shell git rev-parse --short HEAD) 29 | PROJECT ?= $(shell basename `find src -name "*.app.src"` .app.src) 30 | DEP_DIR ?= "deps" 31 | EBIN_DIR ?= "ebin" 32 | 33 | .PHONY: compile-no-deps test docs xref dialyzer-run dialyzer-quick dialyzer \ 34 | cleanplt upload-docs 35 | 36 | compile-no-deps: 37 | ${REBAR} compile skip_deps=true 38 | 39 | docs: 40 | ${REBAR} doc skip_deps=true 41 | 42 | xref: compile 43 | ${REBAR} xref skip_deps=true 44 | 45 | dialyzer: compile 46 | ${REBAR} dialyzer 47 | --------------------------------------------------------------------------------