├── .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 |
--------------------------------------------------------------------------------