ESP32forth

Download

STABLE RELEASE

ESP32forth-7.0.5.4.zip - Single .ino file ready for installation
Version: 7.0.5.4

Beta Release

ESP32forth-7.0.6.5.zip - Single .ino file ready for installation
Version: 7.0.6.5


Release Archive - Prior Releases

http://github.com/flagxor/eforth - Complete Unprocessed Source Code (under ueforth/)

License

NOTE: Although Esp32forth is licensed under Apache 2.0, please be aware that binaries built with the Arduino IDE (such as ESP32forth) include components and libraries under other additional licenses.
The ESP32 plugin for Arduino is also under a different license.
Additionally, the ESP32 toolchain used by Arduino, contains further components under other licenses.
Be sure to consult a lawyer before using for comercial purposes.


Copyright 2021 Bradley D. Nelson

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Install

Download the Arduino IDE for your platform.

Go to File > Preferences.
Under Additional Board Manager URLs enter: https://dl.espressif.com/dl/package_esp32_index.json

Choose these options under Tools.

Use

Initially ESP32forth can be interacted with over a serial port (over USB). You can do this from the Arduino IDE's Serial Monitor option. Be sure to config the serial port to: baud rate = 115200, data bits = 8, stop bits = 1, and parity = N.

On boot, ESP32forth configures PIN 2 (typically an LED) to be an output and brings it HIGH (lighting the LED after boot).

ESP32forth Features

ESP32forth Specific Words

Null Terminated Strings

As null terminated strings are used throughout C interfaces, their use is supported in Forth by way of several non-standard words with the convention of using Z/z to refer to such strings in names and stack comments.

Z" ( "string" -- z ) Creates a null terminated string on the heap
Z>S ( z -- a n ) Convert a null terminated string to a counted string
S>Z ( a n -- z ) Conver a counted string string to null terminated (copies string to heap)
Raw Strings

Raw strings are provided better support using a string for the duration of the current command, without consuming heap memory.

R" ( "string" -- a n ) Creates a temporary counted string
R| ( string| -- a n ) Creates a temporary counted string ending with |
Utilities
DUMP ( a n -- ) Dump a memory region
SEE ( "name" -- ) Attempt to decompile a word
VARIABLE ECHO -- Determines if commands are echoed
Vocabularies

ESP32forth uses a hybrid of Forth-79 and Forth-83 style vocabularies. By default vocabularies chain to the vocabulary in which they were defined, as in Forth-79. However, like Forth-83, ALSO can be used to add vocabularies to a vocabulary stack of which CONTEXT @ is the first item. The word ONLY clears the vocabulary stack, but as there is no separate ONLY vocabulary, it also sets CONTEXT to the FORTH vocabulary. The word SEALED modifies the most recently defined vocabulary such that it does not chain. Note, this must be done before words are added to it.

VOCABULARY ( "name" ) Create a vocabulary with the current vocabulary as parent
FORTH ( -- ) Make the FORTH vocabulary the context vocabulary
DEFINITIONS ( -- ) Make the context vocabulary the current vocabulary
VLIST ( -- ) List the words in the context vocabulary (not chains)
WORDS ( -- ) List the words in the context vocabulary (including chains)
TRANSFER ( "name" ) Move a word from its current dictionary to the current vocabulary
                    Useful for "hiding" built-in words
TRANSFER{ ..words.. }TRANSFER ( -- ) Transfer multiple words to the current vocabulary
ALSO ( -- ) Duplicate the vocabulary at the top of the vocabulary stack
ONLY ( -- ) Reset context stack to one item, the FORTH dictionary
            Non-standard, as there's no distinct ONLY vocabulary
ORDER ( -- ) Print the vocabulary search order
SEALED ( -- ) Alter the last vocabulary defined so it doesn't chain
Interpret Time Conditions

[I]F, [ELSE], and [THEN] can be used to selectively compile. Used in tandem with DEFINED? they can be used to handle the absence of modules gracefully. Nesting is supported.

DEFINED? ( "name" -- xt|0 ) Check if a word exists (works at compile time too).
[IF] ( f -- ) Conditionally interpret the text the follows.
[ELSE] ( -- ) Interpret time ELSE.
[THEN] ( -- ) Interpret time THEN.
Blocks
USE ( "name" -- ) Use "name" as the blockfile, e.g. USE /spiffs/foo
OPEN-BLOCKS ( a n -- ) Open a file as the block file
LOAD ( n -- ) Evaluate a block
THRU ( a b -- ) Load blocks a thru b
LIST ( n -- ) List a block
BLOCK ( n -- a ) Get a 1024 byte block
BUFFER ( n -- a ) Get a 1024 byte block without regard to old contents
UPDATE ( -- ) Mark the last block modified
FLUSH ( -- ) Save and empty all buffers
EMPTY-BUFFERS ( -- ) Empty all buffers
SAVE-BUFFERS ( -- ) Save all buffers
SCR ( -- a ) Pointer to last listed block
Block Editor
These words are available inside the EDITOR vocabulary. Note the block editor places newlines in the 63rd column of each line to make the block file readable in a text editor.
WIPE ( -- ) Blank out the current block
L ( -- ) List the current block
D ( n -- ) Delete a line in the current block
E ( n -- ) Clear a line in the current block
R ( n "text" -- ) Replace a line in the current block
A ( n "text" -- ) Add (insert) a line in the current block
P ( -- ) Move to the previous block
N ( -- ) Move to the next block
Utilities
SEE ( "name" -- ) Attempt to decompile a word
ECHO ( -- a ) -- Address of flag that determines if commands are echoed
Floating-Point

(Requires v7.0.6.5+)

Single precision floating-point support is available as a work in progress. While initially left out in the name of minimalism, hardware support for floating-point argues some advantages to limited support.

Floating point is kept on a separate stack.

NOTE: Tasks currently don't correctly support floating point. A single floating point stack is shared by all tasks.

FLOAT OPCODES
-------------
DOFLIT ( --- ) Puts a float from the next cell onto float stack.
FP@ ( -- a )
FP! ( a -- )
SF@ ( a -- r ) Single precision load
SF! ( r a -- ) Single precision store
FDUP ( r -- r r )
FNIP ( ra rb -- rb )
FDROP ( r -- )
FOVER ( ra rb -- ra rb ra )
FSWAP ( ra rb -- rb ra )
F0< ( r -- f )
F0= ( r -- f )
F+ ( r r -- r )
F- ( r r -- r )
F* ( r r -- r )
F/ ( r r -- r )
1/F ( r -- r )
S>F ( n -- r )
F>S ( r -- n )

HIGH LEVEL
----------
F= ( r r -- f )
F< ( r r -- f )
F> ( r r -- f )
F<= ( r r -- f )
F>= ( r r -- f )
F<> ( r r -- f )

SFLOAT ( -- 4 )
SFLOATS ( n -- n*4 )
SFLOAT+ ( a -- a+4 )
SF, ( r -- )

AFLITERAL ( r -- )
FLITERAL ( r -- ) IMMEDIATE

FCONSTANT ( r "name" )
FVARAIABLE ( "name" )

PI ( -- r )

FSQRT ( r r -- r )
Locals

Locals allow named word local parameters and values.

Syntax:
  { local1 local2 .. -- comment }
or
  { local1 local2 .. }

Locals are ordered to match the stack, examples:
: 2OVER { a b c d } a b c d a b ;
: MAX { a b -- biggest } a b < IF b ELSE a THEN ;

( Equivalent with DO and FOR )
: POW2 { n } 1 { s } n FOR AFT s 2* to s THEN NEXT s ;
: POW2 { n } 1 { s } n 0 DO s 2* to s LOOP s ;

Capabilities and limitations:

Dictionary Images and Startup

WARNING: Danger ahead.
Snapshotting the dictionary may not be stable across reinstallations of the C build of Forth.

A collection of non-standard words is provided that allow snapshotting the dictionary and restoring it at startup, with a start word.

SAVE ( "name" -- ) Saves a snapshot of the current dictionary to a file.
RESTORE ( "name" -- ) Restore a snapshot from a file.
REMEMBER ( -- ) Save a snapshot to the default file
                (./myforth or /spiffs/myforth on ESP32).
STARTUP: ( "name" -- ) Save a snapshot to the default file arranging for
                       "name" to be run on startup.
REVIVE ( -- ) Restore the default filename.
RESET ( -- ) Delete the default filename.

Here's an example usage:

: welcome ." Hello!" cr 100 0 do i . loop cr ;
startup: welcome
bye

( Next boot will run a custom startup message )

reset

( Reset removes the custom message )

The INTERNALS vocabulary has some additional words for more control.

SAVE-NAME ( a n -- ) Save a snapshot if the current vocabulary to a file.
RESTORE-NAME ( a n -- ) Restore a snapshot from a file.
'COLD ( -- a ) Address of the word that will be run on startup.
REMEMBER-FILENAME ( -- a n ) Deferred word specifying the platform specific
                             default snapshot filename.
Web Interface
These words are inside the web-interface vocabulary.
server ( port -- ) Start web UI on port (used to implement webui).
HTTP Daemon

Requires v7.0.6+

These words are inside the httpd vocabulary.
server ( port -- ) Start an httpd on port.
handleClient ( -- ) Get next request.
path ( -- a n ) Request path, e.g. /foo
method ( -- a n ) Request method, e.g. GET
header ( a n -- a n ) Contents of header (or empty string).
body ( -- a n ) Rquest body.
Telnet Daemon
These words are inside the web-interface vocabulary.
server ( port -- ) Start telnet daemon on port.

ESP32forth Bindings

Because Arduino builds a statically linked image for flashing into ESP32 devices, all C function bindings need to be explicitly added. This is the current collection. Typically to reduce confusion, function names have be preserved even through verbose. In popular cases a shorted higher level name is provided.

See template.ino.

Allocation
These words are inside the internals vocabulary.
MALLOC ( n -- a | 0 )   System malloc
SYSFREE ( a -- )   System free
REALLOC ( a n -- a | 0 )   System realloc
Serial
These words are inside the Serial vocabulary.
Serial.begin ( baud -- )   Start serial port
Serial.end ( -- )   End serial port
Serial.available ( -- f )   Is serial data available
Serial.readBytes ( a n -- n )   Read serial bytes, return number gotten
Serial.write ( a n -- n )   Write serial bytes
Serial.flush ( -- )   Flush serial buffer
Serial Bluetooth
These words are inside the bluetooth vocabulary.
SerialBT.new ( -- bt )   Allocate new BT object
SerialBT.delete ( bt -- )   Free BT object
SerialBT.begin ( localname ismaster bt -- f )
SerialBT.end ( bt -- )
SerialBT.available ( bt -- f )
SerialBT.readBytes ( a n bt -- n )
SerialBT.write ( a n bt -- n )
SerialBT.flush ( bt -- )
SerialBT.hasClient ( bt -- f )
SerialBT.enableSSP ( bt -- )
SerialBT.setPin ( z bt -- f )
SerialBT.unpairDevice ( addr bt -- f )
SerialBT.connect ( remotename bt -- f )
SerialBT.connectAddr ( addr bt -- f )
SerialBT.disconnect ( bt -- f )
SerialBT.connected ( timeout bt -- f )
SerialBT.isReady ( checkMaster timeout -- f )   Default checkMaster=false, timeout=0
Bluetooth
These words are inside the bluetooth vocabulary.
esp_bt_dev_get_address ( -- a ) addr of 6 byte mac address
GPIO
pinMode ( pin mode -- )   Set GPIO pin mode
digitalWrite ( pin value -- )   Set GPIO pin state
analogRead ( pin -- n )   Analog read from 0-4095
pulseIn ( pin value usec -- usec/0 )  Wait for a pulse
dacWrite ( pin 0-255 -- )  Write to DAC (pin 25, 26)
ledc
These words are inside the ledc vocabulary.
ledcSetup ( channel freq resolution -- freq )
ledcAttachPin ( pin channel -- )
ledcDetachPin ( pin -- )
ledcRead ( channel -- n )
ledcReadFreq ( channel -- freq )   Get frequency (x 1,000,000)
ledcWrite ( channel duty -- )
ledcWriteTone ( channel freq )   Write tone frequency (x 1000)
ledcWriteNote ( channel note octave -- freq )
Short GPIO Names
pin ( value pin# -- )   Set GPIO pin value
adc ( pin# -- n )   Analog read pin, result 0-1023
System
MS ( n -- ) Pause for some number of milliseconds.
MS-TICKS ( -- n ) Time since start in milliseconds.
TERMINATE ( n -- ) Call system exit.
Files
R/O ( -- mode )
R/W ( -- mode )
W/O ( -- mode )
BIN ( mode -- mode )
CLOSE-FILE ( fh -- ior )
OPEN-FILE ( a n mode -- fh ior )
CREATE-FILE ( a n mode -- fh ior )
DELETE-FILE ( a n -- ior )
WRITE-FILE ( a n fh -- ior )
READ-FILE ( a n fh -- n ior )
FILE-POSITION ( fh -- n ior )
REPOSITION-FILE ( n fh -- ior )
FILE-SIZE ( fh -- n ior )
WiFi
These words are inside the WiFi vocabulary.
WiFi.config ( ip dns gateway subnet -- )   Packaged a.b.c.d little-endian
Wifi.begin ( ssid-z password-z -- )
Wifi.disconnect ( -- )
WiFi.status ( -- n )
WiFi.macAddress ( a -- )
WiFi.localIP ( -- ip )
WiFi.mode ( mode -- ) WIFI_MODE_NULL WIFI_MODE_STA WIFI_MODE_AP WIFI_MODE_APSTA
WiFi.setTxPower ( powerx4 -- )   Set power x4
WiFi.getTxPower ( -- powerx4 )   Get power x4
mDNS
MDNS.begin ( name-z -- )   Start multicast dns
SPIFFS
These words are inside the SPIFFS vocabulary.
SPIFFS.begin ( format-on-fail path-z max-files -- f )
SPIFFS.end ( -- )
SPIFFS.format ( -- f )
SPIFFS.totalBytes ( -- n )
SPIFFS.usedBytes ( -- n )
WebServer
These words are inside the WebServer vocabulary.
WebServer.new ( port -- ws )   Allocate new webserver object
WebServer.delete ( ws -- )   Delete webserver object
WebServer.begin ( port ws -- )
WebServer.stop ( ws -- )
WebServer.on ( path-z xt ws -- ) Set up a web path handle callback
WebServer.handleClient ( ws -- )   Handle one client request
WebServer.hasArg ( z ws -- f )   By name
WebServer.arg ( z ws -- z )   By name
WebServer.argi ( n ws -- z )   By index
WebServer.argName ( n ws -- z)   By index
WebServer.args ( ws -- n )   Number of args
WebServer.setContentLength ( n ws -- )
WebServer.sendHeader ( name-z value-z fist ws -- )
WebServer.send ( code mimetype data ws -- )
WebServer.sendContent ( z ws -- )
WebServer.method ( ws -- n )   GET / POST etc.
Wire
These words are inside the Wire vocabulary.
Wire.begin ( -- f )
Wire.setPins ( sda scl -- f )
Wire.setClock ( frequency -- )
Wire.getClock ( -- frequency )
Wire.setTimeout ( ms -- ) Default is 50ms
Wire.getTimeout ( -- ms )
Wire.lastError ( -- n )
Wire.getErrorText ( n -- z )
Wire.beginTransmission ( n -- )
Wire.endTransmission ( sendstop -- f ) Default is true
Wire.requestFrom ( address quantity sendstop -- n )
Wire.writeTransmission ( addr a n sendstop -- err )
Wire.readTransmission ( addr a n sendstop acount -- err )
Wire.write ( a n -- n )
Wire.available ( -- f )
Wire.read ( -- ch )
Wire.peek ( -- ch )
Wire.busy ( -- f )
Wire.flush ( -- )
Camera
These words are inside the camera vocabulary.
esp_camera_init ( config -- f )
esp_camera_deinit ( -- f )
esp_camera_fb_get ( -- fb )
esp_camera_fb_return ( fb -- )
esp_camera_sensor_get ( -- sensor )
Camera Server

Requires v7.0.6+

WIP Prototype - Not yet stable
These words are inside the camera-server vocabulary.
server ( port -- ) Start an image server at port,
                   e.g. http://IP/image will produce an image
SD_MMC
These words are inside the SD_MMC vocabulary.
SD_MMC.begin ( mount mode1bit )   default mode1bit=false
SD_MMC.end ( -- )
SD_MMC.cardType ( -- n )
SD_MMC.totalBytes ( -- n )
SD_MMC.usedBytes ( -- n )
Interrupts
These words are inside the INTERRUPTS vocabulary.

High Level words:

pinchange ( xt pin -- ) Call xt when pin changes.

Example:

17 input pinMode
: test ." pinvalue: " 17 digitalRead . cr ;
' test 17 pinchange

Low Level words:

ESP_INTR_FLAG_DEFAULT -- Default handler allows per pin routing

Various triggers:
  #GPIO_INTR_DISABLE
  #GPIO_INTR_POSEDGE
  #GPIO_INTR_NEGEDGE
  #GPIO_INTR_ANYEDGE
  #GPIO_INTR_LOW_LEVEL
  #GPIO_INTR_HIGH_LEVEL

gpio_config ( gpio_config_t* -- 0/err )
gpio_reset_pin ( pin -- 0/err )

gpio_set_intr_type ( pin type -- 0/err )

gpio_intr_enable ( pin -- 0/err )
gpio_intr_disable ( pin -- 0/err )

gpio_set_level ( pin level -- 0/err )
gpio_get_level ( pin -- level )

gpio_set_direction ( pin mode -- 0/err )

gpio_set_pull_mode ( pin mode -- 0/err )

gpio_wakeup_enable ( pin type -- 0/err )
gpio_wakeup_disable ( pin -- 0/err )

gpio_pullup_en ( pin -- 0/err )
gpio_pullup_dis ( pin -- 0/err )
gpio_pulldown_en ( pin -- 0/err )
gpio_pulldown_dis ( pin -- 0/err )
gpio_hold_en ( pin -- 0/err )
gpio_hold_dis ( pin -- 0/err )

gpio_deep_sleep_hold_en ( -- )
gpio_deep_sleep_hold_dis ( -- )

gpio_install_isr_service ( a -- ) Typically ESP_INTR_FLAG_DEFAULT
gpio_uninstall_isr_service

gpio_isr_handler_add ( pin xt arg -- 0/err )
gpio_isr_handler_remove ( pin -- 0/err )

gpio_set_drive_capability ( pin cap -- 0/err )
gpio_get_drive_capability ( pin cap* -- 0/err )

esp_intr_alloc ( source flags xt args handle* -- 0/err )
esp_intr_free ( handle -- 0/err )

timer_isr_register ( group timer xt arg ret -- 0/err )
Tasks
These words are inside the TASKS vocabulary.
PAUSE ( -- ) Yield to other tasks.
MS ( n -- ) Pause for some number of milliseconds (yields to other tasks).
TASK ( xt dsz rsz "name" -- ) Create a new task with dsz size data stack
                              and rsz size return stack running xt.
START-TASK ( task -- ) Activate a task.
.TASKS ( -- ) List running tasks.

Example:
tasks
: hi   begin ." Time is: " ms-ticks . cr 1000 ms again ;
' hi 100 100 task my-counter
my-counter start-task 
Timers
These words are inside the TIMERS vocabulary.
NOTE: These are low level ESP32 timers. For a periodic background operation, you'll probably want to use TASKS.

High Level words:

( timer t = 0-3 )

interval ( xt usec t -- ) Setup timer t to call xt each after usec
rerun ( t -- ) Rerun timer t triggering

Example:
timers
: hi   ." hi" cr   0 rerun ;
' hi 1000000 0 interval ( run hi every second )

Medium Level words:

timer@ ( t -- lo hi )
timer! ( lo hi t -- )
alarm ( t -- a )
enable! ( f t -- ) Timer enable/disable
increase! ( f t -- ) Timer increasing/decreasing
divider! ( n t -- ) Timer divider 2 - 65535
edgeint! ( f t -- ) Edge trigger
levelint! ( f t -- ) Level trigger
alarm-enable! ( f t -- ) Alarm enable
alarm-enable@ ( t -- f ) Alarm enabled?
onalarm ( xt t -- ) Set callback

Low Level words:

( group n = 0/1, timer x = 0/1, watchdog m = 0-5 )

TIMGn_TxCONFIG_REG ( n x -- a )
TIMGn_TxLOHI_REG ( n x -- a )
TIMGn_TxUPDATE_REG ( n x -- a )
TIMGn_TxALARMLOHI_REG ( n x -- a )
TIMGn_TxLOADLOHI_REG ( n x -- a )
TIMGn_TxLOAD_REG ( n x -- a )

TIMGn_Tx_WDTCONFIGm_REG ( n m -- a )
TIMGn_Tx_WDTFEED_REG ( n -- a )
TIMGn_Tx_WDTWPROTECT_REG ( n -- a )

TIMGn_RTCCALICFG_REG ( n -- a )
TIMGn_RTCCALICFG1_REG ( n -- a )

TIMGn_Tx_INT_ENA_REG ( n -- a )
TIMGn_Tx_INT_RAW_REG ( n -- a )
TIMGn_Tx_INT_ST_REG ( n -- a )
TIMGn_Tx_INT_CLR_REG ( n -- a )
RTOS
These words are inside the RTOS vocabulary.
xPortGetCoreID ( -- n )
xTaskCreatePinnedToCore ( fn name stack-depth params priority taskout coreid -- )
vTaskDelete ( task ) -- ) 

ESP32 WebUI

A terminal over the web can be activated. Contact at port printed or via mDNS http://forth/.

webui ( network-z password-z -- ) login and start webui
login ( network-z password-z -- ) login to wifi only

Usage:

z" NETWORK-NAME" z" PASSWORD" webui

See web_interface.fs.

Autoexec.fs

NOTE: This section describes one mechanism for running code at startup. See this for an alternate option.

The system will automatically attempt to mount SPIFFS filesystem at /spiffs. It will then at start attempt to load /spiffs/autoexec.fs

One way this feature can be used to configure the Web UI to start by default. When doing this, be sure to test your Web UI settings work well first.

r| z" NETWORK-NAME" z" PASSWORD" webui | s" /spiffs/autoexec.fs" dump-file

To remove a previously configured autoexec.fs you will need to be able to reboot in a mode with Forth. One way to do this is to search for the line in the .ino file that refers to autoexec.fs and replace it with a different name. Then run the following:

s" /spiffs/autoexec.fs" delete-file

See autoboot.fs.

Adding Words

Words can be added based on C functions by placing a file named userwords.h alongside the .ino file for ESP32forth.
(Requires v7.0.6.4+)
Before v7.0.6.4, user words required editing the .ino file.

Because of the use of X-Macros words can be added in as little as one line. Locate the macro called PLATFORM_OPCODE_LIST and add your words there.

NOTE: Be careful to end each line with a \ so the macros will chain correctly.

To add a word containing only letters, numbers, and underscore you can use Y.

---- userwords.h ----
#define USER_WORDS \
  Y(MY_WORD123, c_function_to_call()) \

If your word name contains other characters, instead use X. You will need to make up an alternate name for the middle parameter. It must contain only letters, numbers, and underscore. The name is not used anywhere else, but must be unique.

---- userwords.h ----
#define USER_WORDS \
  X("myword!", MY_WORD_BANG, c_function_to_call()) \

Values from the data stack can be accessed via the variables tos (Top of Stack) and sp (Pointer to the rest of the stack). The stack has the data type cell_t.

You can push a value of any type to the stack with the macro PUSH:

---- userwords.h ----
#define USER_WORDS \
  Y(MY_WORD, PUSH calculate_magic_value()) \

To simplify calling C functions, you can also refer to elements on the stack with the types positional names:

n10 n9 n8 n7 n6 n5 n4 n3 n2 n1 n0 - Access stack as cell_t integer values
                   c4 c3 c2 c1 c0 - Access stack as char* values
                   b4 b3 b2 b1 b0 - Access stack as uint8_t* byte values
             a6 a5 a4 a3 a2 a1 a0 - Access stack as void* values

Examples:

---- userwords.h ----
void send_message(const char *message, int code);
...
#define USER_WORDS \
  X("send-message", SEND_MESSAGE, send_message(c1, n0)) \

You can always replace the top item on the stack with SET:

---- userwords.h ----
#define USER_WORDS \
  Y(DECODE, SET decode_func(n0)) \

You can drop elements from the stack with DROP or DROPn(number). You can nip elements from below top of the stack with NIP or NIPn(number). Be aware that like PUSH this will cause stack indices to change.

---- userwords.h ----
int my_cool_function(char *foo, int bar, uint8_t *baz);
...
#define USER_WORDS \
  Y(MY_FUNC, n0 = my_cool_function(c2, n1, b0); NIPn(2)) \

Multiple C statements can be included in the code area of a word, but care must be taken to generally avoid {}s. If you find you need nesting, a separate function is recommended.

New variables can be declared in each word and are scoped to that word.

---- userwords.h ----
#define USER_WORDS \
  Y(MY_WORD, cell_t foo = n0; DROP; char *x = c0; DROP; \
             PUSH my_func(foo, x)) \