-
Notifications
You must be signed in to change notification settings - Fork 5
EmfrpDoc
This page is the easy documentation for Emfrp language and Emfrp compiler.
These appear with module declaration and define inputs and outputs respectively.
The type-specification follwing : is required for Input-definition.
module MyModule
in x : Int, y : Int
out a, b
You can optionally attach a type-annotation for a output-definition by same way with Input.
out a : Int, b : Int
You can set an initial value of an input by () following name.
in x(0) : Int, y(0) : Int
The initial value is used when the input node is referred with @last at the first-iteration.
use declaration can appear after in/out declarations.
This is the declaration to import and use Emfrp's materials in the module.
(The material is mentioned later.)
module MyModule in x : Int out y use Std
In this case, the Emfrp's standard library (Std) is imported.
Multiple materials can be imported.
use Std, MyMaterial
This defines a time-varying value as a node of the directed-graph by specifying the names of its parents.
The parents are other nodes or inputs.
In the following example, a node c is defined as
(time-varying value of a node a) + (time-varying value of a node b).
node from[a, b] c = a + b
Note that the right-side of = is an arbitrary expression.
(The expression is mentioned later.)
You can use aliases of the nodes by naming at from specification.
Or you can omit the whole of from specification.
Thus, the following three node-definitions have the same semantics.
node from[a, b] c = a + b
node from[a as x, b as y] c = x + y
node c = a + b
You can refer the last-time value of arbitrary nodes.
The nodes whose last-time value is referred by others should be defined with init specification.
node init[0] x = x@last + 1
node from[x@last as lastX, x] dx = lastX - x
You can use a pattern match at the left-side value of the node-definition. (The pattern match is mentioned later.)
node init[(0, 0)] (x, y) as pos = if flag then pos@last else (x@last + 1, y@last + 1)
This defines nodes in a way of instantiating the other Module.
node x = inp + 1
newnode a, b = SubModule(x, x + 1)
A bit more detailed example is shown later (in Module/Material section).
This defines a polymorphic algebraic data type. Note that defining recursive data types is not allowed in Emfrp. This constraint is for the Emfrp's property of using only fixed-size memory.
type Maybe[a] = Just(a) | Nothing
type Tuple2[a, b] = Tuple2(a, b)
The notation (arg1, arg2, ..., argN) is available for type-names or constructors instead of TupleN(arg1, arg2, ..., argN)
data x : (Int, Int) = (1, 2)
This defines a polymorphic function. Note that defining recursive functions is not allowed and functions are not first-class in Emfrp. This constraint is for the Emfrp's properties of using only fixed-size memory and keeping runtime stability.
func square(i) = i * i
func id(x) = x
You can attach type-annotations for function's parameter and/or body.
func square(i : Int) : Int = i * i
func id(x : a) : a = x
Note that defining 0-arg functions is not allowed.
This defines monomorphic global constant value.
data two : Int = 2
data twotwo = two * two
Defined data can be referred in everywhere.
func twice(i) = i * two
<func-name>(<arg-expression>+)
Example:
func inc(i) = i + 1
data two = inc(1)
As syntax sugar, the method-like calling is supported.
<arg1-expression>.<func-name>(<rest-arg-expression>*)
<arg1-expression>.<func-name>
Thus, the following three function calls have the same semantics.
inc(1)
1.inc()
1.inc
An operator is a function. If a 2-arg function is defined with the name containing only symbol-chars, it behaves as a binary-operator.
func ++(a, b) = a + a + b + b
data x = 1 ++ 2
You can set the order of binary-operator by infix, infixl, infixr definitions.
infixl 6 ++
Same as the Haskell's notation, infix, infixl and infixr mean non-associative, left-associative and right-associative respectively.
The priority can be set from 1 to 9.
By quoting by the back-quote, you can use a normal 2-arg function as a binary-operator.
func add(a, b) = a + b
infix 6 `add`
data x = 1 `add` 2
You can use also unary-operators as follows:
func @!(x : Bool) = if x then False else True
data x = !True
Note that @ has the special meaning to define an unary-operator.
(In fact, @ doesn't have special meaning here so that !True is just pre-converted to @!(True).)
Nesting pattern matches are available.
One notation:
<exp> of <pattern1> -> <body-exp1>, <pattern2> -> <body-exp2>, ...
Another notation:
<exp> of:
<pattern1> -> <body-exp1>
<pattern2> -> <body-exp2>
Basic example:
m of Just(x) -> x, Nothing -> 0
Complex example:
m of ((x, _) as left, right) -> (x, left, right)
To use non-complete patterns is not allowed.
# invalid expression
m of Just(x) -> x
Note that the process of checking the pattern's non-completeness is not implemented currently (as of v0.1.2).
You can define local-values by the let-expression.
data x = {
foo = 1 + 1
bar = foo + foo
bar * 2
}
The if-expression is also supported:
func f(a) = if a == 0 then -1 else 1
And this is a just syntax sugar of:
func f(a) = a == 0 of True -> -1, False -> 1
Int is a type but not an algebraic data type so Int is a premitive type and user can define arbitrary premitive types as follows.
primtype Int = c{ int }
primtype Double = c{ double }
The first definition means Int is compiled into C-language as int.
Operator + is a function but cannot be expressed as an Emfrp's expression so + is a primitive function and user can define arbitrary primitive functions as follows.
primfunc + (a : Int, b : Int) : Int = c{ a + b }, ruby{ a + b }
This means an Emfrp's expression a + b is compiled into C-language as a C's expression a + b and interpreted by Ruby as a + b.
The material is used for the purpose of modularizing functions/datas. You can define a material file with func/data definitions and use it from other Module/Material files.
# Foo.mfrp
material Foo
data foo = 1
func foofoo(a) = a + foo
# Main.mfrp
module Main in x : Int out y
use Foo
node y = foofoo(x)
The module is used for the purpose of modularizing FRP. It acts as a unit of the whole FRP program and a factory generating nodes with a newnode-definition.
# MyMainModule.mfrp
module MyMainModule in x : Int out a, b, c, d use Std
newnode a, b = MySubModule(x, x + 1)
newnode c, d = MySubModule(x, x + 2)
# MySubModule.mfrp
module MySubModule
in x : Int, y : Int
out sum, pro
use Std
node sum = x + y
node pro = x * y
$ emfrpi
emfrpi:001> type HW = HelloWorld
emfrpi:002> HelloWorld
HelloWorld : HW
emfrpi:003>
You can use the statement of data, func, type on emfrpi.
If you input an expression, emfrpi evaluates it and prints evaluated value with type on emfrpi console.
$ emfrpi
emfrpi:001> 1 + 2
2 : Int
$ emfrpi ./MyModule.mfrp
Definitions on MyModule.mfrp is now available on emfrpi console.
There are convenient interpreter's commands.
For example, :t shows func/data's type.
emfrpi:001> func myfst(a) = a of (x, _) -> x
emfrpi:002> :t myfst
func myfst : Tuple2[a2, a6] -> a2
emfrpi:003> data foo = 1.0
emfrpi:004> :t foo
data foo : Double
:assert-equals checks two expression's equality.
emfrpi001> :assert-equals 2, 1 + 1
emfrpi002> :assert-equals 3, 1 + 1
Assertion failed
Description: 3, 1 + 1
Type: Int
Expected: 3
Actual: 2
You can find the list of all available commands and their usages by typing :commands on emfrpi console.
From keyword # to end-of-line is treated as comment.
# This is comment.
data x = 1
By using the keyword #@, You can embed interpreter's commands on the programs as follows.
# MyMaterial.mfrp
material MyMaterial use Std
#@ :assert-equals 3, 1 ++ 1
func ++(a, b) = a + b
This special form of comment is called as embedded-command.
By using :assert-XXX as embedded-command, you can make automated tests.
You can execute embedded-commands by typing :exec-embedded-commands on emfrpi.
$ emfrpi ./MyMaterial.mfrp
emfrpi:001> :exec-embedded-commands
Assertion failed
Description: 3, 1 ++ 1
Type: Int
Expected: 3
Actual: 2
Embedded command on /home/sawaken/MyMaterial.mfrp:4
emfrpi:003>
As syntax sugar, line-continuation #- is provided.
#@ :assert-equals
#- 2, 1 + 1
#@ :assert-equals
#- 4, 2 + 2
Note that command : is last-executed command.
#@ :assert-equals 2, 1 + 1
#@ : 4, 2 + 2
This two-lines of commands have the same semantics with the former four-lines of commands.
Compile Emfrp's module file by emfrp or :compile command.
$ emfrp ./MyModule.mfrp
$ emfrpi ./MyModule.mfrp
emfrpi:001> :compile
MyModule.c, MyModule.h, MyModuleMain.c will be generated.
You need not (and should not) edit MyModule.c and MyModule.h.
All you need is open MyModuleMain.c and write IO codes in C-language.
Finally, compile these C-programs by C\C++ compiler.
$ cc MyModule.c MyModuleMain.c
$ ./a.out
Do almost the same above.
Write IO codes in MyModuleMain.c as suitable for your embedded system and compile the C-programs by C/C++ compiler for your embedded system.