Skip to content

Commit

Permalink
feat: runner (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
fahchen authored Sep 24, 2024
1 parent 0caba9d commit 6d71d47
Show file tree
Hide file tree
Showing 73 changed files with 3,251 additions and 116 deletions.
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ locals_without_parens = [colset: 1, var: 1, val: 1, return: 1]

[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:typed_structor],
import_deps: [:typed_structor, :ecto, :ecto_sql],
locals_without_parens: locals_without_parens,
export: [
locals_without_parens: locals_without_parens
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ jobs:
test:
runs-on: ubuntu-latest
needs: mix
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: jet_test
ports:
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Setup
uses: byzanteam/jet-actions/setup-elixir@main
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def deps do
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/coloured_flow>.

Documentation can be generated with
[ExDoc](https://github.com/elixir-lang/ex_doc) and published on
[HexDocs](https://hexdocs.pm). Once published, the docs can be found at
<https://hexdocs.pm/coloured_flow>.
17 changes: 17 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Config

if config_env() == :test do
config :coloured_flow,
ColouredFlow.Runner.Storage,
repo: ColouredFlow.TestRepo

config :coloured_flow,
ecto_repos: [ColouredFlow.TestRepo]

config :coloured_flow, ColouredFlow.TestRepo,
database: "coloured_flow_test",
username: "postgres",
password: "postgres",
hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox
end
58 changes: 44 additions & 14 deletions lib/coloured_flow/definition/action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,67 @@ defmodule ColouredFlow.Definition.Action do
alias ColouredFlow.Definition.Variable
alias ColouredFlow.Expression.Action, as: ActionExpression

@type output() :: {:cpn_output_variable, Variable.name()} | ColourSet.value()
# TODO: meta is necessary?
@type output() ::
{:cpn_output_variable, {Variable.name(), meta :: keyword()}} | ColourSet.value()

typed_structor enforce: true do
typed_structor do
plugin TypedStructor.Plugins.DocFields

field :free_vars, [Variable.name()],
field :code, Expression.t(),
doc: """
The variables are the unbound variables in the out-going places.
The return values of the action will be bound to the free variables.
- `[:x, :y]`: outputs will be bound to [x, y]
The code segment to be executed when the transition is fired.
Examples:
```
quotient = div(dividend, divisor)
modulo = Integer.mod(dividend, divisor)
# use `output` keyword to mark outputs
output {quotient, modulo}
# the outputs are:
[
[
cpn_output_variable: {:quotient, [line: 4, column: 9]},
cpn_output_variable: {:modulo, [line: 4, column: 19]}
]
]
```
```
output {1, x}
# the outpus are:
[
[
1,
{:cpn_output_variable, {:x, [line: 1, column: 12]}}
]
]
```
"""

field :outputs, [output()],
field :outputs, [[output()]],
enforce: true,
doc: """
The return values of the action will be bound to the free variables.
- `[1, {:cpn_bind_variable, :x}]`: outputs [1, x]
- `[{:cpn_bind_variable, :x}, {:cpn_bind_variable, :5}]`: outputs [x, x]
- `[1, {:cpn_output_variable, :x}]`: outputs [1, x]
- `[{:cpn_output_variable, :x}, {:cpn_output_variable, :x}]`: outputs [x, x]
"""

field :code, Expression.t()
end

@spec build_outputs(Expression.t()) ::
{:ok, list(output())} | {:error, ColouredFlow.Expression.compile_error()}
{:ok, list(list(output()))} | {:error, ColouredFlow.Expression.compile_error()}
def build_outputs(%Expression{} = expression) do
outputs = extract_outputs(expression.expr)
check_outputs(outputs)
end

@spec build_outputs!(Expression.t()) :: list(output())
@spec build_outputs!(Expression.t()) :: list(list(output()))
def build_outputs!(%Expression{} = expression) do
case build_outputs(expression) do
{:ok, outputs} -> outputs
Expand All @@ -64,6 +93,7 @@ defmodule ColouredFlow.Definition.Action do
|> elem(1)
end

defp check_outputs([]), do: {:ok, []}
defp check_outputs([output]), do: {:ok, [output]}

defp check_outputs(outputs) do
Expand Down
20 changes: 18 additions & 2 deletions lib/coloured_flow/definition/arc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule ColouredFlow.Definition.Arc do
alias ColouredFlow.Definition.Variable
alias ColouredFlow.Expression.Arc, as: ArcExpression

@type name() :: binary()
@type label() :: binary()
@type orientation() :: :p_to_t | :t_to_p
@type binding() :: {
non_neg_integer() | {:cpn_bind_variable, Variable.name()},
Expand All @@ -22,7 +22,9 @@ defmodule ColouredFlow.Definition.Arc do
typed_structor enforce: true do
plugin TypedStructor.Plugins.DocFields

field :name, name()
field :label, label(),
enforce: false,
doc: "The label of the arc, optional, used for debugging."

field :orientation, orientation(),
doc: """
Expand All @@ -49,6 +51,20 @@ defmodule ColouredFlow.Definition.Arc do
(see <https://cpntools.org/2018/01/09/resource-allocation-example/>).
However, outgoing arcs are allowed to refer to an unbound variable
that will be updated during the transition action.
Examples:
```elixir
if x > 0 do
# use `bind` keyword to bind the variable
bind {1, x}
else
bind {2, 1}
end
# the bindings are:
# [{2, 1}, {1, {:cpn_bind_variable, :x}}]
```
"""

field :bindings,
Expand Down
9 changes: 6 additions & 3 deletions lib/coloured_flow/definition/colour_set.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ defmodule ColouredFlow.Definition.ColourSet do
# list
| [value()]

@typep primitive_descr() :: {:integer, []} | {:float, []} | {:boolean, []} | {:binary, []}
@typep unit_descr() :: {:unit, []}
@typep primitive_descr() ::
{:unit, []}
| {:integer, []}
| {:float, []}
| {:boolean, []}
| {:binary, []}
@typep tuple_descr() :: {:tuple, [descr()]}
@typep map_descr() :: {:map, %{atom() => descr()}}
@typep enum_descr() :: {:enum, [atom()]}
Expand All @@ -53,7 +57,6 @@ defmodule ColouredFlow.Definition.ColourSet do

@type descr() ::
primitive_descr()
| unit_descr()
| tuple_descr()
| map_descr()
| enum_descr()
Expand Down
16 changes: 8 additions & 8 deletions lib/coloured_flow/definition/colour_set.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Colour set

The colour set is like a data type in a programming language.
We use Elixir terms to define the colour sets.
The colour set is like a data type in a programming language. We use Elixir
terms to define the colour sets.

## Unit colour sets

Expand Down Expand Up @@ -53,13 +53,13 @@ colset int() :: integer();

### Operations

`i1 / i2` returns the floating-point division of `i1` by `i2`.
If you want to perform integer division, use the `Kernel.div/2` operator.
`i1 / i2` returns the floating-point division of `i1` by `i2`. If you want to
perform integer division, use the `Kernel.div/2` operator.

## Float(ing-point) colour sets

Floats are numerals with a decimal point.
It follows IEEE 754 standard, except for the `NaN` and `Infinity` values.
Floats are numerals with a decimal point. It follows IEEE 754 standard, except
for the `NaN` and `Infinity` values.

### Declaration syntax

Expand Down Expand Up @@ -163,8 +163,8 @@ colset packet() :: {:data, binary()} | {:ack, integer()}

## List colour sets

A list is a collection of elements, note that the
elements must be of the same type.
A list is a collection of elements, note that the elements must be of the same
type.

### Declaration syntax

Expand Down
1 change: 0 additions & 1 deletion lib/coloured_flow/definition/colour_set/of.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ defmodule ColouredFlow.Definition.ColourSet.Of do
defp type?(float, {:float, []}) when is_float(float), do: true
defp type?(bool, {:boolean, []}) when is_boolean(bool), do: true
defp type?(binary, {:binary, []}) when is_binary(binary), do: true

defp type?({}, {:unit, []}), do: true

# composite
Expand Down
48 changes: 48 additions & 0 deletions lib/coloured_flow/definition/colour_set/value.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule ColouredFlow.Definition.ColourSet.Value do
@moduledoc """
The value of a colour set.
"""

@doc """
Check if the value is valid.
"""
# credo:disable-for-next-line JetCredo.Checks.ExplicitAnyType
@spec valid?(value :: term()) :: boolean()
def valid?(term)

def valid?(int) when is_integer(int), do: true
def valid?(float) when is_float(float), do: true
def valid?(bool) when is_boolean(bool), do: true
def valid?(binary) when is_binary(binary), do: true
def valid?({}), do: true

# union: place it before tuple
def valid?({tag, value}) when is_atom(tag) do
valid?(value)
end

# tuple
def valid?(tuple) when is_tuple(tuple) and tuple_size(tuple) >= 2 do
values = Tuple.to_list(tuple)

Enum.all?(values, &valid?/1)
end

# map
def valid?(map) when is_map(map) and map_size(map) >= 1 do
Enum.all?(map, fn
{key, value} when is_atom(key) -> valid?(value)
_other -> false
end)
end

# enum
def valid?(enum) when is_atom(enum), do: true

# list
def valid?(list) when is_list(list) do
Enum.all?(list, &valid?/1)
end

def valid?(_value), do: false
end
18 changes: 17 additions & 1 deletion lib/coloured_flow/definition/expression.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ defmodule ColouredFlow.Definition.Expression do
typed_structor enforce: true do
plugin TypedStructor.Plugins.DocFields

field :code, binary() | nil,
default: nil,
doc: """
the original code of the expression. `nil` is a valid code that does nothing.
We store the code along with the `expr`, because compiling to `expr` will lose the original formatting.
"""

field :expr, Macro.t(),
default: nil,
doc: "a quoted expression, `nil` is a valid expression that does nothing."
Expand All @@ -23,17 +30,26 @@ defmodule ColouredFlow.Definition.Expression do
"""
end

@doc """
Build an expression from code.
Note that, `""` and `nil` are valid codes that are always evaluated to `nil`,
and are treated as `false` in the guard of a transition.
"""
@spec build(binary() | nil) ::
{:ok, t()}
| {:error, ColouredFlow.Expression.compile_error()}
def build(expr) when is_nil(expr) when expr === "", do: {:ok, %__MODULE__{}}

def build(expr) when is_binary(expr) do
with({:ok, quoted, vars} <- ColouredFlow.Expression.compile(expr, __ENV__)) do
{:ok, %__MODULE__{expr: quoted, vars: Map.keys(vars)}}
{:ok, %__MODULE__{code: expr, expr: quoted, vars: Map.keys(vars)}}
end
end

@doc """
Build an expression from code, raise if failed. See `build/1`.
"""
@spec build!(binary() | nil) :: t()
def build!(expr) do
case build(expr) do
Expand Down
3 changes: 1 addition & 2 deletions lib/coloured_flow/definition/procedure.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ defmodule ColouredFlow.Definition.Procedure do
plugin TypedStructor.Plugins.DocFields

field :name, name()
field :parameters, [ColourSet.name()], default: []
field :expression, Expression.t()
field :result, ColourSet.name()
field :result, ColourSet.descr()
end
end
4 changes: 4 additions & 0 deletions lib/coloured_flow/definition/transition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ defmodule ColouredFlow.Definition.Transition do
If not specified, the transition is always enabled.
Note that, the guard can't refer to an unbound variable.
If the guard is `nil`, the transition is always enabled.
However, if the guard code is `""`, the transition is never enabled.
See `ColouredFlow.Definition.Expression.build/1` for more details.
"""

field :action, Action.t(),
Expand Down
8 changes: 1 addition & 7 deletions lib/coloured_flow/enabled_binding_elements/computation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,7 @@ defmodule ColouredFlow.EnabledBindingElements.Computation do
[]

to_consume ->
[
%BindingElement{
transition: transition.name,
binding: binding,
to_consume: to_consume
}
]
[BindingElement.new(transition.name, binding, to_consume)]
end
end)
end
Expand Down
Loading

0 comments on commit 6d71d47

Please sign in to comment.