Loading

WarpScript best practices

WarpScript is a concatenative stack based language. Once you've read the basic concepts, you will get used to manage the stack state in your brain, while writing WarpScript. Without any kind of obfuscation, you can turn your code to brainfuck really easily...

Look carefully at the code below. What is this doing ?

{} 1 10 <% { SWAP DUP TOSTRING SWAP 1 SWAP 1 SWAP <% * %> FOR } APPEND %> FOR

Well, it outputs a map of numbers and their factorials, without any variable use.

And what does this WarpScript do?

{ 1 1 2 10 <% DUP 3 PICK * %> FOR }

The same thing with far less warpscript operations.

The common points of both scripts:

  • You will not be able to understand them one week after writing them
  • They seem to be highly optimized
  • There is not a single comment

Do not over optimize

Warp 10 is written in Java. The JVM will optimize your code, especially if you run it several times (the Just In Time compiler will turn it in native machine code).

Do not try to over optimize your WarpScript from start. The best practice is to do something functional, easy to read a few days later (comments help).

You will optimize later the loops called several hundred of millions time.

If you do have performance problem, contact the Warp 10 core team.

Keep your stack clean

Mental visualization of the stack state when you are doing WarpScript is a gym you will get used pretty fast. But it is a waste of brain time to have to do the job each time you open a 200 lines WarpScript program.

To simplify, divide your code into blocks. Between each block, write a comment with the stack content, if any.

NEWGTS 'mygts' RENAME NOW NOW 5 m + <% 1 m + %> <% NaN NaN NaN 4 PICK ADDVALUE %> FORSTEP NEWGTS 'random' RENAME 1 10 <% NaN NaN NaN RAND ADDVALUE %> FOR SWAP { 'linear' 'yes' } SETATTRIBUTES

Code review:

On line 9, it would be great to store the resulting GTS in a variable, instead of leaving it on the stack and SWAP it later. On line 7, using PICK is not a good idea for readability. A few comments will help.

NEWGTS 'mygts' RENAME NOW NOW 5 m + <% 1 m + %> //one point every minutes before now and now +5 minutes <% 'i' STORE $i NaN NaN NaN $i ADDVALUE //value = timestamp %> FORSTEP 'linearGTS' STORE //stack empty NEWGTS 'random' RENAME 1 10 <% NaN NaN NaN RAND ADDVALUE %> FOR 'randomGTS' STORE //random value from timestamp 1 to 10 //stack empty $linearGTS { 'linear' 'yes' } SETATTRIBUTES DROP //stack empty //push results $randomGTS $linearGTS

Use well documented macros

Writing custom functions is a basic reflex for every language. In WarpScript, you will do inline or server side macros.

You can document them with a few comments, or you can use the INFO function. INFO will allow you to later generate a documentation from within WarpScript.

//factorial macro. take a number on the stack, push its factorial <% 'input' STORE 1 1 $input <% * %> FOR %> 'factorial' STORE //build a map with key from 1 to 10 and value = key! {} 'result' STORE 1 10 <% 'key' STORE $result $key @factorial $key PUT DROP //remove the map let by PUT %> FOR //push the result on the stack $result

Code review:

Well, that's far more readable. It could be optimized later if needed. Between each block, the stack is empty You might do something to clearly separate arguments of the PUT function. Maybe adding spaces to understand $key @factorial is the value expected by PUT, for example: $result   $key @factorial   $key PUT

VSCode plugin allows you to develop your server side macros easily. See documentation here.

Macro skeleton

In VSCode, when you type "macro", a snippet will output the typical WarpScript macro skeleton:

<%
  {
    'name' '<macro name>'
    'desc'
      <'
Macro description
      '>
    'sig' [ [ [ 'input:TYPE' ] [ 'output:TYPE' ] ] ] // Signature
    'params' {
      // Signature params description
      'input' 'TODO'
      'output' 'TODO'
    }
    'examples' [
      <'
Warpscript usage example
      '>
    ]
  } INFO
  SAVE 'context' STORE
    // Code of the actual macro
  $context RESTORE
%>
'macro' STORE
// Unit tests
$macro

The content of /macros/maths/factorial.mc2 could be:

<% { 'name' 'factorial' 'desc' <' This macro compute (input)! Beware of the 64bits limit. Limitations: if input is zero or negative, output will be 1. '> 'sig' [ [ [ 'input:LONG' ] [ 'output:LONG' ] ] ] // Signature 'params' { // Signature params description 'input' 'x' 'output' 'x! (factorial of x)' } 'examples' [ <' 10 @factorial '> ] } INFO SAVE 'context' STORE 'input' STORE 1 1 $input <% * %> FOR $context RESTORE %> 'factorial' STORE //unit test 1 10 @factorial 3628800 == ASSERT $factorial

Unit tests will be called each time the macro is reloaded by Warp 10. Not every time the macro is called. When you execute a macro file itself, the output stack should contain only one element, and this element must be a MACRO (check that it is the case with TYPEOF).

Your unit tests must leave the stack clean. In a large project, you will end up with lots of macros. INFO and INFOMODE will allow you to generate automatic documentation.

  • Do not optimize too soon, prefer a clear style and keep your stack clean between code blocks.
  • Use variables.
  • Stack gym is brain consuming. It is absolutely necessary when you write WarpScript, but it should not be when you read your WarpScript a few week later.
  • Custom function = macro.
  • Macro documentation generation is possible.
  • Include unit tests in your macros.
  • There is no style rules for WarpScript yet. Craft your own!