Skip to content
Kensuke Sawada edited this page Jan 30, 2016 · 5 revisions

Emfrp Documentation

This page is the easy documentation for Emfrp language and Emfrp compiler.

Top level definitions

in/out declaration

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

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

node definition

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)

newnode definition

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).

type definition

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)

func definition

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.

data definition

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

Expression

Function call

<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

Operator

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).)

Pattern match

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).

Let-expression and If-expression

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

PrimType and PrimFunc

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.

Module and Material

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

Emfrp Interpreter

Hello World?

$ 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

Load Module/Material

$ emfrpi ./MyModule.mfrp

Definitions on MyModule.mfrp is now available on emfrpi console.

Commands

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.

Comment and Embedded-command

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.

Emfrp Compiler

Compile & Execute on PC

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

Compile & Execute on microcomputer

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.