/* $xxxterm$ */
/*
* Copyright (c) 2010 Marco Peereboom <marco@peereboom.us>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* TODO:
* inverse color browsing
* favs
* download files status
* multi letter commands
* pre and post counts for commands
* search on page
* search on engines
* fav icon
* close tab X
* autocompletion on various inputs
*/
#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <webkit/webkit.h>
#include <libsoup/soup.h>
#include <JavaScriptCore/JavaScript.h>
static char *version = "$xxxterm$";
#define XT_DEBUG
/* #define XT_DEBUG */
#ifdef XT_DEBUG
#define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
#define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
#define XT_D_MOVE 0x0001
#define XT_D_KEY 0x0002
#define XT_D_TAB 0x0004
#define XT_D_URL 0x0008
#define XT_D_CMD 0x0010
#define XT_D_NAV 0x0020
#define XT_D_DOWNLOAD 0x0040
#define XT_D_CONFIG 0x0080
u_int32_t swm_debug = 0
| XT_D_MOVE
| XT_D_KEY
| XT_D_TAB
| XT_D_URL
| XT_D_CMD
| XT_D_NAV
| XT_D_DOWNLOAD
| XT_D_CONFIG
;
#else
#define DPRINTF(x...)
#define DNPRINTF(n,x...)
#endif
#define LENGTH(x) (sizeof x / sizeof x[0])
#define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
~(GDK_BUTTON1_MASK) & \
~(GDK_BUTTON2_MASK) & \
~(GDK_BUTTON3_MASK) & \
~(GDK_BUTTON4_MASK) & \
~(GDK_BUTTON5_MASK))
struct tab {
TAILQ_ENTRY(tab) entry;
GtkWidget *vbox;
GtkWidget *label;
GtkWidget *uri_entry;
GtkWidget *toolbar;
GtkWidget *browser_win;
GtkWidget *cmd;
guint tab_id;
/* adjustments for browser */
GtkScrollbar *sb_h;
GtkScrollbar *sb_v;
GtkAdjustment *adjust_h;
GtkAdjustment *adjust_v;
/* flags */
int focus_wv;
int ctrl_click;
gchar *hover;
WebKitWebView *wv;
WebKitWebSettings *settings;
};
TAILQ_HEAD(tab_list, tab);
struct karg {
int i;
char *s;
};
/* defines */
#define XT_DIR (".xxxterm")
#define XT_CONF_FILE ("xxxterm.conf")
#define XT_CB_HANDLED (TRUE)
#define XT_CB_PASSTHROUGH (FALSE)
/* actions */
#define XT_MOVE_INVALID (0)
#define XT_MOVE_DOWN (1)
#define XT_MOVE_UP (2)
#define XT_MOVE_BOTTOM (3)
#define XT_MOVE_TOP (4)
#define XT_MOVE_PAGEDOWN (5)
#define XT_MOVE_PAGEUP (6)
#define XT_MOVE_LEFT (7)
#define XT_MOVE_FARLEFT (8)
#define XT_MOVE_RIGHT (9)
#define XT_MOVE_FARRIGHT (10)
#define XT_TAB_PREV (-2)
#define XT_TAB_NEXT (-1)
#define XT_TAB_INVALID (0)
#define XT_TAB_NEW (1)
#define XT_TAB_DELETE (2)
#define XT_TAB_DELQUIT (3)
#define XT_TAB_OPEN (4)
#define XT_NAV_INVALID (0)
#define XT_NAV_BACK (1)
#define XT_NAV_FORWARD (2)
/* globals */
extern char *__progname;
struct passwd *pwd;
GtkWidget *main_window;
GtkNotebook *notebook;
struct tab_list tabs;
/* settings */
int showtabs = 1; /* show tabs on notebook */
int showurl = 1; /* show url toolbar on notebook */
int tabless = 0; /* allow only 1 tab */
int ctrl_click_focus = 0; /* ctrl click gets focus */
int cookies_enabled = 1; /* enable cookies */
int read_only_cookies = 0; /* enable to not write cookies */
int enable_scripts = 1;
int enable_plugins = 1;
int default_font_size = 12;
char *home = "http://www.peereboom.us";
char *http_proxy = NULL;
SoupURI *proxy_uri = NULL;
char work_dir[PATH_MAX];
char cookie_file[PATH_MAX];
char download_dir[PATH_MAX];
SoupSession *session;
SoupCookieJar *cookiejar;
/* protos */
void create_new_tab(char *, int);
void delete_tab(struct tab *);
struct valid_url_types {
char *type;
} vut[] = {
{ "http://" },
{ "https://" },
{ "ftp://" },
{ "file://" },
};
int
valid_url_type(char *url)
{
int i;
for (i = 0; i < LENGTH(vut); i++)
if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
return (0);
return (1);
}
char *
guess_url_type(char *url_in)
{
struct stat sb;
char *url_out = NULL;
/* XXX not sure about this heuristic */
if (stat(url_in, &sb) == 0) {
if (asprintf(&url_out, "file://%s", url_in) == -1)
err(1, "aprintf file");
} else {
/* guess http */
if (asprintf(&url_out, "http://%s", url_in) == -1)
err(1, "aprintf http");
}
if (url_out == NULL)
err(1, "asprintf pointer");
DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
return (url_out);
}
#define WS "\n= \t"
void
config_parse(char *filename)
{
FILE *config;
char *line, *cp, *var, *val;
size_t len, lineno = 0;
DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
if (filename == NULL)
return;
if ((config = fopen(filename, "r")) == NULL) {
warn("config_parse: cannot open %s", filename);
return;
}
for (;;) {
if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
if (feof(config))
break;
cp = line;
cp += (long)strspn(cp, WS);
if (cp[0] == '\0') {
/* empty line */
free(line);
continue;
}
if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
break;
cp += (long)strspn(cp, WS);
if ((val = strsep(&cp, WS)) == NULL)
break;
DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
/* get settings */
if (!strcmp(var, "home"))
home = strdup(val);
else if (!strcmp(var, "ctrl_click_focus"))
ctrl_click_focus = atoi(val);
else if (!strcmp(var, "read_only_cookies"))
read_only_cookies = atoi(val);
else if (!strcmp(var, "cookies_enabled"))
cookies_enabled = atoi(val);
else if (!strcmp(var, "enable_scripts"))
enable_scripts = atoi(val);
else if (!strcmp(var, "enable_plugins"))
enable_plugins = atoi(val);
else if (!strcmp(var, "default_font_size"))
default_font_size = atoi(val);
else if (!strcmp(var, "http_proxy")) {
http_proxy = strdup(val);
if (http_proxy == NULL)
err(1, "http_proxy");
} else if (!strcmp(var, "download_dir")) {
if (val[0] == '~')
snprintf(download_dir, sizeof download_dir,
"%s/%s", pwd->pw_dir, &val[1]);
else
strlcpy(download_dir, val, sizeof download_dir);
fprintf(stderr, "download dir: %s\n", download_dir);
} else
errx(1, "invalid conf file entry: %s=%s", var, val);
free(line);
}
fclose(config);
}
int
quit(struct tab *t, struct karg *args)
{
gtk_main_quit();
return (1);
}
int
help(struct tab *t, struct karg *args)
{
if (t == NULL)
errx(1, "help");
webkit_web_view_load_string(t->wv,
"<html><body><h1>XXXTerm</h1></body></html>",
NULL,
NULL,
NULL);
return (0);
}
int
navaction(struct tab *t, struct karg *args)
{
DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
t->tab_id, args->i);
switch (args->i) {
case XT_NAV_BACK:
webkit_web_view_go_back(t->wv);
break;
case XT_NAV_FORWARD:
webkit_web_view_go_forward(t->wv);
break;
}
return (XT_CB_PASSTHROUGH);
}
int
move(struct tab *t, struct karg *args)
{
GtkAdjustment *adjust;
double pi, si, pos, ps, upper, lower, max;
switch (args->i) {
case XT_MOVE_DOWN:
case XT_MOVE_UP:
case XT_MOVE_BOTTOM:
case XT_MOVE_TOP:
case XT_MOVE_PAGEDOWN:
case XT_MOVE_PAGEUP:
adjust = t->adjust_v;
break;
default:
adjust = t->adjust_h;
break;
}
pos = gtk_adjustment_get_value(adjust);
ps = gtk_adjustment_get_page_size(adjust);
upper = gtk_adjustment_get_upper(adjust);
lower = gtk_adjustment_get_lower(adjust);
si = gtk_adjustment_get_step_increment(adjust);
pi = gtk_adjustment_get_page_increment(adjust);
max = upper - ps;
DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
"max %f si %f pi %f\n",
args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
pos, ps, upper, lower, max, si, pi);
switch (args->i) {
case XT_MOVE_DOWN:
case XT_MOVE_RIGHT:
pos += si;
gtk_adjustment_set_value(adjust, MIN(pos, max));
break;
case XT_MOVE_UP:
case XT_MOVE_LEFT:
pos -= si;
gtk_adjustment_set_value(adjust, MAX(pos, lower));
break;
case XT_MOVE_BOTTOM:
case XT_MOVE_FARRIGHT:
gtk_adjustment_set_value(adjust, max);
break;
case XT_MOVE_TOP:
case XT_MOVE_FARLEFT:
gtk_adjustment_set_value(adjust, lower);
break;
case XT_MOVE_PAGEDOWN:
pos += pi;
gtk_adjustment_set_value(adjust, MIN(pos, max));
break;
case XT_MOVE_PAGEUP:
pos -= pi;
gtk_adjustment_set_value(adjust, MAX(pos, lower));
break;
default:
return (XT_CB_PASSTHROUGH);
}
DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
return (XT_CB_HANDLED);
}
char *
getparams(char *cmd, char *cmp)
{
char *rv = NULL;
if (cmd && cmp) {
if (!strncmp(cmd, cmp, strlen(cmp))) {
rv = cmd + strlen(cmp);
while (*rv == ' ')
rv++;
if (strlen(rv) == 0)
rv = NULL;
}
}
return (rv);
}
int
tabaction(struct tab *t, struct karg *args)
{
int rv = XT_CB_HANDLED;
char *url = NULL, *newuri = NULL;
DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
if (t == NULL)
return (XT_CB_PASSTHROUGH);
switch (args->i) {
case XT_TAB_NEW:
if ((url = getparams(args->s, "tabnew")))
create_new_tab(url, 1);
else
create_new_tab(NULL, 1);
break;
case XT_TAB_DELETE:
delete_tab(t);
break;
case XT_TAB_DELQUIT:
if (gtk_notebook_get_n_pages(notebook) > 1)
delete_tab(t);
else
quit(t, args);
break;
case XT_TAB_OPEN:
if ((url = getparams(args->s, "open")) ||
((url = getparams(args->s, "op"))) ||
((url = getparams(args->s, "o"))))
;
else {
rv = XT_CB_PASSTHROUGH;
goto done;
}
if (valid_url_type(url)) {
newuri = guess_url_type(url);
url = newuri;
}
webkit_web_view_load_uri(t->wv, url);
if (newuri)
free(newuri);
break;
default:
rv = XT_CB_PASSTHROUGH;
goto done;
}
done:
if (args->s) {
free(args->s);
args->s = NULL;
}
return (rv);
}
int
movetab(struct tab *t, struct karg *args)
{
struct tab *tt;
int x;
DNPRINTF(XT_D_TAB, "movetab: %p %d\n", t, args->i);
if (t == NULL)
return (XT_CB_PASSTHROUGH);
if (args->i == XT_TAB_INVALID)
return (XT_CB_PASSTHROUGH);
if (args->i < XT_TAB_INVALID) {
/* next or previous tab */
if (TAILQ_EMPTY(&tabs))
return (XT_CB_PASSTHROUGH);
if (args->i == XT_TAB_NEXT)
gtk_notebook_next_page(notebook);
else
gtk_notebook_prev_page(notebook);
return (XT_CB_HANDLED);
}
/* jump to tab */
x = args->i - 1;
if (t->tab_id == x) {
DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
return (XT_CB_HANDLED);
}
TAILQ_FOREACH(tt, &tabs, entry) {
if (tt->tab_id == x) {
gtk_notebook_set_current_page(notebook, x);
DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
if (tt->focus_wv)
gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
}
}
return (XT_CB_HANDLED);
}
int
command(struct tab *t, struct karg *args)
{
DNPRINTF(XT_D_CMD, "command:\n");
gtk_entry_set_text(GTK_ENTRY(t->cmd), ":");
gtk_widget_show(t->cmd);
gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
return (XT_CB_HANDLED);
}
/* inherent to GTK not all keys will be caught at all times */
struct key {
guint mask;
guint modkey;
guint key;
int (*func)(struct tab *, struct karg *);
struct karg arg;
} keys[] = {
{ GDK_SHIFT_MASK, 0, GDK_colon, command, {0} },
{ GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
/* navigation */
{ 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
{ GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
{ GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
{ GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
/* vertical movement */
{ 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
{ 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
{ 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
{ 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
{ GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
{ 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
{ 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
{ 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
{ 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
{ 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
{ 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
/* horizontal movement */
{ 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
{ 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
{ 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
{ 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
{ GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
{ 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
/* tabs */
{ GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
{ GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
{ GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
{ GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
{ GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
{ GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
{ GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
{ GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
{ GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
{ GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
{ GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
{ GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
};
struct cmd {
char *cmd;
int params;
int (*func)(struct tab *, struct karg *);
struct karg arg;
} cmds[] = {
{ "q!", 0, quit, {0} },
{ "qa", 0, quit, {0} },
{ "qa!", 0, quit, {0} },
{ "help", 0, help, {0} },
/* tabs */
{ "o", 1, tabaction, {.i = XT_TAB_OPEN} },
{ "op", 1, tabaction, {.i = XT_TAB_OPEN} },
{ "open", 1, tabaction, {.i = XT_TAB_OPEN} },
{ "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
{ "tabedit", 0, tabaction, {.i = XT_TAB_NEW} },
{ "tabe", 0, tabaction, {.i = XT_TAB_NEW} },
{ "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
{ "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
{ "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
/* XXX add count to these commands and add tabl and friends */
{ "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
{ "tabp", 0, movetab, {.i = XT_TAB_PREV} },
{ "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
{ "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
};
void
focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
{
DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
t->tab_id, t->focus_wv);
if (t == NULL)
errx(1, "focus_uri_entry_cb");
/* focus on wv instead */
if (t->focus_wv)
gtk_widget_grab_focus(GTK_WIDGET(t->wv));
}
void
activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
{
const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
char *newuri = NULL;
DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
if (t == NULL)
errx(1, "activate_uri_entry_cb");
if (uri == NULL)
errx(1, "uri");
if (valid_url_type((char *)uri)) {
newuri = guess_url_type((char *)uri);
uri = newuri;
}
webkit_web_view_load_uri(t->wv, uri);
gtk_widget_grab_focus(GTK_WIDGET(t->wv));
if (newuri)
free(newuri);
}
void
notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
{
WebKitWebFrame *frame;
const gchar *uri;
if (t == NULL)
errx(1, "notify_load_status_cb");
switch (webkit_web_view_get_load_status(wview)) {
case WEBKIT_LOAD_COMMITTED:
frame = webkit_web_view_get_main_frame(wview);
uri = webkit_web_frame_get_uri(frame);
if (uri)
gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
t->focus_wv = 1;
/* take focus if we are visible */
if (gtk_notebook_get_current_page(notebook) == t->tab_id)
gtk_widget_grab_focus(GTK_WIDGET(t->wv));
break;
case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
uri = webkit_web_view_get_title(wview);
if (uri == NULL) {
frame = webkit_web_view_get_main_frame(wview);
uri = webkit_web_frame_get_uri(frame);
}
gtk_label_set_text(GTK_LABEL(t->label), uri);
break;
case WEBKIT_LOAD_PROVISIONAL:
case WEBKIT_LOAD_FINISHED:
case WEBKIT_LOAD_FAILED:
default:
break;
}
}
int
webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
WebKitWebPolicyDecision *pd, struct tab *t)
{
char *uri;
if (t == NULL)
errx(1, "webview_npd_cb");
DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
webkit_network_request_get_uri(request));
if (t->ctrl_click) {
uri = (char *)webkit_network_request_get_uri(request);
create_new_tab(uri, ctrl_click_focus);
t->ctrl_click = 0;
webkit_web_policy_decision_ignore(pd);
return (TRUE); /* we made the decission */
}
return (FALSE);
}
int
webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
{
/* we can not eat the event without throwing gtk off so defer it */
/* catch ctrl click */
if (e->type == GDK_BUTTON_RELEASE &&
CLEAN(e->state) == GDK_CONTROL_MASK)
t->ctrl_click = 1;
else
t->ctrl_click = 0;
return (XT_CB_PASSTHROUGH);
}
int
webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
WebKitNetworkRequest *request, char *mime_type,
WebKitWebPolicyDecision *decision, struct tab *t)
{
if (t == NULL)
errx(1, "webview_mimetype_cb");
DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
t->tab_id, mime_type);
if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
webkit_web_policy_decision_download(decision);
return (TRUE);
}
return (FALSE);
}
int
webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
{
const gchar *filename;
char *uri = NULL;
if (download == NULL || t == NULL)
errx(1, "webview_download_cb: invalid pointers");
filename = webkit_download_get_suggested_filename(download);
if (filename == NULL)
return (FALSE); /* abort download */
if (asprintf(&uri, "file://%s/%s", download_dir, filename) == -1)
err(1, "aprintf uri");
DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
"local %s\n",
t->tab_id, filename, uri);
webkit_download_set_destination_uri(download, uri);
if (uri)
free(uri);
webkit_download_start(download);
return (TRUE); /* start download */
}
void
webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
{
DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
if (t == NULL)
errx(1, "webview_hover_cb");
if (uri) {
if (t->hover) {
free(t->hover);
t->hover = NULL;
}
t->hover = strdup(uri);
} else if (t->hover) {
free(t->hover);
t->hover = NULL;
}
}
int
webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
{
int i;
/* don't use w directly; use t->whatever instead */
if (t == NULL)
errx(1, "webview_keypress_cb");
DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
e->keyval, e->state, t);
for (i = 0; i < LENGTH(keys); i++)
if (e->keyval == keys[i].key && CLEAN(e->state) ==
keys[i].mask) {
keys[i].func(t, &keys[i].arg);
return (XT_CB_HANDLED);
}
return (XT_CB_PASSTHROUGH);
}
int
cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
{
int rv = XT_CB_HANDLED;
const gchar *c = gtk_entry_get_text(w);
if (t == NULL)
errx(1, "cmd_keypress_cb");
DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
e->keyval, e->state, t);
/* sanity */
if (c == NULL)
e->keyval = GDK_Escape;
else if (c[0] != ':')
e->keyval = GDK_Escape;
switch (e->keyval) {
case GDK_BackSpace:
if (strcmp(c, ":"))
break;
/* FALLTHROUGH */
case GDK_Escape:
gtk_widget_hide(t->cmd);
gtk_widget_grab_focus(GTK_WIDGET(t->wv));
goto done;
}
rv = XT_CB_PASSTHROUGH;
done:
return (rv);
}
int
cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
{
if (t == NULL)
errx(1, "cmd_focusout_cb");
DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
t->tab_id, t->focus_wv);
/* abort command when losing focus */
gtk_widget_hide(t->cmd);
if (t->focus_wv)
gtk_widget_grab_focus(GTK_WIDGET(t->wv));
else
gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
return (XT_CB_PASSTHROUGH);
}
void
cmd_activate_cb(GtkEntry *entry, struct tab *t)
{
int i;
char *s;
const gchar *c = gtk_entry_get_text(entry);
if (t == NULL)
errx(1, "cmd_activate_cb");
DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
/* sanity */
if (c == NULL)
goto done;
else if (c[0] != ':')
goto done;
if (strlen(c) < 2)
goto done;
s = (char *)&c[1];
for (i = 0; i < LENGTH(cmds); i++)
if (cmds[i].params) {
if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
cmds[i].arg.s = strdup(s);
cmds[i].func(t, &cmds[i].arg);
}
} else {
if (!strcmp(s, cmds[i].cmd))
cmds[i].func(t, &cmds[i].arg);
}
done:
gtk_widget_hide(t->cmd);
}
GtkWidget *
create_browser(struct tab *t)
{
GtkWidget *w;
if (t == NULL)
errx(1, "create_browser");
t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
g_signal_connect(t->wv, "notify::load-status",
G_CALLBACK(notify_load_status_cb), t);
return (w);
}
GtkWidget *
create_window(void)
{
GtkWidget *w;
w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
gtk_widget_set_name(w, "xxxterm");
gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
return (w);
}
GtkWidget *
create_toolbar(struct tab *t)
{
GtkWidget *toolbar = gtk_toolbar_new();
GtkToolItem *i;
#if GTK_CHECK_VERSION(2,15,0)
gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
GTK_ORIENTATION_HORIZONTAL);
#else
gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
GTK_ORIENTATION_HORIZONTAL);
#endif
gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
i = gtk_tool_item_new();
gtk_tool_item_set_expand(i, TRUE);
t->uri_entry = gtk_entry_new();
gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
g_signal_connect(G_OBJECT(t->uri_entry), "activate",
G_CALLBACK(activate_uri_entry_cb), t);
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
return (toolbar);
}
void
delete_tab(struct tab *t)
{
DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
if (t == NULL)
return;
TAILQ_REMOVE(&tabs, t, entry);
if (TAILQ_EMPTY(&tabs))
create_new_tab(NULL, 1);
gtk_widget_destroy(t->vbox);
g_free(t);
}
void
setup_webkit(struct tab *t)
{
gchar *strval;
gchar *ua;
t->settings = webkit_web_settings_new();
g_object_get((GObject *)t->settings, "user-agent", &strval, NULL);
if (strval == NULL) {
warnx("setup_webkit: can't get user-agent property");
return;
}
if (asprintf(&ua, "%s %s+", strval, version) == -1)
err(1, "aprintf user-agent");
g_object_set((GObject *)t->settings,
"user-agent", ua, NULL);
g_object_set((GObject *)t->settings,
"enable-scripts", enable_scripts, NULL);
g_object_set((GObject *)t->settings,
"enable-plugins", enable_plugins, NULL);
g_object_set((GObject *)t->settings,
"default-font-size", default_font_size, NULL);
webkit_web_view_set_settings(t->wv, t->settings);
g_free (strval);
free(ua);
}
void
create_new_tab(char *title, int focus)
{
struct tab *t;
int load = 1;
char *newuri = NULL;
DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
if (tabless && !TAILQ_EMPTY(&tabs)) {
DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
return;
}
t = g_malloc0(sizeof *t);
TAILQ_INSERT_TAIL(&tabs, t, entry);
if (title == NULL) {
title = "(untitled)";
load = 0;
} else {
if (valid_url_type(title)) {
newuri = guess_url_type(title);
title = newuri;
}
}
t->vbox = gtk_vbox_new(FALSE, 0);
/* label for tab */
t->label = gtk_label_new(title);
gtk_widget_set_size_request(t->label, 100, -1);
/* toolbar */
t->toolbar = create_toolbar(t);
gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
/* browser */
t->browser_win = create_browser(t);
gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
setup_webkit(t);
/* command entry */
t->cmd = gtk_entry_new();
gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
/* and show it all */
gtk_widget_show_all(t->vbox);
t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
t->label);
g_object_connect((GObject*)t->cmd,
"signal::key-press-event", (GCallback)cmd_keypress_cb, t,
"signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
"signal::activate", (GCallback)cmd_activate_cb, t,
NULL);
g_object_connect((GObject*)t->wv,
"signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
/* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
"signal::download-requested", (GCallback)webview_download_cb, t,
"signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
"signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
"signal::event", (GCallback)webview_event_cb, t,
NULL);
/* hijack the unused keys as if we were the browser */
g_object_connect((GObject*)t->toolbar,
"signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
NULL);
g_signal_connect(G_OBJECT(t->uri_entry), "focus",
G_CALLBACK(focus_uri_entry_cb), t);
/* hide stuff */
gtk_widget_hide(t->cmd);
if (showurl == 0)
gtk_widget_hide(t->toolbar);
if (focus) {
gtk_notebook_set_current_page(notebook, t->tab_id);
DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
t->tab_id);
}
if (load)
webkit_web_view_load_uri(t->wv, title);
else
gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
if (newuri)
free(newuri);
}
void
notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
gpointer *udata)
{
struct tab *t;
DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
TAILQ_FOREACH(t, &tabs, entry) {
if (t->tab_id == pn) {
DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
"%d\n", pn);
gtk_widget_hide(t->cmd);
}
}
}
void
create_canvas(void)
{
GtkWidget *vbox;
vbox = gtk_vbox_new(FALSE, 0);
notebook = GTK_NOTEBOOK(gtk_notebook_new());
if (showtabs == 0)
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
g_object_connect((GObject*)notebook,
"signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
NULL);
main_window = create_window();
gtk_container_add(GTK_CONTAINER(main_window), vbox);
gtk_widget_show_all(main_window);
}
void
setup_cookies(void)
{
if (cookiejar) {
soup_session_remove_feature(session,
(SoupSessionFeature*)cookiejar);
g_object_unref(cookiejar);
cookiejar = NULL;
}
if (cookies_enabled == 0)
return;
cookiejar = soup_cookie_jar_text_new(cookie_file, read_only_cookies);
soup_session_add_feature(session, (SoupSessionFeature*)cookiejar);
}
void
setup_proxy(char *uri)
{
if (proxy_uri) {
g_object_set(session, "proxy_uri", NULL, NULL);
soup_uri_free(proxy_uri);
proxy_uri = NULL;
}
if (http_proxy) {
freepre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */# Function calls in a single line.
#
# To run (on Linux):
# $ ./translate_subx init.linux 0*.subx apps/subx-params.subx apps/calls.subx
# $ mv a.elf apps/calls
#
# Example 1:
# $ echo '(foo %eax)' | apps/calls
# # . (foo %eax) # output has comments
# ff 6/subop/push %eax # push
# e8/call foo/disp32 # call
# 81 0/subop/add %esp 4/imm32 # undo push
#
# Example 2:
# $ echo '(foo Var1 *(eax + 4) "blah")' | apps/calls
# # . (foo Var1 *(eax + 4) "blah")
# 68/push "blah"/imm32
# ff 6/subop/push *(eax + 4) # push args in..
# 68/push Var1/imm32 # ..reverse order
# e8/call foo/disp32
# 81 0/subop/add %esp 0xc/imm32 # undo pushes
#
# Calls always begin with '(' as the first non-whitespace on a line.
== code
Entry: # run tests if necessary, convert stdin if not
# . prologue
89/<- %ebp 4/r32/esp
# initialize heap
# . Heap = new-segment(Heap-size)
# . . push args
68/push Heap/imm32
ff 6/subop/push *Heap-size
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# - if argc > 1 and argv[1] == "test", then return run_tests()
# if (argc <= 1) goto run-main
81 7/subop/compare *ebp 1/imm32
7e/jump-if-<= $subx-calls-main:interactive/disp8
# if (!kernel-string-equal?(argv[1], "test")) goto run-main
# . eax = kernel-string-equal?(argv[1], "test")
# . . push args
68/push "test"/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call kernel-string-equal?/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . if (eax == false) goto run-main
3d/compare-eax-and 0/imm32/false
74/jump-if-= $subx-calls-main:interactive/disp8
# run-tests()
e8/call run-tests/disp32
# syscall(exit, *Num-test-failures)
8b/-> *Num-test-failures 3/r32/ebx
eb/jump $subx-calls-main:end/disp8
$subx-calls-main:interactive:
# - otherwise convert stdin
# subx-calls(Stdin, Stdout)
# . . push args
68/push Stdout/imm32
68/push Stdin/imm32
# . . call
e8/call subx-calls/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# syscall(exit, 0)
bb/copy-to-ebx 0/imm32
$subx-calls-main:end:
e8/call syscall_exit/disp32
subx-calls: # in: (addr buffered-file), out: (addr buffered-file)
# pseudocode:
# var line: (stream byte 512)
# var words: (stream slice 16) # at most function name and 15 args
# while true
# clear-stream(line)
# read-line-buffered(in, line)
# if (line->write == 0) break # end of file
# skip-chars-matching(line, ' ')
# if line->data[line->read] != '('
# write-stream-data(out, line)
# continue
# # emit comment
# write-buffered(out, "# . ")
# write-stream-data(out, line)
# # emit code
# ++line->read to skip '('
# clear-stream(words)
# words = parse-line(line)
# emit-call(out, words)
# flush(out)
#
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
52/push-edx
56/push-esi
# var line/esi: (stream byte 512)
81 5/subop/subtract %esp 0x200/imm32
68/push 0x200/imm32/length
68/push 0/imm32/read
68/push 0/imm32/write
89/<- %esi 4/r32/esp
# var words/edx: (stream slice 128) # 16 rows * 8 bytes/row
81 5/subop/subtract %esp 0x80/imm32
68/push 0x80/imm32/length
68/push 0/imm32/read
68/push 0/imm32/write
89/<- %edx 4/r32/esp
$subx-calls:loop:
# clear-stream(line)
# . . push args
56/push-esi
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# read-line-buffered(in, line)
# . . push args
56/push-esi
ff 6/subop/push *(ebp+8)
# . . call
e8/call read-line-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
$subx-calls:check0:
# if (line->write == 0) break
81 7/subop/compare *esi 0/imm32
0f 84/jump-if-= $subx-calls:break/disp32
# skip-chars-matching(line, ' ')
# . . push args
68/push 0x20/imm32/space
56/push-esi
# . . call
e8/call skip-chars-matching/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# if (line->data[line->read] == '(') goto convert-call
# . ecx = line->read
8b/-> *(esi+4) 1/r32/ecx
# . eax = line->data[line->read]
31/xor-with %eax 0/r32/eax
8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
# . if (eax == '(') goto convert-call
3d/compare-eax-and 0x28/imm32/open-paren
74/jump-if-= $subx-calls:convert-call/disp8
$subx-calls:pass-through:
# write-stream-data(out, line)
# . . push args
56/push-esi
ff 6/subop/push *(ebp+0xc)
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# continue
eb/jump $subx-calls:loop/disp8
$subx-calls:convert-call:
# - emit comment
# write-buffered(out, "# . ")
# . . push args
68/push "# . "/imm32
ff 6/subop/push *(ebp+0xc)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-stream-data(out, line)
# . . push args
56/push-esi
ff 6/subop/push *(ebp+0xc)
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# - emit code
# ++line->read to skip '('
ff 0/subop/increment *(esi+4)
# clear-stream(words)
# . . push args
52/push-edx
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# words = parse-line(line)
# . . push args
52/push-edx
56/push-esi
# . . call
e8/call parse-line/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# emit-call(out, words)
# . . push args
52/push-edx
ff 6/subop/push *(ebp+0xc)
# . . call
e8/call emit-call/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# loop
e9/jump $subx-calls:loop/disp32
$subx-calls:break:
# flush(out)
# . . push args
ff 6/subop/push *(ebp+0xc)
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
$subx-calls:end:
# . reclaim locals
81 0/subop/add %esp 0x298/imm32 # 0x20c + 0x8c
# . restore registers
5e/pop-to-esi
5a/pop-to-edx
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
parse-line: # line: (addr stream byte), words: (addr stream slice)
# pseudocode:
# var word-slice: slice
# while true
# word-slice = next-word-string-or-expression-without-metadata(line)
# if slice-empty?(word-slice)
# break # end of line
# write-int(words, word-slice->start)
# write-int(words, word-slice->end)
#
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
51/push-ecx
# var word-slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
$parse-line:loop:
# word-slice = next-word-string-or-expression-without-metadata(line)
# . . push args
51/push-ecx
ff 6/subop/push *(ebp+8)
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
$parse-line:check1:
# if (slice-empty?(word-slice)) break
# . eax = slice-empty?(word-slice)
# . . push args
51/push-ecx
# . . call
e8/call slice-empty?/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . if (eax != false) break
3d/compare-eax-and 0/imm32/false
0f 85/jump-if-!= $parse-line:end/disp32
#? # dump word-slice {{{
#? # . write(2/stderr, "w: ")
#? # . . push args
#? 68/push "w: "/imm32
#? 68/push 2/imm32/stderr
#? # . . call
#? e8/call write/disp32
#? # . . discard args
#? 81 0/subop/add %esp 8/imm32
#? # . clear-stream($Stderr->buffer)
#? # . . push args
#? 68/push $Stderr->buffer/imm32
#? # . . call
#? e8/call clear-stream/disp32
#? # . . discard args
#? 81 0/subop/add %esp 4/imm32
#? # . write-slice-buffered(Stderr, word-slice)
#? # . . push args
#? 51/push-ecx
#? 68/push Stderr/imm32
#? # . . call
#? e8/call write-slice-buffered/disp32
#? # . . discard args
#? 81 0/subop/add %esp 8/imm32
#? # . flush(Stderr)
#? # . . push args
#? 68/push Stderr/imm32
#? # . . call
#? e8/call flush/disp32
#? # . . discard args
#? 81 0/subop/add %esp 4/imm32
#? # . write(2/stderr, "$\n")
#? # . . push args
#? 68/push "$\n"/imm32
#? 68/push 2/imm32/stderr
#? # . . call
#? e8/call write/disp32
#? # . . discard args
#? 81 0/subop/add %esp 8/imm32
#? # }}}
$parse-line:write-word:
# write-int(words, word-slice->start)
# . . push args
ff 6/subop/push *ecx
ff 6/subop/push *(ebp+0xc)
# . . call
e8/call write-int/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-int(words, word-slice->end)
# . . push args
ff 6/subop/push *(ecx+4)
ff 6/subop/push *(ebp+0xc)
# . . call
e8/call write-int/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# loop
e9/jump $parse-line:loop/disp32
$parse-line:end:
# . reclaim locals
81 0/subop/add %esp 8/imm32
# . restore registers
59/pop-to-ecx
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
emit-call: # out: (addr buffered-file), words: (addr stream slice)
# pseudocode:
# if (words->write < 8) abort
# curr = &words->data[words->write-8]
# min = words->data
# # emit pushes
# while true
# if (curr <= min) break
# if *curr->start in '%' '*'
# write-buffered(out, "ff 6/subop/push ")
# write-slice-buffered(out, curr)
# write-buffered(out, "\n")
# else
# write-buffered(out, "68/push ")
# write-slice-buffered(out, curr)
# write-buffered(out, "/imm32\n")
# curr -= 8
# # emit call
# write-buffered(out, "e8/call ")
# write-slice-buffered(out, curr)
# write-buffered(out, "/disp32\n")
# # emit pops
# write-buffered(out, "81 0/subop/add %esp ")
# write-int32-hex-buffered(out, words->write >> 1 - 4)
# write-buffered(out, "/imm32\n")
#
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
52/push-edx
56/push-esi
# esi = words
8b/-> *(ebp+0xc) 6/r32/esi
# if (words->write < 8) abort
# . ecx = words->write - 8
8b/-> *esi 1/r32/ecx
81 5/subop/subtract %ecx 8/imm32
0f 8c/jump-if-< $emit-call:error1/disp32
# var curr/ecx: (addr slice) = &words->data[words->write-8]
8d/copy-address *(esi+ecx+0xc) 1/r32/ecx
# var min/edx: (addr byte) = words->data
8d/copy-address *(esi+0xc) 2/r32/edx
# - emit pushes
$emit-call:push-loop:
# if (curr <= min) break
39/compare %ecx 2/r32/edx
0f 8e/jump-if-<= $emit-call:call-instruction/disp32
# if (*curr->start in '%' '*') goto push-rm32
# . var start/eax: (addr byte) = curr->start
8b/-> *ecx 0/r32/eax
# . var c/eax: byte = *eax
8b/-> *eax 0/r32/eax
81 4/subop/and %eax 0xff/imm32
# . if (c == '%') goto push-rm32
3d/compare-eax-and 0x25/imm32/percent
74/jump-if-= $emit-call:push-rm32/disp8
# . if (c == '*') goto push-rm32
3d/compare-eax-and 0x2a/imm32/asterisk
74/jump-if-= $emit-call:push-rm32/disp8
$emit-call:push-imm32:
# write-buffered(out, "68/push ")
68/push "68/push "/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-slice-buffered(out, curr)
# . . push args
51/push-ecx
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-slice-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-buffered(out, "/imm32\n")
68/push "/imm32\n"/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# continue
eb/jump $emit-call:next-push/disp8
$emit-call:push-rm32:
# write-buffered(out, "ff 6/subop/push ")
# . . push args
68/push "ff 6/subop/push "/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-slice-buffered(out, curr)
# . . push args
51/push-ecx
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-slice-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-buffered(out, "\n")
68/push Newline/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
$emit-call:next-push:
# curr -= 8
81 5/subop/subtract %ecx 8/imm32
# loop
e9/jump $emit-call:push-loop/disp32
$emit-call:call-instruction:
# write-buffered(out, "e8/call ")
68/push "e8/call "/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-slice-buffered(out, curr)
# . . push args
51/push-ecx
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-slice-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-buffered(out, "/disp32\n")
68/push "/disp32\n"/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
$emit-call:pop-instruction:
# write-buffered(out, "81 0/subop/add %esp ")
68/push "81 0/subop/add %esp "/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-int32-hex-buffered(out, words->write >> 1 - 4)
# . . push args
8b/-> *esi 0/r32/eax
c1/shift 7/subop/arith-right %eax 1/imm8
2d/subtract-from-eax 4/imm32
50/push-eax
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-int32-hex-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# write-buffered(out, "/imm32\n")
68/push "/imm32\n"/imm32
ff 6/subop/push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
$emit-call:end:
# . restore registers
5e/pop-to-esi
5a/pop-to-edx
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
$emit-call:error1:
# print(stderr, "error: calls.subx: '()' is not a valid call")
# . write-buffered(Stderr, "error: calls.subx: '()' is not a valid call")
# . . push args
68/push "error: calls.subx: '()' is not a valid call"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
test-subx-calls-passes-most-lines-through:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . clear-stream($_test-input-buffered-file->buffer)
# . . push args
68/push $_test-input-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . clear-stream(_test-output-stream)
# . . push args
68/push _test-output-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . clear-stream($_test-output-buffered-file->buffer)
# . . push args
68/push $_test-output-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . write(_test-input-stream, "== abcd 0x1\n")
# . . push args
68/push "== abcd 0x1\n"/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# subx-calls(_test-input-buffered-file, _test-output-buffered-file)
# . . push args
68/push _test-output-buffered-file/imm32
68/push _test-input-buffered-file/imm32
# . . call
e8/call subx-calls/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check that the line just passed through
# . flush(_test-output-buffered-file)
# . . push args
68/push _test-output-buffered-file/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . check-stream-equal(_test-output-stream, "== abcd 0x1\n", msg)
# . . push args
68/push "F - test-subx-calls-passes-most-lines-through"/imm32
68/push "== abcd 0x1\n"/imm32
68/push _test-output-stream/imm32
# . . call
e8/call check-stream-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-subx-calls-processes-calls:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . clear-stream($_test-input-buffered-file->buffer)
# . . push args
68/push $_test-input-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . clear-stream(_test-output-stream)
# . . push args
68/push _test-output-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . clear-stream($_test-output-buffered-file->buffer)
# . . push args
68/push $_test-output-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . write(_test-input-stream, "(foo %eax)\n")
# . . push args
68/push "(foo %eax)\n"/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# subx-calls(_test-input-buffered-file, _test-output-buffered-file)
# . . push args
68/push _test-output-buffered-file/imm32
68/push _test-input-buffered-file/imm32
# . . call
e8/call subx-calls/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check that the line just passed through
# . flush(_test-output-buffered-file)
# . . push args
68/push _test-output-buffered-file/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
#? # dump _test-output-stream {{{
#? # . write(2/stderr, "^")
#? # . . push args
#? 68/push "^"/imm32
#? 68/push 2/imm32/stderr
#? # . . call
#? e8/call write/disp32
#? # . . discard args
#? 81 0/subop/add %esp 8/imm32
#? # . write-stream(2/stderr, _test-output-stream)
#? # . . push args
#? 68/push _test-output-stream/imm32
#? 68/push 2/imm32/stderr
#? # . . call
#? e8/call write-stream/disp32
#? # . . discard args
#? 81 0/subop/add %esp 8/imm32
#? # . write(2/stderr, "$\n")
#? # . . push args
#? 68/push "$\n"/imm32
#? 68/push 2/imm32/stderr
#? # . . call
#? e8/call write/disp32
#? # . . discard args
#? 81 0/subop/add %esp 8/imm32
#? # . rewind-stream(_test-output-stream)
#? # . . push args
#? 68/push _test-output-stream/imm32
#? # . . call
#? e8/call rewind-stream/disp32
#? # . . discard args
#? 81 0/subop/add %esp 4/imm32
#? # }}}
# . check-next-stream-line-equal(_test-output-stream, "# . (foo %eax)", msg)
# . . push args
68/push "F - test-subx-calls-processes-calls: comment"/imm32
68/push "# . (foo %eax)"/imm32
68/push _test-output-stream/imm32
# . . call
e8/call check-next-stream-line-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . check-next-stream-line-equal(_test-output-stream, "ff 6/subop/push %eax", msg)
# . . push args
68/push "F - test-subx-calls-processes-calls: arg 0"/imm32
68/push "ff 6/subop/push %eax"/imm32
68/push _test-output-stream/imm32
# . . call
e8/call check-next-stream-line-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . check-next-stream-line-equal(_test-output-stream, "e8/call foo/disp32", msg)
# . . push args
68/push "F - test-subx-calls-processes-calls: call"/imm32
68/push "e8/call foo/disp32"/imm32
68/push _test-output-stream/imm32
# . . call
e8/call check-next-stream-line-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . check-next-stream-line-equal(_test-output-stream, "81 0/subop/add %esp 4/imm32", msg)
# . . push args
68/push "F - test-subx-calls-processes-calls: pops"/imm32
68/push "81 0/subop/add %esp 0x00000004/imm32"/imm32
68/push _test-output-stream/imm32
# . . call
e8/call check-next-stream-line-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
next-word-string-or-expression-without-metadata: # line: (addr stream byte), out: (addr slice)
# pseudocode:
# skip-chars-matching(line, ' ')
# if line->read >= line->write # end of line
# out = {0, 0}
# return
# out->start = &line->data[line->read]
# if line->data[line->read] == '#' # comment
# out->end = &line->data[line->write] # skip to end of line
# return
# if line->data[line->read] == '"' # string literal
# skip-string(line)
# out->end = &line->data[line->read] # no metadata
# return
# if line->data[line->read] == '*' # expression
# if line->data[line->read + 1] == ' '
# abort
# if line->data[line->read + 1] == '('
# skip-until-close-paren(line)
# if (line->data[line->read] != ')'
# abort
# ++line->read to skip ')'
# out->end = &line->data[line->read]
# return
# if line->data[line->read] == ')'
# ++line->read to skip ')'
# # make sure there's nothing else of importance
# if line->read >= line->write
# out = {0, 0}
# return
# if line->data[line->read] != ' '
# abort
# skip-chars-matching-whitespace(line)
# if line->read >= line->write
# out = {0, 0}
# return
# if line->data[line->read] == '#' # only thing permitted after ')' is a comment
# out = {0, 0}
# return
# abort
# # default case: read a word -- but no metadata
# while true
# if line->read >= line->write
# break
# if line->data[line->read] == ' '
# break
# if line->data[line->read] == ')'
# break
# if line->data[line->read] == '/'
# abort
# ++line->read
# out->end = &line->data[line->read]
#
# registers:
# ecx: often line->read
# eax: often line->data[line->read]
#
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
56/push-esi
57/push-edi
# esi = line
8b/-> *(ebp+8) 6/r32/esi
# edi = out
8b/-> *(ebp+0xc) 7/r32/edi
# skip-chars-matching(line, ' ')
# . . push args
68/push 0x20/imm32/space
ff 6/subop/push *(ebp+8)
# . . call
e8/call skip-chars-matching/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
$next-word-string-or-expression-without-metadata:check0:
# if (line->read >= line->write) abort because we didn't encounter a final ')'
# . ecx = line->read
8b/-> *(esi+4) 1/r32/ecx
# . if (ecx >= line->write) abort
3b/compare<- *esi 1/r32/ecx
0f 8d/jump-if->= $next-word-string-or-expression-without-metadata:error0/disp32
$next-word-string-or-expression-without-metadata:check-for-comment:
# out->start = &line->data[line->read]
8d/copy-address *(esi+ecx+0xc) 0/r32/eax
89/<- *edi 0/r32/eax
# if (line->data[line->read] != '#') goto next check
# . var eax: byte = line->data[line->read]
31/xor-with %eax 0/r32/eax
8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
# . if (eax != '#') goto next check
3d/compare-eax-and 0x23/imm32/pound
75/jump-if-!= $next-word-string-or-expression-without-metadata:check-for-string-literal/disp8
$next-word-string-or-expression-without-metadata:comment:
# out->end = &line->data[line->write]
8b/-> *esi 0/r32/eax
8d/copy-address *(esi+eax+0xc) 0/r32/eax
89/<- *(edi+4) 0/r32/eax
# line->read = line->write # skip rest of line
8b/-> *esi 0/r32/eax
89/<- *(esi+4) 0/r32/eax
# return
e9/jump $next-word-string-or-expression-without-metadata:end/disp32
$next-word-string-or-expression-without-metadata:check-for-string-literal:
# if (line->data[line->read] != '"') goto next check
3d/compare-eax-and 0x22/imm32/dquote
75/jump-if-!= $next-word-string-or-expression-without-metadata:check-for-expression/disp8
$next-word-string-or-expression-without-metadata:string-literal:
# skip-string(line)
# . . push args
56/push-esi
# . . call
e8/call skip-string/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# out->end = &line->data[line->read]
8b/-> *(esi+4) 1/r32/ecx
8d/copy-address *(esi+ecx+0xc) 0/r32/eax
89/<- *(edi+4) 0/r32/eax
# return
e9/jump $next-word-string-or-expression-without-metadata:end/disp32
$next-word-string-or-expression-without-metadata:check-for-expression:
# if (line->data[line->read] != '*') goto next check
3d/compare-eax-and 0x2a/imm32/asterisk
75/jump-if-!= $next-word-string-or-expression-without-metadata:check-for-end-of-call/disp8
# if (line->data[line->read + 1] == ' ') goto error1
8a/copy-byte *(esi+ecx+0xd) 0/r32/AL
3d/compare-eax-and 0x20/imm32/space
0f 84/jump-if-= $next-word-string-or-expression-without-metadata:error1/disp32
# if (line->data[line->read + 1] != '(') goto regular-word
3d/compare-eax-and 0x28/imm32/open-paren
0f 85/jump-if-!= $next-word-string-or-expression-without-metadata:regular-word-without-metadata/disp32
$next-word-string-or-expression-without-metadata:paren:
# skip-until-close-paren(line)
# . . push args
56/push-esi
# . . call
e8/call skip-until-close-paren/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# if (line->data[line->read] != ')') goto error2
# . eax = line->data[line->read]
8b/-> *(esi+4) 1/r32/ecx
8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
# . if (eax != ')') goto error2
3d/compare-eax-and 0x29/imm32/close-paren
0f 85/jump-if-!= $next-word-string-or-expression-without-metadata:error2/disp32
# ++line->read to skip ')'
ff 0/subop/increment *(esi+4)
# out->end = &line->data[line->read]
8b/-> *(esi+4) 1/r32/ecx
8d/copy-address *(esi+ecx+0xc) 0/r32/eax
89/<- *(edi+4) 0/r32/eax
# return
e9/jump $next-word-string-or-expression-without-metadata:end/disp32
$next-word-string-or-expression-without-metadata:check-for-end-of-call:
# if (line->data[line->read] != ')') goto next check
3d/compare-eax-and 0x29/imm32/close-paren
75/jump-if-!= $next-word-string-or-expression-without-metadata:regular-word-without-metadata/disp8
# ++line->read to skip ')'
ff 0/subop/increment *(esi+4)
# - error checking: make sure there's nothing else of importance on the line
# if (line->read >= line->write) return out = {0, 0}
# . ecx = line->read
8b/-> *(esi+4) 1/r32/ecx
# . if (ecx >= line->write) return {0, 0}
3b/compare<- *esi 1/r32/ecx
0f 8d/jump-if->= $next-word-string-or-expression-without-metadata:return-eol/disp32
# if (line->data[line->read] == '/') goto error3
# . eax = line->data[line->read]
8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
# . if (eax == '/') goto error3
3d/compare-eax-and 0x2f/imm32/slash
0f 84/jump-if-= $next-word-string-or-expression-without-metadata:error3/disp32
# skip-chars-matching-whitespace(line)
# . . push args
56/push-esi
# . . call
e8/call skip-chars-matching-whitespace/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# if (line->read >= line->write) return out = {0, 0}
# . ecx = line->read
8b/-> *(esi+4) 1/r32/ecx
# . if (ecx >= line->write) return {0, 0}
3b/compare<- *esi 1/r32/ecx
0f 8d/jump-if->= $next-word-string-or-expression-without-metadata:return-eol/disp32
# if (line->data[line->read] == '#') return out = {0, 0}
# . eax = line->data[line->read]
8b/-> *(esi+4) 1/r32/ecx
8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
# . if (eax == '#') return out = {0, 0}
3d/compare-eax-and 0x23/imm32/pound
74/jump-if-= $next-word-string-or-expression-without-metadata:return-eol/disp8
# otherwise goto error4
e9/jump $next-word-string-or-expression-without-metadata:error4/disp32
$next-word-string-or-expression-without-metadata:regular-word-without-metadata:
# if (line->read >= line->write) break
# . ecx = line->read
8b/-> *(esi+4) 1/r32/ecx
# . if (ecx >= line->write) break
3b/compare<- *esi 1/r32/ecx
7d/jump-if->= $next-word-string-or-expression-without-metadata:regular-word-break/disp8
# if (line->data[line->read] == ' ') break
# . eax = line->data[line->read]
8b/-> *(esi+4) 1/r32/ecx
8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
# . if (eax == ' ') break
3d/compare-eax-and 0x20/imm32/space
74/jump-if-= $next-word-string-or-expression-without-metadata:regular-word-break/disp8
# if (line->data[line->read] == ')') break
3d/compare-eax-and 0x29/imm32/close-paren
0f 84/jump-if-= $next-word-string-or-expression-without-metadata:regular-word-break/disp32
# if (line->data[line->read] == '/') goto error5
3d/compare-eax-and 0x2f/imm32/slash
0f 84/jump-if-= $next-word-string-or-expression-without-metadata:error5/disp32
# ++line->read
ff 0/subop/increment *(esi+4)
# loop
e9/jump $next-word-string-or-expression-without-metadata:regular-word-without-metadata/disp32
$next-word-string-or-expression-without-metadata:regular-word-break:
# out->end = &line->data[line->read]
8b/-> *(esi+4) 1/r32/ecx
8d/copy-address *(esi+ecx+0xc) 0/r32/eax
89/<- *(edi+4) 0/r32/eax
eb/jump $next-word-string-or-expression-without-metadata:end/disp8
$next-word-string-or-expression-without-metadata:return-eol:
# return out = {0, 0}
c7 0/subop/copy *edi 0/imm32
c7 0/subop/copy *(edi+4) 0/imm32
$next-word-string-or-expression-without-metadata:end:
# . restore registers
5f/pop-to-edi
5e/pop-to-esi
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
$next-word-string-or-expression-without-metadata:error0:
# print(stderr, "error: missing final ')' in '" line "'")
# . write-buffered(Stderr, "error: missing final ')' in '")
# . . push args
68/push "error: missing final ')' in '"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-stream-data(Stderr, line)
# . . push args
56/push-esi
68/push Stderr/imm32
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-buffered(Stderr, "'")
# . . push args
68/push "'"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
$next-word-string-or-expression-without-metadata:error1:
# print(stderr, "error: no space allowed after '*' in '" line "'")
# . write-buffered(Stderr, "error: no space allowed after '*' in '")
# . . push args
68/push "error: no space allowed after '*' in '"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-stream-data(Stderr, line)
# . . push args
56/push-esi
68/push Stderr/imm32
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-buffered(Stderr, "'")
# . . push args
68/push "'"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
$next-word-string-or-expression-without-metadata:error2:
# print(stderr, "error: *(...) expression must be all on a single line in '" line "'")
# . write-buffered(Stderr, "error: *(...) expression must be all on a single line in '")
# . . push args
68/push "error: *(...) expression must be all on a single line in '"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-stream-data(Stderr, line)
# . . push args
56/push-esi
68/push Stderr/imm32
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-buffered(Stderr, "'")
# . . push args
68/push "'"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
$next-word-string-or-expression-without-metadata:error3:
# print(stderr, "error: no metadata after calls; just use a comment (in '" line "')")
# . write-buffered(Stderr, "error: no metadata after calls; just use a comment (in '")
# . . push args
68/push "error: no metadata after calls; just use a comment (in '"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-stream-data(Stderr, line)
# . . push args
56/push-esi
68/push Stderr/imm32
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-buffered(Stderr, "')")
# . . push args
68/push "')"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
$next-word-string-or-expression-without-metadata:error4:
# print(stderr, "error: unexpected text after end of call in '" line "'")
# . write-buffered(Stderr, "error: unexpected text after end of call in '")
# . . push args
68/push "error: unexpected text after end of call in '"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-stream-data(Stderr, line)
# . . push args
56/push-esi
68/push Stderr/imm32
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-buffered(Stderr, "'")
# . . push args
68/push "'"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
$next-word-string-or-expression-without-metadata:error5:
# print(stderr, "error: no metadata anywhere in calls (in '" line "')")
# . write-buffered(Stderr, "error: no metadata anywhere in calls (in '")
# . . push args
68/push "error: no metadata anywhere in calls (in '"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-stream-data(Stderr, line)
# . . push args
56/push-esi
68/push Stderr/imm32
# . . call
e8/call write-stream-data/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . write-buffered(Stderr, "')")
# . . push args
68/push "')"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
test-next-word-string-or-expression-without-metadata:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " ab")
# . . push args
68/push " ab"/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(_test-input-stream->read, 4, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata/updates-stream-read-correctly"/imm32
68/push 4/imm32
b8/copy-to-eax _test-input-stream/imm32
ff 6/subop/push *(eax+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
# . check-ints-equal(slice->start - _test-input-stream, 14, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata: start"/imm32
68/push 0xe/imm32
# . . push slice->start - _test-input-stream
8b/-> *ecx 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end - _test-input-stream->data, 4, msg)
# . check-ints-equal(slice->end - _test-input-stream, 16, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata: end"/imm32
68/push 0x10/imm32
# . . push slice->end - _test-input-stream
8b/-> *(ecx+4) 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-returns-whole-comment:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " # a")
# . . push args
68/push " # a"/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(_test-input-stream->read, 5, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-comment/updates-stream-read-correctly"/imm32
68/push 5/imm32
b8/copy-to-eax _test-input-stream/imm32
ff 6/subop/push *(eax+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
# . check-ints-equal(slice->start - _test-input-stream, 14, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-comment: start"/imm32
68/push 0xe/imm32
# . . push slice->start - _test-input-stream
8b/-> *ecx 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end - _test-input-stream->data, 5, msg)
# . check-ints-equal(slice->end - _test-input-stream, 17, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-comment: end"/imm32
68/push 0x11/imm32
# . . push slice->end - _test-input-stream
8b/-> *(ecx+4) 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-returns-string-literal:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " \"a b\" ")
# . . push args
68/push " \"a b\" "/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
# . check-ints-equal(slice->start - _test-input-stream, 13, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-literal: start"/imm32
68/push 0xd/imm32
# . . push slice->start - _test-input-stream
8b/-> *ecx 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end - _test-input-stream->data, 6, msg)
# . check-ints-equal(slice->end - _test-input-stream, 18, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-literal: end"/imm32
68/push 0x12/imm32
# . . push slice->end - _test-input-stream
8b/-> *(ecx+4) 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-returns-string-with-escapes:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " \"a\\\"b\"")
# . . push args
68/push " \"a\\\"b\""/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
# . check-ints-equal(slice->start - _test-input-stream, 13, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-with-escapes: start"/imm32
68/push 0xd/imm32
# . . push slice->start - _test-input-stream
8b/-> *ecx 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end - _test-input-stream->data, 7, msg)
# . check-ints-equal(slice->end - _test-input-stream, 19, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-with-escapes: end"/imm32
68/push 0x13/imm32
# . . push slice->end - _test-input-stream
8b/-> *(ecx+4) 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-returns-whole-expression:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " *(a b) ")
# . . push args
68/push " *(a b) "/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
# . check-ints-equal(slice->start - _test-input-stream, 13, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-expression: start"/imm32
68/push 0xd/imm32
# . . push slice->start - _test-input-stream
8b/-> *ecx 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end - _test-input-stream->data, 7, msg)
# . check-ints-equal(slice->end - _test-input-stream, 19, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-expression: end"/imm32
68/push 0x13/imm32
# . . push slice->end - _test-input-stream
8b/-> *(ecx+4) 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-returns-eol-on-trailing-close-paren:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " ) ")
# . . push args
68/push " ) "/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(slice->start, 0, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-eol-on-trailing-close-paren: start"/imm32
68/push 0/imm32
ff 6/subop/push *ecx
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end, 0, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-returns-eol-on-trailing-close-paren: end"/imm32
68/push 0/imm32
ff 6/subop/push *(ecx+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-handles-comment-after-trailing-close-paren:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " ) # abc ")
# . . push args
68/push " ) # abc "/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(slice->start, 0, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-handles-comment-after-trailing-close-paren: start"/imm32
68/push 0/imm32
ff 6/subop/push *ecx
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end, 0, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-handles-comment-after-trailing-close-paren: end"/imm32
68/push 0/imm32
ff 6/subop/push *(ecx+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-handles-newline-after-trailing-close-paren:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " )\n")
# . . push args
68/push " )\n"/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(slice->start, 0, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-handles-newline-after-trailing-close-paren: start"/imm32
68/push 0/imm32
ff 6/subop/push *ecx
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end, 0, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-handles-newline-after-trailing-close-paren: end"/imm32
68/push 0/imm32
ff 6/subop/push *(ecx+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-next-word-string-or-expression-without-metadata-stops-at-close-paren:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
# . clear-stream(_test-input-stream)
# . . push args
68/push _test-input-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %ecx 4/r32/esp
# write(_test-input-stream, " abc) # def")
# . . push args
68/push " abc) # def"/imm32
68/push _test-input-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# next-word-string-or-expression-without-metadata(_test-input-stream, slice)
# . . push args
51/push-ecx
68/push _test-input-stream/imm32
# . . call
e8/call next-word-string-or-expression-without-metadata/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
# . check-ints-equal(slice->start - _test-input-stream, 13, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-stops-at-close-paren: start"/imm32
68/push 0xd/imm32
# . . push slice->start - _test-input-stream
8b/-> *ecx 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# check-ints-equal(slice->end - _test-input-stream->data, 4, msg)
# . check-ints-equal(slice->end - _test-input-stream, 16, msg)
# . . push args
68/push "F - test-next-word-string-or-expression-without-metadata-stops-at-close-paren: end"/imm32
68/push 0x10/imm32
# . . push slice->end - _test-input-stream
8b/-> *(ecx+4) 0/r32/eax
81 5/subop/subtract %eax _test-input-stream/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add %esp 0xc/imm32
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return