577 lines
15 KiB
HTML
577 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>ESP32forth</title>
|
|
<link rel="stylesheet" href="static/eforth.css">
|
|
</head>
|
|
<body>
|
|
<div>
|
|
<h1>ESP32forth</h1>
|
|
|
|
{{MENU}}
|
|
|
|
<h2>Download</h2>
|
|
|
|
<p>
|
|
<a href="downloads/ESP32forth.zip">ESP32forth.zip</a>
|
|
- Single .ino file ready for installation<br/>
|
|
<i>Version: {{VERSION}} Rev: {{REVISION}}</i>
|
|
</p>
|
|
|
|
<p>
|
|
<a href="https://github.com/flagxor/eforth" target="_blank">http://github.com/flagxor/eforth</a>
|
|
- Complete Source Code (under ueforth/)
|
|
</p>
|
|
|
|
<h2>Install</h2>
|
|
|
|
<p>
|
|
Download the <a href="https://www.arduino.cc/en/software">Arduino IDE</a> for your platform.
|
|
</p>
|
|
|
|
<p>
|
|
Go to <b>File > Preferences</b>.<br/>
|
|
Under <b>Additional Board Manager URLs</b> enter: <code>https://dl.espressif.com/dl/package_esp32_index.json</code>
|
|
</p>
|
|
|
|
<p>
|
|
Choose these options under Tools.
|
|
|
|
<ul>
|
|
<li><b>ESP32 Dev Module or similar (may work better with your particular board)</b>
|
|
<ul>
|
|
<li><b>Board:</b> ESP32 Dev Module</li>
|
|
<li><b>Partition Scheme:</b> No OTA (2M APP, 2M SPIFFS) <b>← Non-default</b></li>
|
|
<small>
|
|
<li><b>Upload Speed:</b> 921600</li>
|
|
<li><b>CPU Frequency:</b> 240MHz</li>
|
|
<li><b>Flash Frequency:</b> 80MHz</li>
|
|
<li><b>Flash Mode:</b> QIO</li>
|
|
<li><b>Flash Size:</b> 4MB</li>
|
|
<li><b>Core Debug Level:</b> None</li>
|
|
<li><b>PSRAM:</b> Disabled</li>
|
|
</small>
|
|
</ul>
|
|
</li>
|
|
<li><b>ESP32-CAM</b>
|
|
<ul>
|
|
<li><b>Board:</b> AI Thinker ESP32-CAM</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</p>
|
|
|
|
<h2>Use</h2>
|
|
|
|
<p>
|
|
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: <code>baud rate = 115200, data bits = 8, stop bits = 1, and parity = N</code>.
|
|
</p>
|
|
|
|
<p>
|
|
On boot, ESP32forth configures PIN 2 (typically an LED) to be an output and brings it HIGH (lighting the LED after boot).
|
|
</p>
|
|
|
|
<h2>ESP32forth Features</h2>
|
|
|
|
<h3>ESP32forth Specific Words</h3>
|
|
|
|
{{COMMON}}
|
|
|
|
<h3>ESP32forth Bindings</h3>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<p>
|
|
See <a href="https://github.com/flagxor/eforth/blob/main/ueforth/esp32/template.ino">template.ino</a>.
|
|
</p>
|
|
|
|
<h5>Allocation</h5>
|
|
These words are inside the <code>internals</code> vocabulary.
|
|
<pre>
|
|
MALLOC ( n -- a | 0 ) System malloc
|
|
SYSFREE ( a -- ) System free
|
|
REALLOC ( a n -- a | 0 ) System realloc
|
|
</pre>
|
|
|
|
<h5>Serial</h5>
|
|
These words are inside the <code>Serial</code> vocabulary.
|
|
<pre>
|
|
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
|
|
</pre>
|
|
|
|
<h5>Serial Bluetooth</h5>
|
|
These words are inside the <code>bluetooth</code> vocabulary.
|
|
<pre>
|
|
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
|
|
</pre>
|
|
|
|
<h5>Bluetooth</h5>
|
|
These words are inside the <code>bluetooth</code> vocabulary.
|
|
<pre>
|
|
esp_bt_dev_get_address ( -- a ) addr of 6 byte mac address
|
|
</pre>
|
|
|
|
<h5>GPIO</h5>
|
|
<pre>
|
|
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)
|
|
</pre>
|
|
|
|
<h5>ledc</h5>
|
|
These words are inside the <code>ledc</code> vocabulary.
|
|
<pre>
|
|
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 )
|
|
</pre>
|
|
|
|
<h5>Short GPIO Names</h5>
|
|
<pre>
|
|
pin ( value pin# -- ) Set GPIO pin value
|
|
adc ( pin# -- n ) Analog read pin, result 0-1023
|
|
</pre>
|
|
|
|
<h5>System</h5>
|
|
<pre>
|
|
MS ( n -- )
|
|
TERMINATE ( n -- ) Call system exit
|
|
</pre>
|
|
|
|
<h5>Files</h5>
|
|
<pre>
|
|
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 )
|
|
</pre>
|
|
|
|
<h5>WiFi</h5>
|
|
These words are inside the <code>WiFi</code> vocabulary.
|
|
<pre>
|
|
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
|
|
</pre>
|
|
|
|
<h5>mDNS</h5>
|
|
<pre>
|
|
MDNS.begin ( name-z -- ) Start multicast dns
|
|
</pre>
|
|
|
|
<h5>SPIFFS</h5>
|
|
These words are inside the <code>SPIFFS</code> vocabulary.
|
|
<pre>
|
|
SPIFFS.begin ( format-on-fail path-z max-files -- f )
|
|
SPIFFS.end ( -- )
|
|
SPIFFS.format ( -- f )
|
|
SPIFFS.totalBytes ( -- n )
|
|
SPIFFS.usedBytes ( -- n )
|
|
</pre>
|
|
|
|
<h5>WebServer</h5>
|
|
These words are inside the <code>WebServer</code> vocabulary.
|
|
<pre>
|
|
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.
|
|
</pre>
|
|
|
|
<h5>Wire</h5>
|
|
These words are inside the <code>Wire</code> vocabulary.
|
|
<pre>
|
|
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 ( -- )
|
|
</pre>
|
|
|
|
<h5>Camera</h5>
|
|
These words are inside the <code>camera</code> vocabulary.
|
|
<pre>
|
|
esp_camera_init ( config -- f )
|
|
esp_camera_deinit ( -- f )
|
|
esp_camera_fb_get ( -- fb )
|
|
esp_camera_fb_return ( fb -- )
|
|
esp_camera_sensor_get ( -- sensor )
|
|
</pre>
|
|
|
|
<h5>SD_MMC</h5>
|
|
These words are inside the <code>SD_MMC</code> vocabulary.
|
|
<pre>
|
|
SD_MMC.begin ( mount mode1bit ) default mode1bit=false
|
|
SD_MMC.end ( -- )
|
|
SD_MMC.cardType ( -- n )
|
|
SD_MMC.totalBytes ( -- n )
|
|
SD_MMC.usedBytes ( -- n )
|
|
</pre>
|
|
|
|
<h5 id="interrupts">Interrupts</h5>
|
|
These words are inside the <code>INTERRUPTS</code> vocabulary.
|
|
|
|
<p>High Level words:</p>
|
|
<pre>
|
|
pinchange ( xt pin -- ) Call xt when pin changes.
|
|
</pre>
|
|
|
|
<p>Example:</p>
|
|
|
|
<pre>
|
|
17 input pinMode
|
|
: test ." pinvalue: " 17 digitalRead . cr ;
|
|
' test 17 pinchange
|
|
</pre>
|
|
|
|
<p>Low Level words:</p>
|
|
<pre>
|
|
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 )
|
|
</pre>
|
|
|
|
<h5 id="timers">Timers</h5>
|
|
These words are inside the <code>TIMERS</code> vocabulary.
|
|
|
|
<p>High Level words:</p>
|
|
<pre>
|
|
( 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 )
|
|
</pre>
|
|
|
|
<p>Medium Level words:</p>
|
|
<pre>
|
|
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
|
|
</pre>
|
|
|
|
<p>Low Level words:</p>
|
|
<pre>
|
|
( 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 )
|
|
</pre>
|
|
|
|
<h5>RTOS</h5>
|
|
These words are inside the <code>RTOS</code> vocabulary.
|
|
<pre>
|
|
xPortGetCoreID ( -- n )
|
|
xTaskCreatePinnedToCore ( fn name stack-depth params priority taskout coreid -- )
|
|
vTaskDelete ( task ) -- )
|
|
</pre>
|
|
|
|
<h3 id="webui">ESP32 WebUI</h3>
|
|
|
|
<p>
|
|
A terminal over the web can be activated.
|
|
Contact at port printed or via mDNS <a href="http://forth/">http://forth/</a>.
|
|
</p>
|
|
|
|
<pre>
|
|
webui ( network-z password-z -- )
|
|
</pre>
|
|
|
|
<p>Usage:</p>
|
|
|
|
<pre>
|
|
z" NETWORK-NAME" z" PASSWORD" webui
|
|
</pre>
|
|
|
|
<p>
|
|
See <a href="https://github.com/flagxor/eforth/blob/main/ueforth/esp32/web_interface.fs">web_interface.fs</a>.
|
|
</p>
|
|
|
|
<h3 id="autoexec">Autoexec.fs</h3>
|
|
|
|
<p>
|
|
The system will automatically attempt to mount SPIFFS filesystem at <code>/spiffs</code>.
|
|
It will then at start attempt to load <code>/spiffs/autoexec.fs</code>
|
|
</p>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<pre>
|
|
r| z" NETWORK-NAME" z" PASSWORD" webui | s" /spiffs/autoexec.fs" dump-file
|
|
</pre>
|
|
|
|
<p>
|
|
To remove a previously configured <code>autoexec.fs</code> 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 <code>autoexec.fs</code>
|
|
and replace it with a different name. Then run the following:
|
|
</p>
|
|
|
|
<pre>
|
|
s" /spiffs/autoexec.fs" delete-file
|
|
</pre>
|
|
|
|
<p>
|
|
See <a href="https://github.com/flagxor/eforth/blob/main/ueforth/esp32/autoboot.fs">autoboot.fs</a>.
|
|
</p>
|
|
|
|
<h3 id="adding_words">Adding Words</h3>
|
|
|
|
<p>
|
|
Adding words based on C functions can be done by editing the source code.
|
|
For ESP32forth <code>template.ino</code> is the appropriate place.
|
|
</p>
|
|
|
|
<p>
|
|
Because of the use of <a href="https://en.wikipedia.org/wiki/X_Macro">X-Macros</a> words can be
|
|
added in as little as one line. Locate the macro called <code>PLATFORM_OPCODE_LIST</code>
|
|
and add your words there.
|
|
</p>
|
|
|
|
<p>NOTE: Be careful to end each line with a \ so the macros will chain correctly.</p>
|
|
|
|
<p>
|
|
To add a word containing only letters, numbers, and underscore you can use <code>Y</code>.
|
|
</p>
|
|
|
|
<pre>
|
|
Y(MY_WORD123, c_function_to_call()) \
|
|
</pre>
|
|
|
|
<p>
|
|
If your word name contains other characters, instead use <code>X</code>.
|
|
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.
|
|
</p>
|
|
|
|
<pre>
|
|
X("myword!", MY_WORD_BANG, c_function_to_call()) \
|
|
</pre>
|
|
|
|
<p>
|
|
Values from the data stack can be accessed via
|
|
the variables <code>tos</code> (Top of Stack) and
|
|
<code>sp</code> (Pointer to the rest of the stack).
|
|
The stack has the data type <code>cell_t</code>.
|
|
</p>
|
|
|
|
<p>
|
|
You can push a value of any type to the stack with the macro <code>PUSH</code>:
|
|
</p>
|
|
|
|
<pre>
|
|
Y(MY_WORD, PUSH calculate_magic_value()) \
|
|
</pre>
|
|
|
|
<p>
|
|
To simplify calling C functions, you can also refer to elements on the stack
|
|
with the types positional names:
|
|
</p>
|
|
|
|
<pre>
|
|
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:
|
|
void send_message(const char *message, int code);
|
|
...
|
|
X("send-message", SEND_MESSAGE, send_message(c1, n0)) \
|
|
</pre>
|
|
|
|
<p>
|
|
You can always replace the top item on the stack with <code>SET</code>:
|
|
</p>
|
|
|
|
<pre>
|
|
Y(DECODE, SET decode_func(n0)) \
|
|
</pre>
|
|
|
|
<p>
|
|
You can drop elements from the stack with <code>DROP</code>
|
|
or <code>DROPn(number)</code>.
|
|
You can nip elements from below top of the stack with <code>NIP</code>
|
|
or <code>NIPn(number)</code>.
|
|
Be aware that like <code>PUSH</code> this will cause stack indices to change.
|
|
</p>
|
|
|
|
<pre>
|
|
int my_cool_function(char *foo, int bar, uint8_t *baz);
|
|
...
|
|
Y(MY_FUNC, n0 = my_cool_function(c2, n1, b0); NIPn(2)) \
|
|
</pre>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<p>
|
|
New variables can be declared in each word and are scoped to that word.
|
|
</p>
|
|
|
|
<pre>
|
|
Y(MY_WORD, cell_t foo = n0; DROP; char *x = c0; DROP; \
|
|
PUSH my_func(foo, x)) \
|
|
</pre>
|
|
|