FLoWS Basics


FLoWS is another syntax to use all the functions you already know from WarpLib. For easier migration, FLoWS can be executed from within WarpScript, and WarpScript can be executed from FLoWS.

Please read the basic concepts of FLoWS first.

To understand some error messages, you must know that FLoWS uses the same execution environment as WarpScript.

What is the language closest to FLoWS?

Similarity learning also works with human brain. So, we can try to compare to well known syntaxes...


stringA = "%F0%9F%96%96 That's " stringB = ' some "weird" multiline string with éÈÇ french %0A characters inside' return stringA + stringB // + can also concatenate strings

JavaScript, Python... lots of well known language allow double or single quotes. Multiline is straightforward. You don't need triple quotes or so.

  • The key difference in FLoWS is that there is no backslash escape, only URL percent encoding (using UTF-8). \n becomes %0A in FLoWS.
  • You won't need double escaping to write regular expressions.


return [1,2,3.14,{"age":26,"name":"Doe","preferences":["apple","peach","burgers"]}]

Using double quotes for strings, this is exactly JSON syntax!

Operators and assignement

a = 2 b = 3 c = a > b == false // legal, but unclear. We strongly suggest parentheses here. sum = 7 - 4 + 2 // left to right precedence return a,b,c,sum

Comparable to all well known languages... With the same pitfalls. Use parentheses to explicit your operations.

Specific to FLoWS: Some functions of WarpLib can return several results. In this case, you must use multiple assignment:

GTS = NEWGTS() ADDVALUE(GTS, 100, NaN, NaN, NaN, 10) ADDVALUE(GTS, 200, NaN, NaN, NaN, 9) ADDVALUE(GTS, 300, NaN, NaN, NaN, 8) ADDVALUE(GTS, 400, NaN, NaN, NaN, 7) ADDVALUE(GTS, 500, NaN, NaN, NaN, 6) (mu,sigma) = MUSIGMA(GTS, false) return mu,sigma > Multiple assignement cannot be used as python tuples: the right operand must be a function returning several results.

User defined functions

These are called macros in FLoWS.

ToRadianFunction = (angle) -> { anglerad = angle * pi() / 180.0 return anglerad } return @ToRadianFunction(90)

Declaration is like Java lambdas, with the same arrow type, and with an explicit return. The call of the macro with a @ prefix is WarpScript legacy.

Dear JavaScript developpers, beware of the arrow syntax, -> is not the same as => in FLoWS. Both are available, see below.

Variable scoping

anglerad = 0 ToRadianFunction = (angle) -> { anglerad = angle * pi() / 180.0 // this will not override line 1 return anglerad } return @ToRadianFunction(90), anglerad

As most programming languages, scope of variables is limited to the macro, as long as your macro is declared with ->.

If you use ->, the macro has its own namespace.

If you do want the macro to be able to change variables in the calling namespace, you can use a leaking macro, with => arrow (double arrow because variables will flow back to the caller), as in the example below:

anglerad = 0 ToRadianFunction = (angle) => { // beware JS devs, this is a leaking macro! anglerad = angle * pi() / 180.0 // anglerad declared on line 1 will be overriden! return anglerad } return @ToRadianFunction(90), anglerad

Control structures

FLoWS is a functional language, control structures are functions that take user defined functions as parameters. There is no IF, THEN or ELSE keyword, just an IFTE function:

gts = NEWGTS() RENAME(gts, "temperature") ADDVALUE(gts, NOW(), NaN, NaN, NaN, 42) status = IFTE( LASTTICK(gts) < ( NOW() - s(30)), // s(30) means 30 seconds. s is a WarpLib function. () -> { return "system dead" }, // a macro for then () -> { return "system alive" } // a macro for else ); return status // then change line 3 with an older timestamp, NOW() - s(40) for example.

Looking at the signature of IFTE, you can also write a macro as first argument, which should return a boolean.

The example above is typical functional programming. But "if then" cannot return any arguments, because nothing will be returned if the condition is not met. The following example illustrates what cannot be done:

gts = NEWGTS() RENAME(gts, "temperature") ADDVALUE(gts, NOW() - s(40), NaN, NaN, NaN, 42) status = "system alive" // IFT in this case cannot return any alernative when the condition is not met. // This is forbidden by FLoWS. status = IFT( LASTTICK(gts) < ( NOW() - s(30)), () -> { return "system dead" } ); return status

If you want to be able to do "if then" (IFT function), then you can choose to break the strictly functional programming and use leaking macros:

gts = NEWGTS() RENAME(gts, "temperature") ADDVALUE(gts, NOW() - s(40), NaN, NaN, NaN, 42) status = "system alive" IFT( LASTTICK(gts) < ( NOW() - s(30)), () => { status = "system dead" } // leaking macro than overrides status variable. // This is not functional anymore, but allowed. ); return status

Conclusion: control structures cannot be compared to well known languages except maybe Lisp, but its wellknownness is opinionated!

Semicolon, indentation, formatter

If you really want, you can add semicolons at the end of statements. Just like TypeScript. They have no effect on the grammar, and could contribute to a clearer code. Though there MUST be a statement before the semicolon, the construct a = 1;; is invalid since there is no statement before the second semicolon.

a=42;b=-1; c=42 d=-1 return (a==c),(b==d);

FLoWS formatter is not yet developped.

Understanding Error messages

This code has a bug. Click on the execute button below:

g = NEWGTS() g = RENAME('raw_temperature') g = ADDVALUE(g,NOW(),0.0,0.0,0,42) return g

Error example:

line #13: Exception at '=>FLOWS<=' in section [TOP] (FLOWS encountered an error. (Error at FLoWS L2:4-2:28 (Empty stack.)))
  • If present, ignore the first line number. line #13 refers to the internally generated WarpScript behind FLoWS. Most tools may hide this in the future.
  • Focus on the error message: L2:4-2:28 You know the error is at line 2, character 2.
  • Empty stack: This is the kind of error message we are trying to remove from WarpLib, but some remain. Keep in mind this is linked to the number of arguments of the function you called.

Remember to check your macro or function signature (number of arguments) each time you see "stack" in an error message.

Looking at the RENAME FLoWS signature in the doc, RENAME first argument must be the GTS you want to rename. Correct code is:

g = NEWGTS() g = RENAME(g,'raw_temperature') g = ADDVALUE(g,NOW(),0.0,0.0,0,42) g = RELABEL(g,{'unit':'°C'}) return g

FLoWS syntax errors

FLoWS is parsed as execution proceeds. Parsing errors are raised as soon as they are encountered. For example, the code below has two errors:

  • RENAME has less than two arguments: this is a runtime error, it will raise during execution of the function call, and can therefore be caught with TRY.
  • gf = 32 + g * 9/5 is a syntax error: 9/5 can not be parsed as a function
  • {'unit','°Fahrenheit'} is a syntax error: maps key value pairs should be separated with a colon, not a comma.
g = NEWGTS() TRY( () => { // the macro you want to TRY g = RENAME('raw_temperature') // error, first argument is missing g = ADDVALUE(g,NOW(),0.0,0.0,0,42) g = RELABEL(g,{'unit':'°C'}) // syntax error, unable to parse 9/5. You should add spaces, 9 / 5 . gf = 32 + g * 9/5 // syntax error, it should be key:value inside the map. gf = RELABEL(gf,{'unit','°Fahrenheit'}) }, () => { // the catch macro g=NEWGTS() gf=NEWGTS()  }, () => { } // the finally macro ) return g, gf

The syntax error will be raised before the RENAME error. Fix the syntax errors in the upper code, and try again: The RENAME runtime error should be caught, and the script returns empty GTS.

FLoWS precedence errors

g = NEWGTS() TRY( () => { g = RENAME(g, 'raw_temperature') g = ADDVALUE(g,NOW(),0.0,0.0,0,42) g = RELABEL(g,{'unit':'°C'}) gf = 32 + g * 9 / 5 gf = RELABEL(gf,{'unit':'°Fahrenheit'}) }, () => { g=NEWGTS() gf=NEWGTS()  }, () => { } ) return g, gf

Does 42°C equals to 133°F? No... It should be 107.6.

FLoWS implements a simple left to right precedence, there is no operator priority.

As you may have done in the C language in the past, the best practice is to explicit the priority with parentheses (remember MISRA C R.12.1). Moreover, the GTS type is LONG. The operations will be integer, not floating point ones: 9 divided by 8 equals 1 instead of 1.8. You must write 9.0 to force type conversion. The correct code is:

g = NEWGTS() TRY( () => { g = RENAME(g, 'raw_temperature') g = ADDVALUE(g,NOW(),0.0,0.0,0,42) g = RELABEL(g,{'unit':'°C'}) gf = 32 + (g * (9.0 / 5)) gf = RELABEL(gf,{'unit':'°Fahrenheit'}) }, () => { g=NEWGTS() gf=NEWGTS()  }, () => { } ) return g, gf


In FLoWS, there are a few primitives (number, strings, boolean) and lots of object: object types are the same as in WarpScript.

TypeUse Case
BITSETUsed for binary operation (decode a CAN bus frame for example)
BYTESBytes array, raw binary data storage
COUNTERAtomic long.
GEOSHAPEPolygon drawing on earth
MAPPERPiece of custom Warpscript
MATRIXMath matrix
PFONTFont object. Linked to Pgraphics.
PGRAPHICSPgraphics Image object. Could be rendered to png.
PIMAGEPgraphics layer object.
PSHAPEPgraphics shape object
REDUCERPiece of custom Warpscript
SETA collection of unique values
VECTORMath vector
VLISTVector, used for example to produce data bags in Pig

The bold ones can be represented in the JSON stack output.

The other objects types cannot be serialized in JSON: it means if you try to return them at the end of your FLoWS script, you will get null in the JSON object.

For example, run this code, and check the JSON tab output:

myImage = PGraphics(200, 300, '2D3') // new image, size 200x300, with antialiasing PtextSize(myImage, 20) Pfill(myImage, 0xffff0000) //red Ptext(myImage, 'Warp 10', 10, 20) return myImage, TYPEOF(myImage)

The PGRAPHICS objects cannot be serialized. They need to be converted into a PNG image, like in the code below. You can see the image in the image tab.

myImage = PGraphics(300, 200, '2D3') // new image, size 300x200, with antialiasing PtextSize(myImage, 20) Pfill(myImage, 0xffff0000) //red Ptext(myImage, 'Warp 10', 10, 20) return Pencode(myImage)

The conversion table is the same.

FLoWS make function arguments and results far more explicit than WarpScript. But as WarpLib is the same behind, some objects are mutable. For example, renaming a GTS does not change the GTS object:

g = NEWGTS() g = RENAME(g, 'raw_temperature') g = ADDVALUE(g,NOW(),0.0,0.0,0,42) g = RELABEL(g,{'unit':'°C'}) h = RELABEL(g,{'unit':'°Fahrenheit'}) // h is not a copy h = RENAME(h, 'US_temperature') return g, h

In this example, g and h refer to the same object. Lots of functions in WarpLib return the input object reference. FLoWS won't bother if you ignore the return value:

g = NEWGTS() g = RENAME(g, 'raw_temperature') g = ADDVALUE(g,NOW(),0.0,0.0,0,42) RELABEL(g,{'unit':'°C'}) return g

Next Step

FLoWS is a WarpScript extension, so you now need to undestand the link between FLoWS and WarpScript with the next tutorial.

Explore FLoWS tutorials and blog posts on the blog