From 93f329328a48ed1358085a643a78100b284d7f52 Mon Sep 17 00:00:00 2001 From: kamkow1 Date: Sun, 9 Mar 2025 15:22:48 +0100 Subject: [PATCH] Prompt to embedded mujs engine --- .gitignore | 3 +- Makefile | 30 +- debugus.c | 84 +- linenoise.c | 1349 +++++++++++++++++++++ linenoise.h | 113 ++ mujs/.gitattributes | 7 + mujs/.gitignore | 12 + mujs/AUTHORS | 1 + mujs/COPYING | 16 + mujs/Makefile | 170 +++ mujs/README | 50 + mujs/astnames.h | 92 ++ mujs/docs/artifex-logo.png | Bin 0 -> 1584 bytes mujs/docs/examples.html | 224 ++++ mujs/docs/index.html | 58 + mujs/docs/introduction.html | 108 ++ mujs/docs/license.html | 50 + mujs/docs/logo.ps | 47 + mujs/docs/mujs-logo.png | Bin 0 -> 6007 bytes mujs/docs/reference.html | 719 ++++++++++++ mujs/docs/style.css | 40 + mujs/genucd.py | 79 ++ mujs/jsarray.c | 832 +++++++++++++ mujs/jsboolean.c | 38 + mujs/jsbuiltin.c | 249 ++++ mujs/jscompile.c | 1428 +++++++++++++++++++++++ mujs/jsdate.c | 861 ++++++++++++++ mujs/jsdtoa.c | 749 ++++++++++++ mujs/jserror.c | 128 ++ mujs/jsfunction.c | 231 ++++ mujs/jsgc.c | 284 +++++ mujs/jsi.h | 867 ++++++++++++++ mujs/jsintern.c | 137 +++ mujs/jslex.c | 878 ++++++++++++++ mujs/jsmath.c | 194 +++ mujs/jsnumber.c | 198 ++++ mujs/jsobject.c | 560 +++++++++ mujs/json.c | 422 +++++++ mujs/jsparse.c | 1065 +++++++++++++++++ mujs/jsproperty.c | 341 ++++++ mujs/jsregexp.c | 232 ++++ mujs/jsrepr.c | 285 +++++ mujs/jsrun.c | 2063 ++++++++++++++++++++++++++++++++ mujs/jsstate.c | 334 ++++++ mujs/jsstring.c | 798 +++++++++++++ mujs/jsvalue.c | 708 +++++++++++ mujs/main.c | 394 +++++++ mujs/mujs.h | 252 ++++ mujs/one.c | 25 + mujs/opnames.h | 85 ++ mujs/pp.c | 980 ++++++++++++++++ mujs/regexp.c | 1277 ++++++++++++++++++++ mujs/regexp.h | 46 + mujs/tools/test262 | 139 +++ mujs/tools/test262-harness.js | 152 +++ mujs/utf.c | 285 +++++ mujs/utf.h | 48 + mujs/utfdata.h | 2073 +++++++++++++++++++++++++++++++++ 58 files changed, 22871 insertions(+), 19 deletions(-) create mode 100644 linenoise.c create mode 100644 linenoise.h create mode 100644 mujs/.gitattributes create mode 100644 mujs/.gitignore create mode 100644 mujs/AUTHORS create mode 100644 mujs/COPYING create mode 100644 mujs/Makefile create mode 100644 mujs/README create mode 100644 mujs/astnames.h create mode 100755 mujs/docs/artifex-logo.png create mode 100644 mujs/docs/examples.html create mode 100644 mujs/docs/index.html create mode 100644 mujs/docs/introduction.html create mode 100644 mujs/docs/license.html create mode 100644 mujs/docs/logo.ps create mode 100644 mujs/docs/mujs-logo.png create mode 100644 mujs/docs/reference.html create mode 100644 mujs/docs/style.css create mode 100644 mujs/genucd.py create mode 100644 mujs/jsarray.c create mode 100644 mujs/jsboolean.c create mode 100644 mujs/jsbuiltin.c create mode 100644 mujs/jscompile.c create mode 100644 mujs/jsdate.c create mode 100644 mujs/jsdtoa.c create mode 100644 mujs/jserror.c create mode 100644 mujs/jsfunction.c create mode 100644 mujs/jsgc.c create mode 100644 mujs/jsi.h create mode 100644 mujs/jsintern.c create mode 100644 mujs/jslex.c create mode 100644 mujs/jsmath.c create mode 100644 mujs/jsnumber.c create mode 100644 mujs/jsobject.c create mode 100644 mujs/json.c create mode 100644 mujs/jsparse.c create mode 100644 mujs/jsproperty.c create mode 100644 mujs/jsregexp.c create mode 100644 mujs/jsrepr.c create mode 100644 mujs/jsrun.c create mode 100644 mujs/jsstate.c create mode 100644 mujs/jsstring.c create mode 100644 mujs/jsvalue.c create mode 100644 mujs/main.c create mode 100644 mujs/mujs.h create mode 100644 mujs/one.c create mode 100644 mujs/opnames.h create mode 100644 mujs/pp.c create mode 100644 mujs/regexp.c create mode 100644 mujs/regexp.h create mode 100755 mujs/tools/test262 create mode 100644 mujs/tools/test262-harness.js create mode 100644 mujs/utf.c create mode 100644 mujs/utf.h create mode 100644 mujs/utfdata.h diff --git a/.gitignore b/.gitignore index 308a44c..c006f14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -debugus.o +*.o +*.d debugus diff --git a/Makefile b/Makefile index 6c9ac56..796fa35 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,23 @@ CC=gcc -CFLAGS= -INCFLAGS= -LDFLAGS= -SRCS=debugus.c +CFLAGS=-MD -MP -I./mujs +LDFLAGS=-lm +SRCS=debugus.c linenoise.c OBJS=$(patsubst %.c,%.o,$(SRCS)) +DEPS=$(patsubst %.c,%.d,$(SRCS)) -all: debugus .gitignore +all: debugus -.gitignore: $(OBJS) debugus - @echo $^ | tr ' ' '\n' > .gitignore - @echo "MK .gitignore" +debugus: $(OBJS) ./mujs/build/debug/libmujs.o + $(CC) -o $@ $^ $(LDFLAGS) -debugus: $(OBJS) - @$(CC) -o $@ $^ $(LDFLAGS) - @echo "LD $^" +./mujs/build/debug/libmujs.o: + make -C mujs -j$(shell nproc) -$(OBJS): $(SRCS) - @$(CC) -c $(CFLAGS) $(INCFLAGS) -o $@ $< - @echo "CC $<" +-include $(DEPS) -clean: $(OBJS) .gitignore - @rm -f $^ - @echo "RM $^" +clean: $(OBJS) $(DEPS) + rm -f $^ + make -C mujs clean .PHONY: all clean diff --git a/debugus.c b/debugus.c index 1e562d6..8a6dad8 100644 --- a/debugus.c +++ b/debugus.c @@ -1,6 +1,88 @@ #include +#include +#include +#include +#include +#include -int main(void) +#include "linenoise.h" +#include "mujs.h" + +#define LOG_ERR(fmt, ...) fprintf(stderr, "Error: " fmt, ##__VA_ARGS__) +#define LOG_INF(fmt, ...) fprintf(stdout, "Info: " fmt, ##__VA_ARGS__) + +#define unused(x) ((void)(x)) + +#define shift(argc, argv, msg, ...) (argc <= 0 ? (LOG_ERR(msg, ##__VA_ARGS__), exit(EXIT_FAILURE)) : (void)0, argc--, *argv++) + +typedef struct { + const char *file; + pid_t pid; + js_State *js; +} Dbg; + +void dbg_js_print_file(js_State *js) { + void *data = js_currentfunctiondata(js); + Dbg *dbg = (Dbg *)data; + + printf("File: %s\n", dbg->file); +} + +void dbg_init_js(Dbg *dbg) +{ + dbg->js = js_newstate(NULL, NULL, JS_STRICT); + + js_newcfunctionx(dbg->js, &dbg_js_print_file, "print_file", 0, dbg, NULL); + js_setglobal(dbg->js, "print_file"); +} + +void dbg_init(Dbg *dbg, const char *file, pid_t pid) +{ + dbg->file = file; + dbg->pid = pid; + dbg_init_js(dbg); +} + +void dbg_deinit(Dbg *dbg) +{ + js_freestate(dbg->js); +} + +void dbg_loop(Dbg *dbg) +{ + int status, options = 0; + waitpid(dbg->pid, &status, options); + + char *line = NULL; + while ((line = linenoise("debugus # ")) != NULL) { + js_dostring(dbg->js, line); + linenoiseHistoryAdd(line); + linenoiseFree(line); + } +} + +void start_debugging(const char *input_file_path) +{ + pid_t pid = fork(); + if (pid == 0) { + /* ptrace(PTRACE_TRACEME, 0, NULL, NULL); */ + /* execl(input_file_path, input_file_path, NULL); */ + } else if (pid >= 1) { + Dbg dbg; + dbg_init(&dbg, input_file_path, pid); + dbg_loop(&dbg); + dbg_deinit(&dbg); + } +} + +int main(int argc, char ** argv) +{ + const char *program_name = shift(argc, argv, ""); + unused(program_name); + + const char *input_file_path = shift(argc, argv, "No input file provided\n"); + start_debugging(input_file_path); + return 0; } diff --git a/linenoise.c b/linenoise.c new file mode 100644 index 0000000..574ab1f --- /dev/null +++ b/linenoise.c @@ -0,0 +1,1349 @@ +/* linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2023, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (Cursor position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linenoise.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 +static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +static linenoiseCompletionCallback *completionCallback = NULL; +static linenoiseHintsCallback *hintsCallback = NULL; +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; +static char *linenoiseNoTTY(void); +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags); +static void refreshLineWithFlags(struct linenoiseState *l, int flags); + +static struct termios orig_termios; /* In order to restore at exit.*/ +static int maskmode = 0; /* Show "***" instead of input. For passwords. */ +static int rawmode = 0; /* For atexit() function to check if restore is needed*/ +static int mlmode = 0; /* Multi line mode. Default is single line. */ +static int atexit_registered = 0; /* Register atexit just 1 time. */ +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +static void linenoiseAtExit(void); +int linenoiseHistoryAdd(const char *line); +#define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen +#define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen. +#define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both. +static void refreshLine(struct linenoiseState *l); + +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ + (int)l->oldrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ======================= Low level terminal handling ====================== */ + +/* Enable "mask mode". When it is enabled, instead of the input that + * the user is typing, the terminal will just display a corresponding + * number of asterisks, like "****". This is useful for passwords and other + * secrets that should not be displayed. */ +void linenoiseMaskModeEnable(void) { + maskmode = 1; +} + +/* Disable mask mode. */ +void linenoiseMaskModeDisable(void) { + maskmode = 0; +} + +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; +} + +/* Return true if the terminal name is in the list of terminals we know are + * not able to understand basic escape sequences. */ +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + int j; + + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; + return 0; +} + +/* Raw mode: 1960 magic shit. */ +static int enableRawMode(int fd) { + struct termios raw; + + if (!isatty(STDIN_FILENO)) goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; + rawmode = 1; + return 0; + +fatal: + errno = ENOTTY; + return -1; +} + +static void disableRawMode(int fd) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) + rawmode = 0; +} + +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. */ +static int getCursorPosition(int ifd, int ofd) { + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf)-1) { + if (read(ifd,buf+i,1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; + return cols; +} + +/* Try to get the number of columns in the current terminal, or assume 80 + * if it fails. */ +static int getColumns(int ifd, int ofd) { + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd,ofd); + if (start == -1) goto failed; + + /* Go to right margin and get position. */ + if (write(ofd,"\x1b[999C",6) != 6) goto failed; + cols = getCursorPosition(ifd,ofd); + if (cols == -1) goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq,32,"\x1b[%dD",cols-start); + if (write(ofd,seq,strlen(seq)) == -1) { + /* Can't recover... */ + } + } + return cols; + } else { + return ws.ws_col; + } + +failed: + return 80; +} + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + /* nothing to do, just to avoid warning. */ + } +} + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + fprintf(stderr, "\x7"); + fflush(stderr); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +/* Called by completeLine() and linenoiseShow() to render the current + * edited line with the proposed completion. If the current completion table + * is already available, it is passed as second argument, otherwise the + * function will use the callback to obtain it. + * + * Flags are the same as refreshLine*(), that is REFRESH_* macros. */ +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) { + /* Obtain the table of completions if the caller didn't provide one. */ + linenoiseCompletions ctable = { 0, NULL }; + if (lc == NULL) { + completionCallback(ls->buf,&ctable); + lc = &ctable; + } + + /* Show the edited line with completion if possible, or just refresh. */ + if (ls->completion_idx < lc->len) { + struct linenoiseState saved = *ls; + ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]); + ls->buf = lc->cvec[ls->completion_idx]; + refreshLineWithFlags(ls,flags); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLineWithFlags(ls,flags); + } + + /* Free the completions table if needed. */ + if (lc != &ctable) freeCompletions(&ctable); +} + +/* This is an helper function for linenoiseEdit*() and is called when the + * user types the key in order to complete the string currently in the + * input. + * + * The state of the editing is encapsulated into the pointed linenoiseState + * structure as described in the structure definition. + * + * If the function returns non-zero, the caller should handle the + * returned value as a byte read from the standard input, and process + * it as usually: this basically means that the function may return a byte + * read from the termianl but not processed. Otherwise, if zero is returned, + * the input was consumed by the completeLine() function to navigate the + * possible completions, and the caller should read for the next characters + * from stdin. */ +static int completeLine(struct linenoiseState *ls, int keypressed) { + linenoiseCompletions lc = { 0, NULL }; + int nwritten; + char c = keypressed; + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + ls->in_completion = 0; + } else { + switch(c) { + case 9: /* tab */ + if (ls->in_completion == 0) { + ls->in_completion = 1; + ls->completion_idx = 0; + } else { + ls->completion_idx = (ls->completion_idx+1) % (lc.len+1); + if (ls->completion_idx == lc.len) linenoiseBeep(); + } + c = 0; + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (ls->completion_idx < lc.len) refreshLine(ls); + ls->in_completion = 0; + c = 0; + break; + default: + /* Update buffer and return */ + if (ls->completion_idx < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s", + lc.cvec[ls->completion_idx]); + ls->len = ls->pos = nwritten; + } + ls->in_completion = 0; + break; + } + + /* Show completion or original buffer */ + if (ls->in_completion && ls->completion_idx < lc.len) { + refreshLineWithCompletion(ls,&lc,REFRESH_ALL); + } else { + refreshLine(ls); + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +/* Register a hits function to be called to show hits to the user at the + * right of the prompt. */ +void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { + hintsCallback = fn; +} + +/* Register a function to free the hints returned by the hints callback + * registered with linenoiseSetHintsCallback(). */ +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { + freeHintsCallback = fn; +} + +/* This function is used by the callback function registered by the user + * in order to add completion options given the input string when the + * user typed . See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + size_t len = strlen(str); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; + memcpy(copy,str,len+1); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; + lc->cvec[lc->len++] = copy; +} + +/* =========================== Line editing ================================= */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + +/* Helper of refreshSingleLine() and refreshMultiLine() to show hints + * to the right of the prompt. */ +void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { + char seq[64]; + if (hintsCallback && plen+l->len < l->cols) { + int color = -1, bold = 0; + char *hint = hintsCallback(l->buf,&color,&bold); + if (hint) { + int hintlen = strlen(hint); + int hintmaxlen = l->cols-(plen+l->len); + if (hintlen > hintmaxlen) hintlen = hintmaxlen; + if (bold == 1 && color == -1) color = 37; + if (color != -1 || bold != 0) + snprintf(seq,64,"\033[%d;%d;49m",bold,color); + else + seq[0] = '\0'; + abAppend(ab,seq,strlen(seq)); + abAppend(ab,hint,hintlen); + if (color != -1 || bold != 0) + abAppend(ab,"\033[0m",4); + /* Call the function to free the hint returned. */ + if (freeHintsCallback) freeHintsCallback(hint); + } + } +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshSingleLine(struct linenoiseState *l, int flags) { + char seq[64]; + size_t plen = strlen(l->prompt); + int fd = l->ofd; + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + abInit(&ab); + /* Cursor to left edge */ + snprintf(seq,sizeof(seq),"\r"); + abAppend(&ab,seq,strlen(seq)); + + if (flags & REFRESH_WRITE) { + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + while (len--) abAppend(&ab,"*",1); + } else { + abAppend(&ab,buf,len); + } + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + } + + /* Erase to right */ + snprintf(seq,sizeof(seq),"\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + + if (flags & REFRESH_WRITE) { + /* Move cursor to original position. */ + snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + } + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshMultiLine(struct linenoiseState *l, int flags) { + char seq[64]; + int plen = strlen(l->prompt); + int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* colum position, zero-based. */ + int old_rows = l->oldrows; + int fd = l->ofd, j; + struct abuf ab; + + l->oldrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + abInit(&ab); + + if (flags & REFRESH_CLEAN) { + if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } + } + + if (flags & REFRESH_ALL) { + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + } + + if (flags & REFRESH_WRITE) { + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + unsigned int i; + for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); + } else { + abAppend(&ab,l->buf,l->len); + } + + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && + l->pos == l->len && + (l->pos+plen) % l->cols == 0) + { + lndebug(""); + abAppend(&ab,"\n",1); + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->oldrows) l->oldrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* Current cursor relative row */ + lndebug("rpos2 %d", rpos2); + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } + + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + } + + lndebug("\n"); + l->oldpos = l->pos; + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLineWithFlags(struct linenoiseState *l, int flags) { + if (mlmode) + refreshMultiLine(l,flags); + else + refreshSingleLine(l,flags); +} + +/* Utility function to avoid specifying REFRESH_ALL all the times. */ +static void refreshLine(struct linenoiseState *l) { + refreshLineWithFlags(l,REFRESH_ALL); +} + +/* Hide the current line, when using the multiplexing API. */ +void linenoiseHide(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l,REFRESH_CLEAN); + else + refreshSingleLine(l,REFRESH_CLEAN); +} + +/* Show the current line, when using the multiplexing API. */ +void linenoiseShow(struct linenoiseState *l) { + if (l->in_completion) { + refreshLineWithCompletion(l,NULL,REFRESH_WRITE); + } else { + refreshLineWithFlags(l,REFRESH_WRITE); + } +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +int linenoiseEditInsert(struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { + /* Avoid a full update of the line in the + * trivial case. */ + char d = (maskmode==1) ? '*' : c; + if (write(l->ofd,&d,1) == -1) return -1; + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +void linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +/* This function is part of the multiplexed API of Linenoise, that is used + * in order to implement the blocking variant of the API but can also be + * called by the user directly in an event driven program. It will: + * + * 1. Initialize the linenoise state passed by the user. + * 2. Put the terminal in RAW mode. + * 3. Show the prompt. + * 4. Return control to the user, that will have to call linenoiseEditFeed() + * each time there is some data arriving in the standard input. + * + * The user can also call linenoiseEditHide() and linenoiseEditShow() if it + * is required to show some input arriving asyncronously, without mixing + * it with the currently edited line. + * + * When linenoiseEditFeed() returns non-NULL, the user finished with the + * line editing session (pressed enter CTRL-D/C): in this case the caller + * needs to call linenoiseEditStop() to put back the terminal in normal + * mode. This will not destroy the buffer, as long as the linenoiseState + * is still valid in the context of the caller. + * + * The function returns 0 on success, or -1 if writing to standard output + * fails. If stdin_fd or stdout_fd are set to -1, the default is to use + * STDIN_FILENO and STDOUT_FILENO. + */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l->in_completion = 0; + l->ifd = stdin_fd != -1 ? stdin_fd : STDIN_FILENO; + l->ofd = stdout_fd != -1 ? stdout_fd : STDOUT_FILENO; + l->buf = buf; + l->buflen = buflen; + l->prompt = prompt; + l->plen = strlen(prompt); + l->oldpos = l->pos = 0; + l->len = 0; + + /* Enter raw mode. */ + if (enableRawMode(l->ifd) == -1) return -1; + + l->cols = getColumns(stdin_fd, stdout_fd); + l->oldrows = 0; + l->history_index = 0; + + /* Buffer starts empty. */ + l->buf[0] = '\0'; + l->buflen--; /* Make sure there is always space for the nulterm */ + + /* If stdin is not a tty, stop here with the initialization. We + * will actually just read a line from standard input in blocking + * mode later, in linenoiseEditFeed(). */ + if (!isatty(l->ifd)) return 0; + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + if (write(l->ofd,prompt,l->plen) == -1) return -1; + return 0; +} + +char *linenoiseEditMore = "If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information."; + +/* This function is part of the multiplexed API of linenoise, see the top + * comment on linenoiseEditStart() for more information. Call this function + * each time there is some data to read from the standard input file + * descriptor. In the case of blocking operations, this function can just be + * called in a loop, and block. + * + * The function returns linenoiseEditMore to signal that line editing is still + * in progress, that is, the user didn't yet pressed enter / CTRL-D. Otherwise + * the function returns the pointer to the heap-allocated buffer with the + * edited line, that the user should free with linenoiseFree(). + * + * On special conditions, NULL is returned and errno is populated: + * + * EAGAIN if the user pressed Ctrl-C + * ENOENT if the user pressed Ctrl-D + * + * Some other errno: I/O error. + */ +char *linenoiseEditFeed(struct linenoiseState *l) { + /* Not a TTY, pass control to line reading without character + * count limits. */ + if (!isatty(l->ifd)) return linenoiseNoTTY(); + + char c; + int nread; + char seq[3]; + + nread = read(l->ifd,&c,1); + if (nread <= 0) return NULL; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if ((l->in_completion || c == 9) && completionCallback != NULL) { + c = completeLine(l,c); + /* Return on errors */ + if (c < 0) return NULL; + /* Read next character when 0 */ + if (c == 0) return linenoiseEditMore; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(l); + hintsCallback = hc; + } + return strdup(l->buf); + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return NULL; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l->len > 0) { + linenoiseEditDelete(l); + } else { + history_len--; + free(history[history_len]); + errno = ENOENT; + return NULL; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l->pos > 0 && l->pos < l->len) { + int aux = l->buf[l->pos-1]; + l->buf[l->pos-1] = l->buf[l->pos]; + l->buf[l->pos] = aux; + if (l->pos != l->len-1) l->pos++; + refreshLine(l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l->ifd,seq,1) == -1) break; + if (read(l->ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l->ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + break; + default: + if (linenoiseEditInsert(l,c)) return NULL; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + l->buf[0] = '\0'; + l->pos = l->len = 0; + refreshLine(l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + l->buf[l->pos] = '\0'; + l->len = l->pos; + refreshLine(l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(l); + break; + } + return linenoiseEditMore; +} + +/* This is part of the multiplexed linenoise API. See linenoiseEditStart() + * for more information. This function is called when linenoiseEditFeed() + * returns something different than NULL. At this point the user input + * is in the buffer, and we can restore the terminal in normal mode. */ +void linenoiseEditStop(struct linenoiseState *l) { + if (!isatty(l->ifd)) return; + disableRawMode(l->ifd); + printf("\n"); +} + +/* This just implements a blocking loop for the multiplexed API. + * In many applications that are not event-drivern, we can just call + * the blocking linenoise API, wait for the user to complete the editing + * and return the buffer. */ +static char *linenoiseBlockingEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Editing without a buffer is invalid. */ + if (buflen == 0) { + errno = EINVAL; + return NULL; + } + + linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt); + char *res; + while((res = linenoiseEditFeed(&l)) == linenoiseEditMore); + linenoiseEditStop(&l); + return res; +} + +/* This special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode(STDIN_FILENO) == -1) return; + memset(quit,' ',4); + while(1) { + char c; + int nread; + + nread = read(STDIN_FILENO,&c,1); + if (nread <= 0) continue; + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", + isprint(c) ? c : '?', (int)c, (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(STDIN_FILENO); +} + +/* This function is called when linenoise() is called with the standard + * input file descriptor not attached to a TTY. So for example when the + * program using linenoise is called in pipe or with a file redirected + * to its standard input. In this case, we want to be able to return the + * line regardless of its length (by default we are limited to 4k). */ +static char *linenoiseNoTTY(void) { + char *line = NULL; + size_t len = 0, maxlen = 0; + + while(1) { + if (len == maxlen) { + if (maxlen == 0) maxlen = 16; + maxlen *= 2; + char *oldval = line; + line = realloc(line,maxlen); + if (line == NULL) { + if (oldval) free(oldval); + return NULL; + } + } + int c = fgetc(stdin); + if (c == EOF || c == '\n') { + if (c == EOF && len == 0) { + free(line); + return NULL; + } else { + line[len] = '\0'; + return line; + } + } else { + line[len] = c; + len++; + } + } +} + +/* The high level function that is the main API of the linenoise library. + * This function checks if the terminal has basic capabilities, just checking + * for a blacklist of stupid terminals, and later either calls the line + * editing function or uses dummy fgets() so that you will be able to type + * something even in the most desperate of the conditions. */ +char *linenoise(const char *prompt) { + char buf[LINENOISE_MAX_LINE]; + + if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. In this mode we don't want any + * limit to the line size, so we call a function to handle that. */ + return linenoiseNoTTY(); + } else if (isUnsupportedTerm()) { + size_t len; + + printf("%s",prompt); + fflush(stdout); + if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; + len = strlen(buf); + while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { + len--; + buf[len] = '\0'; + } + return strdup(buf); + } else { + char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt); + return retval; + } +} + +/* This is just a wrapper the user may want to call in order to make sure + * the linenoise returned buffer is freed with the same allocator it was + * created with. Useful when the main program is using an alternative + * allocator. */ +void linenoiseFree(void *ptr) { + if (ptr == linenoiseEditMore) return; // Protect from API misuse. + free(ptr); +} + +/* ================================ History ================================= */ + +/* Free the history, but does not reset it. Only used when we have to + * exit() to avoid memory leaks are reported by valgrind & co. */ +static void freeHistory(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + } +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + disableRawMode(STDIN_FILENO); + freeHistory(); +} + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + if (history_max_len == 0) return 0; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == history_max_len) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +/* Set the maximum length for the history. This function can be called even + * if there is already some history, the function will make sure to retain + * just the latest 'len' elements if the new history length value is smaller + * than the amount of items already inside the history. */ +int linenoiseHistorySetMaxLen(int len) { + char **new; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + new = malloc(sizeof(char*)*len); + if (new == NULL) return 0; + + /* If we can't copy everything, free the elements we'll not use. */ + if (len < tocopy) { + int j; + + for (j = 0; j < tocopy-len; j++) free(history[j]); + tocopy = len; + } + memset(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = new; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); + FILE *fp; + int j; + + fp = fopen(filename,"w"); + umask(old_umask); + if (fp == NULL) return -1; + chmod(filename,S_IRUSR|S_IWUSR); + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename,"r"); + char buf[LINENOISE_MAX_LINE]; + + if (fp == NULL) return -1; + + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} diff --git a/linenoise.h b/linenoise.h new file mode 100644 index 0000000..3f0270e --- /dev/null +++ b/linenoise.h @@ -0,0 +1,113 @@ +/* linenoise.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2023, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* For size_t. */ + +extern char *linenoiseEditMore; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int in_completion; /* The user pressed TAB and we are now in completion + * mode, so input is handled by completeLine(). */ + size_t completion_idx; /* Index of next completion to propose. */ + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +/* Non blocking API. */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt); +char *linenoiseEditFeed(struct linenoiseState *l); +void linenoiseEditStop(struct linenoiseState *l); +void linenoiseHide(struct linenoiseState *l); +void linenoiseShow(struct linenoiseState *l); + +/* Blocking API. */ +char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); + +/* Completion API. */ +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); +typedef void(linenoiseFreeHintsCallback)(void *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseSetHintsCallback(linenoiseHintsCallback *); +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); + +/* History API. */ +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); + +/* Other utilities. */ +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); +void linenoiseMaskModeEnable(void); +void linenoiseMaskModeDisable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */ diff --git a/mujs/.gitattributes b/mujs/.gitattributes new file mode 100644 index 0000000..46fe0b3 --- /dev/null +++ b/mujs/.gitattributes @@ -0,0 +1,7 @@ +# Define macro for whitespace settings: +[attr]tabs whitespace=trailing-space,space-before-tab,indent-with-non-tab + +* text=auto +Makefile tabs +*.[ch] tabs +*.js tabs diff --git a/mujs/.gitignore b/mujs/.gitignore new file mode 100644 index 0000000..a90efe4 --- /dev/null +++ b/mujs/.gitignore @@ -0,0 +1,12 @@ +# Optional testsuites +tests +specs + +# Generated files: +build +tags + +# Editor settings: +user.make +.exrc +.vimrc diff --git a/mujs/AUTHORS b/mujs/AUTHORS new file mode 100644 index 0000000..593c93f --- /dev/null +++ b/mujs/AUTHORS @@ -0,0 +1 @@ +Tor Andersson diff --git a/mujs/COPYING b/mujs/COPYING new file mode 100644 index 0000000..aa8805f --- /dev/null +++ b/mujs/COPYING @@ -0,0 +1,16 @@ +ISC License + +Copyright (c) 2013-2020 Artifex Software, Inc. + +Permission to use, copy, modify, and/or 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. + diff --git a/mujs/Makefile b/mujs/Makefile new file mode 100644 index 0000000..95ccdc3 --- /dev/null +++ b/mujs/Makefile @@ -0,0 +1,170 @@ +# Makefile for building MuJS libraries, shell, and pretty-printer. +# +# Useful targets are: release, install, uninstall. + +default: build/debug/mujs build/debug/mujs-pp + +CFLAGS = -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter + +OPTIM = -O3 + +prefix = /usr/local +bindir = $(prefix)/bin +incdir = $(prefix)/include +libdir = $(prefix)/lib + +ifeq ($(wildcard .git),.git) + VERSION = $(shell git describe --tags --always) +else + VERSION = $(patsubst mujs-%,%,$(notdir $(CURDIR))) +endif + +ifeq ($(shell uname),Darwin) + SO = dylib +else + SO = so +endif + +ifeq ($(shell uname),FreeBSD) + CFLAGS += -I/usr/local/include -L/usr/local/lib +endif + +HDRS = mujs.h jsi.h regexp.h utf.h astnames.h opnames.h + +ifneq ($(HAVE_READLINE),no) + READLINE_CFLAGS = -DHAVE_READLINE + READLINE_LIBS = -lreadline +endif + +SRCS = \ + jsarray.c \ + jsboolean.c \ + jsbuiltin.c \ + jscompile.c \ + jsdate.c \ + jsdtoa.c \ + jserror.c \ + jsfunction.c \ + jsgc.c \ + jsintern.c \ + jslex.c \ + jsmath.c \ + jsnumber.c \ + jsobject.c \ + json.c \ + jsparse.c \ + jsproperty.c \ + jsregexp.c \ + jsrepr.c \ + jsrun.c \ + jsstate.c \ + jsstring.c \ + jsvalue.c \ + regexp.c \ + utf.c + +one.c: + for F in $(SRCS); do echo "#include \"$$F\""; done > $@ + +astnames.h: jsi.h + grep -E '\<(AST|EXP|STM)_' jsi.h | sed 's/^[^A-Z]*\(AST_\)*/"/;s/,.*/",/' | tr A-Z a-z > $@ + +opnames.h: jsi.h + grep -E '\ $@ + +UnicodeData.txt: + curl -s -o $@ https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt + +utfdata.h: genucd.py UnicodeData.txt + python3 genucd.py UnicodeData.txt >$@ + +build/sanitize/mujs: main.c one.c $(SRCS) $(HDRS) + @mkdir -p $(@D) + $(CC) $(CFLAGS) -g -fsanitize=address -fno-omit-frame-pointer -o $@ main.c one.c -lm $(READLINE_CFLAGS) $(READLINE_LIBS) + +build/debug/libmujs.$(SO): one.c $(SRCS) $(HDRS) + @mkdir -p $(@D) + $(CC) $(CFLAGS) -g -fPIC -shared -o $@ one.c -lm +build/debug/libmujs.o: one.c $(SRCS) $(HDRS) + @mkdir -p $(@D) + $(CC) $(CFLAGS) -g -c -o $@ one.c +build/debug/libmujs.a: build/debug/libmujs.o + $(AR) cr $@ $^ +build/debug/mujs: main.c build/debug/libmujs.o + $(CC) $(CFLAGS) -g -o $@ $^ -lm $(READLINE_CFLAGS) $(READLINE_LIBS) +build/debug/mujs-pp: pp.c build/debug/libmujs.o + $(CC) $(CFLAGS) -g -o $@ $^ -lm + +build/release/libmujs.$(SO): one.c $(SRCS) $(HDRS) + @mkdir -p $(@D) + $(CC) $(CFLAGS) $(OPTIM) -fPIC -shared -o $@ one.c -lm +build/release/libmujs.o: one.c $(SRCS) $(HDRS) + @mkdir -p $(@D) + $(CC) $(CFLAGS) $(OPTIM) -c -o $@ one.c +build/release/libmujs.a: build/release/libmujs.o + $(AR) cr $@ $^ +build/release/mujs: main.c build/release/libmujs.o + $(CC) $(CFLAGS) $(OPTIM) -o $@ $^ -lm $(READLINE_CFLAGS) $(READLINE_LIBS) +build/release/mujs-pp: pp.c build/release/libmujs.o + $(CC) $(CFLAGS) $(OPTIM) -o $@ $^ -lm + +build/release/mujs.pc: + @mkdir -p $(@D) + echo > $@ Name: mujs + echo >> $@ Description: MuJS embeddable Javascript interpreter + echo >> $@ Version: $(VERSION) + echo >> $@ Cflags: -I$(incdir) + echo >> $@ Libs: -L$(libdir) -lmujs + echo >> $@ Libs.private: -lm + +install-common: build/release/mujs build/release/mujs-pp build/release/mujs.pc + install -d $(DESTDIR)$(incdir) + install -d $(DESTDIR)$(libdir) + install -d $(DESTDIR)$(libdir)/pkgconfig + install -d $(DESTDIR)$(bindir) + install -m 644 mujs.h $(DESTDIR)$(incdir) + install -m 644 build/release/mujs.pc $(DESTDIR)$(libdir)/pkgconfig + install -m 755 build/release/mujs $(DESTDIR)$(bindir) + install -m 755 build/release/mujs-pp $(DESTDIR)$(bindir) + +install-static: install-common build/release/libmujs.a + install -m 644 build/release/libmujs.a $(DESTDIR)$(libdir) + +install-shared: install-common build/release/libmujs.$(SO) + install -m 755 build/release/libmujs.$(SO) $(DESTDIR)$(libdir) + +install: install-static + +uninstall: + rm -f $(DESTDIR)$(bindir)/mujs + rm -f $(DESTDIR)$(bindir)/mujs-pp + rm -f $(DESTDIR)$(incdir)/mujs.h + rm -f $(DESTDIR)$(libdir)/pkgconfig/mujs.pc + rm -f $(DESTDIR)$(libdir)/libmujs.a + rm -f $(DESTDIR)$(libdir)/libmujs.$(SO) + +tarball: + git archive --format=zip --prefix=mujs-$(VERSION)/ HEAD > mujs-$(VERSION).zip + git archive --format=tar --prefix=mujs-$(VERSION)/ HEAD | gzip > mujs-$(VERSION).tar.gz + +tags: $(SRCS) $(HDRS) main.c pp.c + ctags $^ + +clean: + rm -rf build + +nuke: clean + rm -f one.c astnames.h opnames.h + +sanitize: build/sanitize/mujs + +debug: build/debug/libmujs.a +debug: build/debug/libmujs.$(SO) +debug: build/debug/mujs +debug: build/debug/mujs-pp + +release: build/release/mujs.pc +release: build/release/libmujs.a +release: build/release/libmujs.$(SO) +release: build/release/mujs +release: build/release/mujs-pp diff --git a/mujs/README b/mujs/README new file mode 100644 index 0000000..467bc41 --- /dev/null +++ b/mujs/README @@ -0,0 +1,50 @@ +MuJS: an embeddable Javascript interpreter in C. + +ABOUT + +MuJS is a lightweight Javascript interpreter designed for embedding in +other software to extend them with scripting capabilities. + +LICENSE + +MuJS is Copyright 2013-2017 Artifex Software, Inc. + +Permission to use, copy, modify, and/or 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. + +COMPILING + +If you are building from source you can either use the provided Unix Makefile: + + make release + +Or compile the source with your preferred compiler: + + cc -O2 -c one.c -o libmujs.o + +INSTALLING + +To install the MuJS command line interpreter, static library and header file: + + make prefix=/usr/local install + +DOWNLOAD + +The latest development source is available directly from the git repository: + + git clone http://git.ghostscript.com/mujs.git + +REPORTING BUGS AND PROBLEMS + +Report bugs on the ghostscript bugzilla, with MuJS as the selected component. + + http://bugs.ghostscript.com/ diff --git a/mujs/astnames.h b/mujs/astnames.h new file mode 100644 index 0000000..aa19557 --- /dev/null +++ b/mujs/astnames.h @@ -0,0 +1,92 @@ +"list", +"fundec", +"identifier", +"exp_identifier", +"exp_number", +"exp_string", +"exp_regexp", +"exp_elision", +"exp_null", +"exp_true", +"exp_false", +"exp_this", +"exp_array", +"exp_object", +"exp_prop_val", +"exp_prop_get", +"exp_prop_set", +"exp_fun", +"exp_index", +"exp_member", +"exp_call", +"exp_new", +"exp_postinc", +"exp_postdec", +"exp_delete", +"exp_void", +"exp_typeof", +"exp_preinc", +"exp_predec", +"exp_pos", +"exp_neg", +"exp_bitnot", +"exp_lognot", +"exp_mod", +"exp_div", +"exp_mul", +"exp_sub", +"exp_add", +"exp_ushr", +"exp_shr", +"exp_shl", +"exp_in", +"exp_instanceof", +"exp_ge", +"exp_le", +"exp_gt", +"exp_lt", +"exp_strictne", +"exp_stricteq", +"exp_ne", +"exp_eq", +"exp_bitand", +"exp_bitxor", +"exp_bitor", +"exp_logand", +"exp_logor", +"exp_cond", +"exp_ass", +"exp_ass_mul", +"exp_ass_div", +"exp_ass_mod", +"exp_ass_add", +"exp_ass_sub", +"exp_ass_shl", +"exp_ass_shr", +"exp_ass_ushr", +"exp_ass_bitand", +"exp_ass_bitxor", +"exp_ass_bitor", +"exp_comma", +"exp_var", +"stm_block", +"stm_empty", +"stm_var", +"stm_if", +"stm_do", +"stm_while", +"stm_for", +"stm_for_var", +"stm_for_in", +"stm_for_in_var", +"stm_continue", +"stm_break", +"stm_return", +"stm_with", +"stm_switch", +"stm_throw", +"stm_try", +"stm_debugger", +"stm_label", +"stm_case", +"stm_default", diff --git a/mujs/docs/artifex-logo.png b/mujs/docs/artifex-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..af610a06d02bd0aeca8c1515d78671a89162c361 GIT binary patch literal 1584 zcmV-02G9A4P)kc>000H|NklG62|e!nLXA4e~I-vkH{_*I!WjhCb74+3s< zk&|sg!EwMSX5P~Q9{|o1`6GZ-Pf~uuk!4!qSGI_ZN8T2}yj$d=CMSw~Wi$K9<~bM_ z=W(_Z{hMMTA7!os^tsGCsmbrsk`Kr&E^`*r5N``4o4C|v10^=bd8`JazurZzfXJO- z$ls)y_xH(3emL@kiTL!2-NF}~sV-)Gf`}gm*ss~dcEFWMK>kUF{P&5>0YD{S1>hlo zHC7>IS9dFkU`YOmcZRj%9XRo>~7M{HXSdXX& z+CPe8AO!)3)OS-ji#^Qwcpm)DYr}nf|$M z#LqUx`qaujVn%4LadYBC{B6t_>FE0C^D0a7+o>l(x${wypU-9cNPeetO%%jC z$iu$NnYQ`ZCSSxKN!$H8tKVa?`mFMeB9K2rH?h^W#+P(C-^*LA=+H)LhG0&}>Dfh_tcw1|s7mi|epx zB;)oq%9%V9@FD3Ve`jI)I1BPq@C6u)E}FXROw=_N_3R}w&!W|iLCCQ1HIXS(=8=v0 zmfGIPa5I|E@CUx@O)lTFs`ZUv-qda#`49-j!MQU9kZZz3gVir2x}^)dN{MG#XsxPD zEt9>|IW97s1SnxUT;%dCiacA1Oar5bl3#=CbjZ(SlF%}u<6%RXxYK>j;Yg;D_;BQ$ zr=~p@@|;Ia@d=-zH|Chxb1`9Z@=_ivNmL)?A`laG`!`?bM^ zlim8`VL0-N1^E&0^8IP(&a?^5%|5@_H^$5|3UIUi?(`)-q>wF zesj+DT&924g8bW=H|=#D33-6Evw}4C@3UA`@FIwJ@K_wlf7c}^Nfh~wj7&XxG62#10W@oi#hX^QUB0gpIT&t?m6!Fi#g7|E}`m8V_?WBr$CG>m7Fh literal 0 HcmV?d00001 diff --git a/mujs/docs/examples.html b/mujs/docs/examples.html new file mode 100644 index 0000000..30304b1 --- /dev/null +++ b/mujs/docs/examples.html @@ -0,0 +1,224 @@ + + + + +MuJS Examples + + + + +
+

MuJS Examples

+
+ + + +
+ +

A stand-alone interpreter

+ +
+#include <stdio.h>
+#include <mujs.h>
+
+int main(int argc, char **argv)
+{
+	char line[256];
+	js_State *J = js_newstate(NULL, NULL, JS_STRICT);
+	while (fgets(line, sizeof line, stdin))
+		js_dostring(J, line);
+	js_freestate(J);
+}
+
+ +

Hello, world!

+ +
+#include <stdio.h>
+#include <mujs.h>
+
+static void hello(js_State *J)
+{
+	const char *name = js_tostring(J, 1);
+	printf("Hello, %s!\n", name);
+	js_pushundefined(J);
+}
+
+int main(int argc, char **argv)
+{
+	js_State *J = js_newstate(NULL, NULL, JS_STRICT);
+
+	js_newcfunction(J, hello, "hello", 1);
+	js_setglobal(J, "hello");
+
+	js_dostring(J, "hello('world');");
+
+	js_freestate(J);
+}
+
+ +

Configuration file

+ +
+js_dofile(J, "config.js")
+
+js_getglobal(J, "foo");
+foo = js_tonumber(J, -1);
+js_pop(J, 1);
+
+ +

Object manipulation

+ +
+// t = { foo: 42, bar: true }
+
+js_newobject(J);
+{
+	js_pushnumber(J, 42);
+	js_setproperty(J, -2, "foo");
+	js_pushboolean(J, 1);
+	js_setproperty(J, -2, "bar");
+}
+js_setglobal(J, "t");
+
+ +

Callbacks from C to JS (by name)

+ +
+static int call_callback(js_State *J, const char *arg1, int arg2)
+{
+	int result;
+
+	/* Find the function to call. */
+	js_getglobal(J, "my_callback");
+
+	/* Push arguments to function. */
+	js_pushnull(J); /* the 'this' object to use */
+	js_pushstring(J, arg1);
+	js_pushnumber(J, arg2);
+
+	/* Call function and check for exceptions. */
+	if (js_pcall(J, 2)) {
+		fprintf(stderr, "an exception occurred in the javascript callback\n");
+		js_pop(J, 1);
+		return -1;
+	}
+
+	/* Retrieve return value. */
+	result = js_tonumber(J, -1);
+	js_pop(J, 1);
+
+	return result;
+}
+
+ +

Callbacks from C to JS

+ +
+const char *handle = NULL; /* handle to stowed away js function */
+
+static void set_callback(js_State *J)
+{
+	if (handle)
+		js_unref(J, handle); /* delete old function */
+	js_copy(J, 1);
+	handle = js_ref(J); /* stow the js function in the registry */
+}
+
+static void call_callback(js_State *J, int arg1, int arg2)
+{
+	js_getregistry(J, handle); /* retrieve the js function from the registry */
+	js_pushnull(J);
+	js_pushnumber(J, arg1);
+	js_pushnumber(J, arg2);
+	js_pcall(J, 2);
+	js_pop(J, 1);
+}
+
+ +

Complete userdata example

+ +
+#include <stdio.h>
+#include <mujs.h>
+
+#define TAG "File"
+
+static void new_File(js_State *J)
+{
+	FILE *file;
+
+	if (js_isundefined(J, 1)) {
+		file = stdin;
+	} else {
+		const char *filename = js_tostring(J, 1);
+		file = fopen(filename, "r");
+		if (!file)
+			js_error(J, "cannot open file: '%s'", filename);
+	}
+
+	js_currentfunction(J);
+	js_getproperty(J, -1, "prototype");
+	js_newuserdata(J, TAG, file);
+}
+
+static void File_prototype_readByte(js_State *J)
+{
+	FILE *file = js_touserdata(J, 0, TAG);
+	js_pushnumber(J, getc(file));
+}
+
+static void File_prototype_readLine(js_State *J)
+{
+	char line[256], *s;
+	FILE *file = js_touserdata(J, 0, TAG);
+	s = fgets(line, sizeof line, file);
+	if (s)
+		js_pushstring(J, line);
+	else
+		js_pushnull(J);
+}
+
+static void File_prototype_close(js_State *J)
+{
+	FILE *file = js_touserdata(J, 0, TAG);
+	fclose(file);
+	js_pushundefined(J);
+}
+
+void initfile(js_State *J)
+{
+	js_getglobal(J, "Object");
+	js_getproperty(J, -1, "prototype");	// File.prototype.[[Prototype]] = Object.prototype
+	js_newuserdata(J, TAG, stdin);		// File.prototype.[[Userdata]] = stdin
+	{
+		js_newcfunction(J, File_prototype_readByte, "File.prototype.readByte", 0);
+		js_defproperty(J, -2, "readByte", JS_DONTENUM);
+
+		js_newcfunction(J, File_prototype_readLine, "File.prototype.readLine", 0);
+		js_defproperty(J, -2, "readLine", JS_DONTENUM);
+
+		js_newcfunction(J, File_prototype_close, "File.prototype.close", 0);
+		js_defproperty(J, -2, "close", JS_DONTENUM);
+	}
+	js_newcconstructor(J, new_File, new_File, "File", 1);
+	js_defglobal(J, "File", JS_DONTENUM);
+}
+
+ +
+ +
+ +Copyright © 2013-2017 Artifex Software Inc. +
+ + + diff --git a/mujs/docs/index.html b/mujs/docs/index.html new file mode 100644 index 0000000..fa046bc --- /dev/null +++ b/mujs/docs/index.html @@ -0,0 +1,58 @@ + + + + +MuJS + + + + +
+

MuJS

+
+ + + +
+ +

+MuJS is a lightweight Javascript interpreter designed for embedding in other +software to extend them with scripting capabilities. + +

+MuJS was designed with a focus on small size, correctness, and simplicity. +It is written in portable C and implements ECMAScript as specified by ECMA-262. +The interface for binding with native code is designed to be as simple as +possible to use, and is very similar to Lua. There is no need to interact with +byzantine C++ template mechanisms, or worry about marking and unmarking garbage +collection roots, or wrestle with obscure build systems. + +

+MuJS is developed and maintained by Artifex Software. +It was originally developed for use with the MuPDF viewer, but is designed to be useful as an independent component. + +

+The primary meeting place for the MuJS community is the +#mupdf +IRC channel on freenode. + +

+MuJS is free open source software distributed under the +ISC license. + +

+ +
+ +Copyright © 2013-2017 Artifex Software Inc. +
+ + + diff --git a/mujs/docs/introduction.html b/mujs/docs/introduction.html new file mode 100644 index 0000000..7096669 --- /dev/null +++ b/mujs/docs/introduction.html @@ -0,0 +1,108 @@ + + + + +MuJS Introduction + + + + +
+

MuJS Introduction

+
+ + + +
+ +

Why choose MuJS?

+ +

Javascript is a proven scripting language

+ +

+Javascript is one of the most popular programming languages in the world. +It is a powerful extension language, used everywhere on the web — both as +a way to add interactivity to web pages in the browser, and on the server side +with platforms like node.js. + +

+With MuJS you can bring this power to your application as well! + +

MuJS is standards compliant

+ +

+MuJS implements ES5. +There are no non-standard extensions, so you can remain confident that +Javascript code that runs on MuJS will also run on any other standards +compliant Javascript implementation. + +

MuJS is portable

+ +

+MuJS is written in portable C and can be built by compiling a single C file using any standard C compiler. +There is no need for configuration or fancy build systems. +MuJS runs on all flavors of Unix and Windows, on mobile devices (such as Android and iOS), +embedded microprocessors (such as the Beagle board and Raspberry Pi), etc. + +

MuJS is embeddable

+ +

+MuJS is a simple language engine with a small footprint that you can easily embed into your application. +The API is simple and well documented and allows strong integration with code written in other languages. +You don't need to work with byzantine C++ templating mechanisms, or manually manage garbage collection roots. +It is easy to extend MuJS with libraries written in other languages. +It is also easy to extend programs written in other languages with MuJS. + +

MuJS is small

+ +

+Adding MuJS to an application does not bloat it. +The source contains around 15'000 lines of C. +Under 64-bit Linux, the compiled library takes 180kB if optimized for size, +and 260kB if optimized for speed. + +Compare this with V8, SpiderMonkey or JavaScriptCore, +which are all several hundred thousand lines of code, +take several megabytes of space, +and require the C++ runtime. + +

MuJS is reasonably fast and secure

+ +

+It is a bytecode interpreter with a very fast mechanism to call-out to C. +The default build is sandboxed with very restricted access to resources. +Due to the nature of bytecode, MuJS is not as fast as JIT compiling +implementations but starts up faster and uses fewer resources. +If you implement heavy lifting in C code, controlled by Javascript, +you can get the best of both worlds. + +

MuJS is free software

+ +

+MuJS is free open source software distributed under the +ISC license. + +

MuJS is developed by a stable company

+ +

+Artifex Software has long experience in +interpreters and page description languages, and has a history with open source +that goes back to 1993 when it was created to facilitate licensing Ghostscript +to OEMs. + +

+ +
+ +Copyright © 2013-2017 Artifex Software Inc. +
+ + + diff --git a/mujs/docs/license.html b/mujs/docs/license.html new file mode 100644 index 0000000..d4e7056 --- /dev/null +++ b/mujs/docs/license.html @@ -0,0 +1,50 @@ + + + + +MuJS License + + + + +
+

MuJS License

+
+ + + +
+ +

+MuJS is Copyright © 2013-2017 Artifex Software, Inc. + +

+Permission to use, copy, modify, and/or 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. + +

+ +
+ +Copyright © 2013-2017 Artifex Software Inc. +
+ + + diff --git a/mujs/docs/logo.ps b/mujs/docs/logo.ps new file mode 100644 index 0000000..2b8347f --- /dev/null +++ b/mujs/docs/logo.ps @@ -0,0 +1,47 @@ +%! +<>setpagedevice + +% #323330 = 50 51 48 +% #F0DB4F = 240 219 79 +% #4386b5 = 67 134 181 + +/cG { 50 255 div 51 255 div 48 255 div setrgbcolor } def +/cY { 240 255 div 219 255 div 79 255 div setrgbcolor } def +/cB { 67 255 div 134 255 div 181 255 div setrgbcolor } def + +% fill background with yellow +cY +0 0 moveto 512 0 lineto 512 512 lineto 0 512 lineto closepath fill + +% move logo to lower right corner +512 0.2 mul 0 translate +0.8 0.8 scale + +% center logo +0.875 0.875 scale +32 32 translate + +% draw electrons and nucleus +cG +gsave + 256 256 translate + + 16 setlinewidth + gsave 0 rotate .5 1 scale 0 0 232 0 360 arc stroke grestore + gsave 60 rotate .5 1 scale 0 0 232 0 360 arc stroke grestore + gsave 120 rotate .5 1 scale 0 0 232 0 360 arc stroke grestore + + 0 0 96 0 360 arc fill +grestore + +% draw yellow 'JS' text in center of nucleus +cY +gsave + /SourceSansPro-Bold findfont 128 scalefont setfont + 256 256 moveto + (JS) + dup stringwidth pop -2 div -44 rmoveto + show +grestore + +showpage diff --git a/mujs/docs/mujs-logo.png b/mujs/docs/mujs-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f1ad1a342d23856ba15184e0db74449f6eac5d85 GIT binary patch literal 6007 zcmV--7l`PIP)4iWNLWfX8kt!vCln`1d(yORI=$%kSY>2o5 zD~gCTMUX`S7hIJhYef_bi-H0cbb~Ac%DQnCQ9)U7-x;0v9Y4JPnS0M~{&VKubImPVp6)m+sTd@7!=F|-zbcqmcsG$bOYbjyAc4b#Cy8m&-tzh5s_*9<+!-K za``EObO502W!B6}Pg&uk%S;cC|H5ZiSg>5+GT&I?=zn?aOU}P6`jX@8&0T&K3IH4_ z`Wp`ahW~T#ic~m)^w_A#Y^G0gRC-pJAd;E3(g_}y%k&f^N5v*YepB%OmHZv0M`ow5 zL~e4*CP8d;Ogi&_4mC0`Gz4E~@y6>*k0D0?f6r${U%ljd0N8T?Ap5?0;gtY%?gBvi z(N|Axxv!Lb06OmQGX$CcdZ?8R4G4e&=s*T20#%?1bb%2t1D3!RH~<&m0enFa*Z}w- z1|$Lj$O5@w2PgnVU>`ULsz5Dh08OA3oCclXJh%d`gWKRP7zU4l5KMxXKm^`_1qgz$ z5E-IDGLRCa4(UKf5F4_F93VHy8w!F#p=c-x%7AjAolp@}3ROXMP!seEbPl=#-Gm0A zhtN1Q1N{wsgb|no(_sZz1J;MxFb8&pec|Pmz$X28fDM#v&R-_y0L+&9$WES~=!k}m8y zq4=m&)K*k6stR=i)q(0o-9wF|M5xbbGFl$3gSJ5P(1GY^bQZb*U5;)8N^Ir=CD|-3|0qgh4sLOVbie%*h=h4>?Q0F zb`tv@N5m=NjBt*)U|bR|4_A(B#$Ch>;ihm4cyYWs-U9D|kHBxjm*5-l-S|8BN&Esq zg1{nJ5qt@8gdKzmLM!1KVT|yGNF=Hd&52&bSmJhK1@RQ|263GDfkY)~lenZ1QU+-+ z={V^!=^^P2nM~Fo+meIH>EylSCh}GCQ}TN;nwXv#Pb@-gn^?72huDzVD+->XPT^2C zP;w~clrxk&lv#1SxP~}aJWPD6c(wRB@lo+P37UkVgoi|;M6pD(#7&85Dwe82b)ZI2 z^Qp(ESE&;;gr-X4()hHUvHNXZiqLBuSMtmh_j*maLY%ASq-Z z3=Ia4k-#WnoMwzLK1wM_aik)p_DHo#4N1*Q%SqcxM@kn;w@D96f0SX$ILO4y?3d}3 zc_NFF)t2>=&5}JLdrfv$jxJ{@$CoRVJ1zG}9+uaZ_m+B4v4HXXOm#I^_Z71r?Tx zpGuxeo5~Yaf+|}zO0`V2M^&Vzq~@-ctJb3SNFA@vR*z9Xq~51Kuffs?(AcffsWHux zX7N}#tQOXoCRx))GgY%*^S%~Fi>(!}by(}JHmq%;9iv^XeMbk@G1ZCHIjl3d3cbpF zRnn^ZRinBjU5;+1Zj0`?9z)MfFHf&i@0Grqez5+2{XYHA1||jx1`P&}4XK7MhIxkF zhJPDr8}W^*jqa}|uXbF$ZFSe`*Ty==k;X@iADGZg+)Z|y^q71yH8T~MwwlhEshNeE z)tHU4>1x%#+P zxju8#b`!XDyTk4-?g!k*)~c^fTHEOXJa`@lJf3*6JX1Y?^}=|0dR2Qp_crv-_3rbb z`E2xQ_Ic-P=Ud|Y*iX|h!>`Am;veGQAKVx` zx6WbR!F7`%CLskOBkNh~v)A9;AiE)9!-b8MjbR(lgrY+OLYqSu!#u+3!{)+y;WgnR zK9_%p|1!chqAX%2(kikvaw^Iysx)dk+A8`$^h}IROnJ;~EH}0)_H~?dTy5NZyhr@; z_@#t^gtkOnVrb&IBx+K8Qg5~h-GQXp0!C>Y&sxx4lU z)DN*g4D4CGr*hBd!id6KMFvIXMT^D!;#)r&{#fzj(%z`O10|*BIr|107||vcj^r2R9zPS#Dfjdx&r-D4*dA%ERj4hh{dhF?=z}_^x{i8{`toC#W0}XEH~2U7{lxyMsZqAEsPWVB zq~pR9-Y0sS%$k~-<(o@d;FgS*nUf(W@BVE6b62ZwYh4?ot>_dum2v9DFQLDToOV6k zbB2AU^{o2Yns!=yVF%Q)sYBEm(<$r<>KZ)fa;~S_vb+6PyIh)<8O`M_RM+CJ$;w>ZehOgz0CU)AJ`xIJ_dZ8TF6?&E>?Wf`qcf|_4C+L z;?mMTLM)KJiso|K000gGNklWM(m*~@lm;qghzxn}_xrzRzwWv3J?Fgdx%a*A zEuZ~7>)B^md+oK?IeYE3)?VjC%>EKsBE`+XU%eE&fO2wj4gt>PusKieZ|qtUWdBYNE=z%O zKu-qwQpol_sHOrNfRkzeefqh~Uos*f-heD%K8LMHxnKn#q8Y2?0Gx)eC5hfYzjgCc-%iSkB>OXkL~Mntr871%z!ARkc11bWC zxlm+E*<{ScC2n+y* z1J!^NfM&EWPuuS}p2P9W9KQzBJH7fvAP0e?H?XrF498WeMPDw51pWhX2XG(vrf_ew zao!{uqI$C%0Ve}(5l}jD$t=YiST%Tw0^p^GmI&p0e^<1vW0$&Kd)d~GfrmDg9)&aa6M0E}@ z2zn>lJ{EA?6IdTK{y+giq!#clM57jrs%fQsC`JCHzveME#iEu_xAJ=k zz5;Stzq>g-mCip*rE}YM4%I5!^bf#kK{d)aN_QeK0sJrQJ?V~XfJ*>*?}`U4383{b z)sf(I4#A`P*V|c=FXa60f1 zecbJ54b!)j^35Dx0lW^}=w6h2tq8JwX~S(-KKt z7x+5WPHEH1>V%5!C?GT|QH=&J1)c(K<^Inp4y20kjib09ho51kKudBshU&kGIDP^;-!7ZW=0Q?t?Qahgt5jk2?jxPg^S6GfL6%5 z#xeA?^lqTNEbtGhH@WCEqNo4+%;km=_Z7`w8d<8r?&q zXK8d%$PQ%^tOAC}XX4URTH2|viHA9C_(SoXD~)5Ll<=Yjj+S_ZVl@?J#_bjj29Vy- zqH&pgJo=SjWU-$YhF|C?#?;eK5BUkGKg^7$(IYgvI!uSOT|?V}9BPG_K>eI?-kL3z zAx*)jG<8D`L<;;;=q$G96{H_I><)-WnIZRZzfMSgC&No`8N#%EGV#8jfV$IY28|}t zsBeftlm@nOs2HDP$RaGk|j7cX83tPR1L4inyS~ zD)9DhSBAb`p^~nX-=*!OyMt(e-`wutJ`U~`UN}JlYDwb-G#*Ri!2x(u#F|=KmE!a($K`{cqR*YTo>CW$d_pvwEghwL|yb`{CzJ z=G8^Z&026Ol`CT!9aYVo-mJbUSu*d7NdITk-^;c??Vr_`X<~i6SAp5^aCzeXn_W;8 zcKH_krhYso^XB!czDV64A`{%g0iH2VxT3AghmsHWQXH3aO)cL*{o6}dnZXlg#*Xj2 zva4x-qGRp9hG1^bo?~|ZxhD_)(bX%P(U*5Nb&ja)=5a3loniZv|0aQTvgeJBV@JIX zAAJ(^;&q%q;1+Ob0a-%QKZ*&}j>mtvmJRW4vT3-s%#F+SvA{HIr)&(;JzfI(dDHKv z$B@U&E`(F|pi-uJquOTMA3M#*E7zHW%N%I3hh1!{R4V5dkfbQLW-I+GNOwKQr?_~M z)vef6cHE}3u8DlQHC$^MH2zcsBzs0Nq1%94Ilt35;R(GtixO}Z%+h|_fYp$$kGE(0rE?L`5a$OJs}atUAzRe@bjH#N7n?Jd4x25(Q*=lff%XSr1okiEOExl8rspm~?4t>MJc} zJcO_J!jpiEW2otAj*-_LMAu6|yLSI+dXIeCtov!Nj~aVx&TH4ioY|t0DOn=F5iT>s zz8Ss(vi!->)e0+vuu)LRI9~p80#aGOaf=!G#4F~LRo~^ouUn(C88@(}sZ>61zE!ac zD3epnT|Qg=y_q`i=Pp@chClACu=ifu!?Zfy z+Yw*$<3`hG%rj=kuH7+PY08*>dF~ghl1{SBYe!(Y8#ic~U$J(*xqr%AX7puUOw~in zo7*P7Ze}m|Ja+GS23?!M??eX;xNn+iTEB+5sN+eorTwR0x0>^YJ`uaujO!2I)XOcL zWXrsSr3ab*W!@F`D72xtea9}-b?~ERD@%0=6m8`S2b=Z3I=j*JYgRGY!!I_9eBM3j z4KscICk}IPSG7|47@nPBW<FuE7I<2gmqTI?GA1%+!^GoZsSc_yljp61&g`HVHM5sb*r1QrAy_byK(b2v+~;w z=6ei8O~;0{s+mJ82AQhD<2|M>~12guXI2JP*xs?uWOLHj=L6{3iO z_R12OXmW%0N}1Umg6DY9zFU?@YH}vFeYClEV-aopD6>q)VaB(83bS$e0&Q0(ZX9wh z*JGv~T%k4&=|ijKBPRdGp>`&EhT*rM8;3=)^{f?TG1ryEw093YC-2sCJkN1k#Cz-c z7?Zp8tj)+`+j?$DqvbhiOkS{pkO3)2O34eOvce@VjI?c^0ET9{l0u_tNM6t}ZC57Z z1f{=dnkXN6e2}r_F_>8T3vJH^-t@R%6zMM=sb)pOr@uT5{#Ep`61ok%aEda4N;x;a zc}3cjGEM@OzTcQ={1d38gT@5y%|$bvK&3(>HCz;NQtiq1%6kH66i*VTi(q0Ju>7+7Oaan>?R72rr^*ZGw4=Q&15J6$d((h9 z?n^DhZx6Mmto*F^5_M2CTpI8|=$&NyD0~`lyjZ2J)VWcS+(dGvxhTO>x&Yv^sRuUA)wBYj-9_m# z?Xb3!g>Cs>$Fag9*{S%L1^E;~K{7?|!hVXd6x=16vR*4uDn&k`ag@3uhRsa)PZ6G{ z?|EVROxMROdSdR6S@a=^be4kRnfRDR#-p}d-2o%L;hQ6w{Fo(di8`M{$!gXLQxu;= z=>aq>=o^_z$;1bBvV1iqi{gX2NzlAJ9W?z)CO^r1pTTrUhM%lhM^}2JX=GpcBvZv@ zl@CKmw-=JuzV%VBihb%b3AFObYc9-Z;j(*VytYqenJ#SE|6_q9r8s|0p|2P3Ox&+Z zEWCO0&4z1wntl1#6!8*uer}>azm&^yer}?l4b1Vul?APwfF{;TKPPKn{<%rKM4bf7 zDT+7goWZX(j-|W7@}xAfHj&)l|***)O0Z<$uTwVbF`(iv9oq002ovPDHLkV1nDAvg`l= literal 0 HcmV?d00001 diff --git a/mujs/docs/reference.html b/mujs/docs/reference.html new file mode 100644 index 0000000..74a7932 --- /dev/null +++ b/mujs/docs/reference.html @@ -0,0 +1,719 @@ + + + + +MuJS Reference + + + + +
+

MuJS Reference

+
+ + + +
+ +

Introduction

+ +

+MuJS is a library, written in clean and simple C. +Being an extension library, MuJS has no notion of a main program: it only works embedded in a host client program. +The host program can invoke functions to execute Javascript code, read and write Javascript variables, and register C functions to be called by Javascript. + +

+The MuJS distribution includes a sample host program called "mujs", which uses the MuJS library to offer a standalone Javascript interpreter for interactive or batch use. + +

+This reference manual assumes that you are already familiar with the Javascript language, in particular the type system and object prototype mechanisms. + +

Basic Concepts

+ +

Values and Types

+ +

+There are six basic types in Javascript: undefined, null, boolean, number, string and object. + +

+Each object also has a class: object, array, function, userdata, regular expression, etc. + +

+Javascript can call functions written in C provided by the host program, as well as other Javascript functions. + +

+Objects with the userdata class are provided to allow arbitrary C data to be attached to Javascript objects. +A userdata object has a pointer to a block of raw memory, which is managed by the host. +Userdata values cannot be created or modified in Javascript, only through the C API. +This guarantees the integrity of data owned by the host program. + +

+Custom properties on userdata objects can be implemented using getter and setter property accessor functions. + +

+Numbers are represented using double precision floating point values. + +

+Strings in the C interface are zero-terminated byte arrays in WTF-8 encoding. +This allows both arbitrary 16-bit values (as required by Javascript) and also +extended code points for the full 21-bit Unicode range. +These extended characters will mostly work as expected in Javascript. + +

+If you have Javascript code that expects to work with UTF-16 surrogate pairs, +you will need to manually convert any extended characters to surrogate pairs +and back when passing strings between C and Javascript. + +

+The U+0000 character is encoded as the two-byte sequence , same as in +modified UTF-8. + +

Environments

+ +

+Each function executes within an environment which defines which variables are accessible. +This is a chain of all environment records in scope, with the global environment at the top. +Each environment record in MuJS is represented as an object with the null prototype, including the global environment object. + +

+The registry is a hidden environment record which is only accessible to C. +This is where Javascript values and objects that should only be accessible to C functions may be stored. + +

Error Handling

+ +

+All Javascript actions start from C code in the host program calling a function from the MuJS library. +Whenever an exception is thrown during the compilation or execution of Javascript, control returns to the host, which can take appropriate measures (such as printing an error message). +C code can also throw exceptions by calling functions to create an error object and return control to Javascript. + +

+Internally, MuJS uses the C longjmp facility to handle errors. +A protected environment uses setjmp to set a recovery point. +The try statement in Javascript creates such a recovery point, as does calling js_dostring, js_dofile, js_ploadstring, js_ploadfile, +js_pcall and js_pconstruct. + +

+When an error occurs or an exception is thrown from Javascript, it does a long jump to the most recent active recovery point. + +

+If an error occurs outside any protected environment, MuJS first calls the panic function and then calls abort, thus exiting the host application. +Your panic function can avoid this exit by never returning (for example by doing a long jump to your own recovery point outside MuJS). + +

Garbage Collection

+ +

+MuJS performs automatic memory management using a basic mark-and-sweep collector. +Collection is automatically triggered when enough allocations have accumulated. +You can also force a collection pass from C. + +

+Userdata objects have an associated C finalizer function that is called when +the corresponding object is freed. + +

The Stack

+ +

+MuJS uses a virtual stack to pass values to and from C. +Each element in this stack represents a Javascript value (null, number, string, etc). + +

+Whenever Javascript calls C, the called function gets a new stack. +This stack initially contains the this value and any arguments passed to the function. +When the C function returns, the top value on the stack is passed back to the caller as the return value. + +

+The stack values are accessed using stack indices. +Index 0 always contains the this value, and function arguments are index 1 and up. +Negative indices count down from the top of the stack, so index -1 is the top of the index and index -2 is the one below that. + +

The Application Program Interface

+ +

State

+ +
+typedef struct js_State js_State;
+
+ +

+The interpreter state is bundled up in the opaque struct js_State. +This state contains the value stacks, protected environments, and environment records. + +

+js_State *js_newstate(js_Alloc alloc, void *context, int flags);
+
+ +

+Create a new state using the allocator function and allocator context. +Pass NULL to use the default allocator. + +

+The available flags: + +

    +
  • JS_STRICT: compile and run code using ES5 strict mode. +
+ +
+void js_freestate(js_State *J);
+
+ +

+Destroy the state and free all dynamic memory used by the state. + +

Allocator

+ +

+The interpreter uses a host provided function for all memory allocation needs: + +

+typedef void *(*js_Alloc)(void *memctx, void *ptr, int size);
+
+ +

+When size is zero, the allocator should behave like free and return NULL. +When size is not zero, the allocator should behave like realloc. +The allocator should return NULL if it cannot fulfill the request. +The default allocator uses malloc, realloc and free. + +

Panic

+ +
+typedef void (*js_Panic)(js_State *J);
+
+js_Panic js_atpanic(js_State *J, js_Panic panic);
+
+ +Set a new panic function, and return the old one. + +

Report

+ +
+typedef void (*js_Report)(js_State *J, const char *message);
+
+void js_setreport(js_State *J, js_Report report);
+
+ +

+Set a callback function for reporting various warnings +and garbage collection statistics. + +

+The report function must not throw an exception +or call any other MuJS function except js_getcontext(). + +

Garbage collection

+ +
+js_gc(js_State *J, int report);
+
+ +

+Force a garbage collection pass. +If the report argument is non-zero, send a summary of garbage collection statistics to +the report callback function. + +

Loading and compiling scripts

+ +

+A script is compiled by calling js_loadstring or js_loadfile. +The result of a successful compilation is a function on the top of the stack. +This function can then be executed with js_call. + +

+void js_loadstring(js_State *J, const char *filename, const char *source);
+void js_loadfile(js_State *J, const char *filename);
+
+ +

+Compile the script and push the resulting function. + +

+int js_ploadstring(js_State *J, const char *filename, const char *source);
+int js_ploadfile(js_State *J, const char *filename);
+
+ +Like js_loadstring/js_loadfile but in a protected environment. +In case of success, return 0 with the result as a function on the stack. +In case of failure, return 1 with the error object on the stack. + +

Calling functions

+ +
+void js_call(js_State *J, int n);
+
+ +

+To call a function, you must use the following protocol: +1) push the function to call onto the stack, +2) push the this value to be used by the function, +3) push the arguments to the function in order, +4) finally, call js_call with the number of arguments pushed in step 3. + +

+Pop the function, the this value, and all arguments; +execute the function; +then push the return value from the function. + +

+void js_construct(js_State *J, int n);
+
+ +

+The construct function implements the 'new' expression in Javascript. +This is similar to js_call, but without pushing a this value: +1) push the constructor function to call onto the stack, +2) push the arguments to the constructor function in order, +3) finally, call js_construct with the number of arguments pushed in step 2. + +

+int js_pcall(js_State *J, int n);
+int js_pconstruct(js_State *J, int n);
+
+ +

+Like js_call and js_construct but in a protected environment. +In case of success, return 0 with the result on the stack. +In case of failure, return 1 with the error object on the stack. + +

Script helpers

+ +

+There are two convenience functions for loading and executing code. + +

+int js_dostring(js_State *J, const char *source);
+
+ +

+Compile and execute the script in the zero-terminated string in source argument. +If any errors occur, call the report callback function and return 1. +Return 0 on success. + +

+int js_dofile(js_State *J, const char *filename);
+
+ +

+Load the script from the file with the given filename, then compile and execute it. +If any errors occur, call the report callback function and return 1. +Return 0 on success. + +

Protected environments

+ +

+The js_try macro pushes a new protected environment and calls setjmp. +If it returns true, an error has occurred. The protected environment has been popped +and the error object is located on the top of the stack. + +

+At the end of the code you want to run in the protected environment you must call +js_endtry in order to pop the protected environment. Note: you should not call +js_endtry when an error has occurred and you are in the true-branch of js_try. + +

+Since the macro is a wrapper around setjmp, the usual +restrictions apply. +Use the following example as a guide for how to use js_try: + +

+if (js_try(J)) {
+	fprintf(stderr, "error: %s", js_trystring(J, -1, "Error"));
+	js_pop(J, 1);
+	return;
+}
+do_some_stuff();
+js_endtry(J);
+
+ +

+Most of the time you shouldn't need to worry about protected environments. +The functions prefixed with 'p' (js_pcall, js_ploadstring, etc) handle setting +up the protected environment and return simple error codes. + +

Errors

+ +
+void js_throw(js_State *J);
+
+ +

+Pop the error object on the top of the stack and return control flow to the most recent protected environment. + +

+void js_newerror(js_State *J, const char *message);
+void js_newevalerror(js_State *J, const char *message);
+void js_newrangeerror(js_State *J, const char *message);
+void js_newreferenceerror(js_State *J, const char *message);
+void js_newsyntaxerror(js_State *J, const char *message);
+void js_newtypeerror(js_State *J, const char *message);
+void js_newurierror(js_State *J, const char *message);
+
+ +

+Push a new error object on the stack. + +

+void js_error(js_State *J, const char *fmt, ...);
+void js_evalerror(js_State *J, const char *fmt, ...);
+void js_rangeerror(js_State *J, const char *fmt, ...);
+void js_referenceerror(js_State *J, const char *fmt, ...);
+void js_syntaxerror(js_State *J, const char *fmt, ...);
+void js_typeerror(js_State *J, const char *fmt, ...);
+void js_urierror(js_State *J, const char *fmt, ...);
+
+ +

+Wrapper to push a new error object on the stack using a printf formatting string and call js_throw. + +

Stack manipulation

+ +
+int js_gettop(js_State *J);
+void js_pop(js_State *J, int n);
+void js_rot(js_State *J, int n);
+void js_copy(js_State *J, int idx);
+void js_remove(js_State *J, int idx);
+void js_insert(js_State *J, int idx);
+void js_replace(js_State* J, int idx);
+
+ +

Comparisons and arithmetic

+ +
+void js_concat(js_State *J);
+int js_compare(js_State *J, int *okay);
+int js_equal(js_State *J);
+int js_strictequal(js_State *J);
+int js_instanceof(js_State *J);
+
+ +

+The equivalent of the '+', comparison, and instanceof operators. +The okay argument to js_compare is set to 0 if any of the values are NaN, otherwise it is set to 1. + + + +

Primitive values

+ +
+void js_pushundefined(js_State *J);
+void js_pushnull(js_State *J);
+void js_pushboolean(js_State *J, int v);
+void js_pushnumber(js_State *J, double v);
+void js_pushstring(js_State *J, const char *v);
+void js_pushliteral(js_State *J, const char *v);
+
+ +

+Push primitive values. +js_pushstring makes a copy of the string, so it may be freed or changed after passing it in. +js_pushliteral keeps a pointer to the string, so it must not be changed or freed after passing it in. + +

+int js_isdefined(js_State *J, int idx);
+int js_isundefined(js_State *J, int idx);
+int js_isnull(js_State *J, int idx);
+int js_isboolean(js_State *J, int idx);
+int js_isnumber(js_State *J, int idx);
+int js_isstring(js_State *J, int idx);
+int js_isprimitive(js_State *J, int idx);
+
+ +

+Test if a primitive value is of a given type. + +

+int js_toboolean(js_State *J, int idx);
+double js_tonumber(js_State *J, int idx);
+int js_tointeger(js_State *J, int idx);
+int js_toint32(js_State *J, int idx);
+unsigned int js_touint32(js_State *J, int idx);
+short js_toint16(js_State *J, int idx);
+unsigned short js_touint16(js_State *J, int idx);
+const char *js_tostring(js_State *J, int idx);
+
+ +

+Convert the value at the given index into a C value. +If the value is an object, invoke the toString and/or valueOf methods to do the conversion. + +

+The conversion may change the actual value in the stack! + +

+There is no guarantee that the pointer returned by js_tostring will be valid after +the corresponding value is removed from the stack. + +

+Note that the toString and valueOf methods that may be invoked by these functions +can throw exceptions. If you want to catch and ignore exceptions, use the following +functions instead. The 'error' argument is the default value that will be returned +if a toString/valueOf method throws an exception. + +

+int js_tryboolean(js_State *J, int idx, int error);
+double js_trynumber(js_State *J, int idx, double error);
+int js_tryinteger(js_State *J, int idx, int error);
+const char *js_trystring(js_State *J, int idx, const char *error);
+
+ +

Objects

+ +
+enum {
+	JS_REGEXP_G = 1,
+	JS_REGEXP_I = 2,
+	JS_REGEXP_M = 4,
+};
+
+void js_newobject(js_State *J);
+void js_newarray(js_State *J);
+void js_newboolean(js_State *J, int v);
+void js_newnumber(js_State *J, double v);
+void js_newstring(js_State *J, const char *v);
+void js_newregexp(js_State *J, const char *pattern, int flags);
+
+ +

+Create and push objects on the stack. + +

+int js_isobject(js_State *J, int idx);
+int js_isarray(js_State *J, int idx);
+int js_iscallable(js_State *J, int idx);
+int js_isregexp(js_State *J, int idx);
+
+ +

+Test the type and class of an object on the stack. + +

Properties

+ +

+The property functions all work on an object. +If the stack slot referenced by the index does not contain an object, they will throw an error. + +

+enum {
+	JS_READONLY = 1,
+	JS_DONTENUM = 2,
+	JS_DONTCONF = 4,
+};
+
+ +

+Property attribute bit-mask values. + +

+int js_hasproperty(js_State *J, int idx, const char *name);
+
+ +

+If the object has a property with the given name, return 1 and push the value of the property; otherwise return 0 and leave the stack untouched. + +

+void js_getproperty(js_State *J, int idx, const char *name);
+
+ +

+Push the value of the named property of the object. +If the object does not have the named property, push undefined instead. + +

+void js_setproperty(js_State *J, int idx, const char *name);
+
+ +

+Pop a value from the top of the stack and set the value of the named property of the object. + +

+void js_defproperty(js_State *J, int idx, const char *name, int atts);
+
+ +

+Pop a value from the top of the stack and set the value of the named property of the object. +Also define the property attributes. + +

+void js_defaccessor(js_State *J, int idx, const char *name, int atts);
+
+ +

+Define the getter and setter attributes of a property on the object. +Pop the two getter and setter functions from the stack. +Use null instead of a function object if you want to leave any of the functions unset. + +

+void js_delproperty(js_State *J, int idx, const char *name);
+
+ +

+Delete the named property from the object. + +

Array properties

+ +
+int js_getlength(js_State *J, int idx);
+void js_setlength(js_State *J, int idx, int len);
+
+ +

+Wrappers to get and set the "length" property of an object. + +

+int js_hasindex(js_State *J, int idx, int i);
+void js_getindex(js_State *J, int idx, int i);
+void js_setindex(js_State *J, int idx, int i);
+void js_delindex(js_State *J, int idx, int i);
+
+ +

+These array index functions functions are simple wrappers around the equivalent property functions. +They convert the numeric index to a string to use as the property name. + +

Globals

+ +
+void js_pushglobal(js_State *J);
+
+ +

+Push the object representing the global environment record. + +

+void js_getglobal(js_State *J, const char *name);
+void js_setglobal(js_State *J, const char *name);
+void js_defglobal(js_State *J, const char *name, int atts);
+
+ +

+Wrappers around js_pushglobal and js_get/set/defproperty to read and write the values of global variables. + +

C Functions

+ +
+void js_newcfunction(js_State *J, js_CFunction fun, const char *name, int length);
+
+ +

+Push a function object wrapping a C function pointer. + +

+The length argument is the number of arguments to the function. +If the function is called with fewer arguments, the argument list will be padded with undefined. + +

+void js_newcconstructor(js_State *J,
+	js_CFunction fun, js_CFunction con,
+	const char *name, int length);
+
+ +

+Pop the object to set as the "prototype" property for the constructor function object. +Push a function object wrapping a C function pointer, allowing for separate function pointers for regular calls and 'new' operator calls. + +

+void js_currentfunction(js_State *J);
+
+ +

+Push the currently executing function object. + +

Userdata

+ +
+typedef void (*js_Finalize)(js_State *J, void *data);
+typedef int (*js_HasProperty)(js_State *J, void *data, const char *name);
+typedef int (*js_Put)(js_State *J, void *data, const char *name);
+typedef int (*js_Delete)(js_State *J, void *data, const char *name);
+
+void js_newuserdata(js_State *J, const char *tag, void *data,
+	js_Finalize finalize);
+
+void js_newuserdatax(js_State *J, const char *tag, void *data,
+	js_HasProperty has,
+	js_Put put,
+	js_Delete delete,
+	js_Finalize finalize);
+
+ +

+Pop an object from the top of the stack to use as the internal prototype property for the new object. +Push a new userdata object wrapping a pointer to C memory. +The userdata object is tagged using a string, to represent the type of the C memory. + +

+The finalize callback, if it is not NULL, will be called when the object is +freed by the garbage collector. + +

+The extended function also has callback functions for overriding property accesses. +If these are set, they can be used to override accesses to certain properties. +Any property accesses that are not overridden will be handled as usual in the runtime. +The "HasProperty" callback should push a value and return true if it wants to +handle the property, otherwise it should do nothing and return false. "Put" +should read the top value and return true if it wants to handle the property. +Likewise, "Delete" should return true if it wants to handle the property. + +

+int js_isuserdata(js_State *J, int idx, const char *tag);
+
+ +

+Test if an object is a userdata object with the given type tag string. + +

+void *js_touserdata(js_State *J, int idx, const char *tag);
+
+ +

+Return the wrapped pointer from a userdata object. +If the object is undefined or null, return NULL. +If the object is not a userdata object with the given type tag string, throw a type error. + +

Registry

+ +

+The registry can be used to store references to Javascript objects accessible from C, +but hidden from Javascript to prevent tampering. + +

+void js_getregistry(js_State *J, const char *name);
+void js_setregistry(js_State *J, const char *name);
+void js_delregistry(js_State *J, const char *name);
+
+ +

+Access properties on the hidden registry object. + +

+const char *js_ref(js_State *J);
+
+ +

+WIP: Pop a value from the stack and store it in the registry using a new unique property name. +Return the property name. + +

+void js_unref(js_State *J, const char *ref);
+
+ +

+WIP: Delete the reference from the registry. + +

+ +
+ +Copyright © 2013-2017 Artifex Software Inc. +
+ + + diff --git a/mujs/docs/style.css b/mujs/docs/style.css new file mode 100644 index 0000000..525f34b --- /dev/null +++ b/mujs/docs/style.css @@ -0,0 +1,40 @@ +h1, nav, footer { font-family: sans-serif; } + +a { text-decoration: none; } +a:hover { text-decoration: underline; } +h2 { font-size: 1.25rem; } +h3 { font-size: 1.12rem; } +ul li { list-style-type: circle; } +pre, table, ol, dl { margin-left: 2rem; } +li { margin: 0; } +th, td { text-align: left; vertical-align: top; } + +body { margin: 0; } +h1 { + font-weight: normal; + margin: 0; + padding: 1rem 2rem; +} +header{ + color: white; + background: no-repeat; + background-color: #36648b; + background-image: url("mujs-logo.png"); + background-position: top right; + min-height: 72px; +} +nav { + padding: 0.75rem 2rem; + background-color: #ddd; + no-text-transform: uppercase; +} +nav a { color: #303030; padding-right: 2rem; } +article { + max-width: 50rem; + margin: 2rem; +} +footer { + background-color: #ddd; + color: #303030; + padding: 1rem 2rem; +} diff --git a/mujs/genucd.py b/mujs/genucd.py new file mode 100644 index 0000000..ea75783 --- /dev/null +++ b/mujs/genucd.py @@ -0,0 +1,79 @@ +# Create utfdata.h from UnicodeData.txt + +import sys + +tolower = [] +toupper = [] +isalpha = [] + +for line in open(sys.argv[1]).readlines(): + line = line.split(";") + code = int(line[0],16) + # if code > 65535: continue # skip non-BMP codepoints + if line[2][0] == 'L': + isalpha.append(code) + if line[12]: + toupper.append((code,int(line[12],16))) + if line[13]: + tolower.append((code,int(line[13],16))) + +def dumpalpha(): + table = [] + prev = 0 + start = 0 + for code in isalpha: + if code != prev+1: + if start: + table.append((start,prev)) + start = code + prev = code + table.append((start,prev)) + + print("") + print("static const Rune ucd_alpha2[] = {") + for a, b in table: + if b - a > 0: + print(hex(a)+","+hex(b)+",") + print("};"); + + print("") + print("static const Rune ucd_alpha1[] = {") + for a, b in table: + if b - a == 0: + print(hex(a)+",") + print("};"); + +def dumpmap(name, input): + table = [] + prev_a = 0 + prev_b = 0 + start_a = 0 + start_b = 0 + for a, b in input: + if a != prev_a+1 or b != prev_b+1: + if start_a: + table.append((start_a,prev_a,start_b)) + start_a = a + start_b = b + prev_a = a + prev_b = b + table.append((start_a,prev_a,start_b)) + + print("") + print("static const Rune " + name + "2[] = {") + for a, b, n in table: + if b - a > 0: + print(hex(a)+","+hex(b)+","+str(n-a)+",") + print("};"); + + print("") + print("static const Rune " + name + "1[] = {") + for a, b, n in table: + if b - a == 0: + print(hex(a)+","+str(n-a)+",") + print("};"); + +print("/* This file was automatically created from " + sys.argv[1] + " */") +dumpalpha() +dumpmap("ucd_tolower", tolower) +dumpmap("ucd_toupper", toupper) diff --git a/mujs/jsarray.c b/mujs/jsarray.c new file mode 100644 index 0000000..962d59f --- /dev/null +++ b/mujs/jsarray.c @@ -0,0 +1,832 @@ +#include "jsi.h" + +#ifndef JS_HEAPSORT +#define JS_HEAPSORT 0 +#endif + +int js_getlength(js_State *J, int idx) +{ + int len; + js_getproperty(J, idx, "length"); + len = js_tointeger(J, -1); + js_pop(J, 1); + return len; +} + +void js_setlength(js_State *J, int idx, int len) +{ + js_pushnumber(J, len); + js_setproperty(J, idx < 0 ? idx - 1 : idx, "length"); +} + +static void jsB_new_Array(js_State *J) +{ + int i, top = js_gettop(J); + + js_newarray(J); + + if (top == 2) { + if (js_isnumber(J, 1)) { + js_copy(J, 1); + js_setproperty(J, -2, "length"); + } else { + js_copy(J, 1); + js_setindex(J, -2, 0); + } + } else { + for (i = 1; i < top; ++i) { + js_copy(J, i); + js_setindex(J, -2, i - 1); + } + } +} + +static void Ap_concat(js_State *J) +{ + int i, top = js_gettop(J); + int n, k, len; + + js_newarray(J); + n = 0; + + for (i = 0; i < top; ++i) { + js_copy(J, i); + if (js_isarray(J, -1)) { + len = js_getlength(J, -1); + for (k = 0; k < len; ++k) + if (js_hasindex(J, -1, k)) + js_setindex(J, -3, n++); + js_pop(J, 1); + } else { + js_setindex(J, -2, n++); + } + } +} + +static void Ap_join(js_State *J) +{ + char * volatile out = NULL; + const char * volatile r = NULL; + const char *sep; + int seplen; + int k, n, len, rlen; + + len = js_getlength(J, 0); + + if (js_isdefined(J, 1)) { + sep = js_tostring(J, 1); + seplen = strlen(sep); + } else { + sep = ","; + seplen = 1; + } + + if (len <= 0) { + js_pushliteral(J, ""); + return; + } + + if (js_try(J)) { + js_free(J, out); + js_throw(J); + } + + n = 0; + for (k = 0; k < len; ++k) { + js_getindex(J, 0, k); + if (js_iscoercible(J, -1)) { + r = js_tostring(J, -1); + rlen = strlen(r); + } else { + rlen = 0; + } + + if (k == 0) { + out = js_malloc(J, rlen + 1); + if (rlen > 0) { + memcpy(out, r, rlen); + n += rlen; + } + } else { + if (n + seplen + rlen > JS_STRLIMIT) + js_rangeerror(J, "invalid string length"); + out = js_realloc(J, out, n + seplen + rlen + 1); + if (seplen > 0) { + memcpy(out + n, sep, seplen); + n += seplen; + } + if (rlen > 0) { + memcpy(out + n, r, rlen); + n += rlen; + } + } + + js_pop(J, 1); + } + + js_pushlstring(J, out, n); + js_endtry(J); + js_free(J, out); +} + +static void Ap_pop(js_State *J) +{ + int n; + + n = js_getlength(J, 0); + + if (n > 0) { + js_getindex(J, 0, n - 1); + js_delindex(J, 0, n - 1); + js_setlength(J, 0, n - 1); + } else { + js_setlength(J, 0, 0); + js_pushundefined(J); + } +} + +static void Ap_push(js_State *J) +{ + int i, top = js_gettop(J); + int n; + + n = js_getlength(J, 0); + + for (i = 1; i < top; ++i, ++n) { + js_copy(J, i); + js_setindex(J, 0, n); + } + + js_setlength(J, 0, n); + + js_pushnumber(J, n); +} + +static void Ap_reverse(js_State *J) +{ + int len, middle, lower; + + len = js_getlength(J, 0); + middle = len / 2; + lower = 0; + + while (lower != middle) { + int upper = len - lower - 1; + int haslower = js_hasindex(J, 0, lower); + int hasupper = js_hasindex(J, 0, upper); + if (haslower && hasupper) { + js_setindex(J, 0, lower); + js_setindex(J, 0, upper); + } else if (hasupper) { + js_setindex(J, 0, lower); + js_delindex(J, 0, upper); + } else if (haslower) { + js_setindex(J, 0, upper); + js_delindex(J, 0, lower); + } + ++lower; + } + + js_copy(J, 0); +} + +static void Ap_shift(js_State *J) +{ + int k, len; + + len = js_getlength(J, 0); + + if (len == 0) { + js_setlength(J, 0, 0); + js_pushundefined(J); + return; + } + + js_getindex(J, 0, 0); + + for (k = 1; k < len; ++k) { + if (js_hasindex(J, 0, k)) + js_setindex(J, 0, k - 1); + else + js_delindex(J, 0, k - 1); + } + + js_delindex(J, 0, len - 1); + js_setlength(J, 0, len - 1); +} + +static void Ap_slice(js_State *J) +{ + int len, s, e, n; + double sv, ev; + + js_newarray(J); + + len = js_getlength(J, 0); + sv = js_tointeger(J, 1); + ev = js_isdefined(J, 2) ? js_tointeger(J, 2) : len; + + if (sv < 0) sv = sv + len; + if (ev < 0) ev = ev + len; + + s = sv < 0 ? 0 : sv > len ? len : sv; + e = ev < 0 ? 0 : ev > len ? len : ev; + + for (n = 0; s < e; ++s, ++n) + if (js_hasindex(J, 0, s)) + js_setindex(J, -2, n); +} + +static int Ap_sort_cmp(js_State *J, int idx_a, int idx_b) +{ + js_Object *obj = js_tovalue(J, 0)->u.object; + if (obj->u.a.simple) { + js_Value *val_a = &obj->u.a.array[idx_a]; + js_Value *val_b = &obj->u.a.array[idx_b]; + int und_a = val_a->t.type == JS_TUNDEFINED; + int und_b = val_b->t.type == JS_TUNDEFINED; + if (und_a) return und_b; + if (und_b) return -1; + if (js_iscallable(J, 1)) { + double v; + js_copy(J, 1); /* copy function */ + js_pushundefined(J); /* no 'this' binding */ + js_pushvalue(J, *val_a); + js_pushvalue(J, *val_b); + js_call(J, 2); + v = js_tonumber(J, -1); + js_pop(J, 1); + if (isnan(v)) + return 0; + if (v == 0) + return 0; + return v < 0 ? -1 : 1; + } else { + const char *str_a, *str_b; + int c; + js_pushvalue(J, *val_a); + js_pushvalue(J, *val_b); + str_a = js_tostring(J, -2); + str_b = js_tostring(J, -1); + c = strcmp(str_a, str_b); + js_pop(J, 2); + return c; + } + } else { + int und_a, und_b; + int has_a = js_hasindex(J, 0, idx_a); + int has_b = js_hasindex(J, 0, idx_b); + if (!has_a && !has_b) { + return 0; + } + if (has_a && !has_b) { + js_pop(J, 1); + return -1; + } + if (!has_a && has_b) { + js_pop(J, 1); + return 1; + } + + und_a = js_isundefined(J, -2); + und_b = js_isundefined(J, -1); + if (und_a) { + js_pop(J, 2); + return und_b; + } + if (und_b) { + js_pop(J, 2); + return -1; + } + + if (js_iscallable(J, 1)) { + double v; + js_copy(J, 1); /* copy function */ + js_pushundefined(J); /* no 'this' binding */ + js_copy(J, -4); + js_copy(J, -4); + js_call(J, 2); + v = js_tonumber(J, -1); + js_pop(J, 3); + if (isnan(v)) + return 0; + if (v == 0) + return 0; + return v < 0 ? -1 : 1; + } else { + const char *str_a = js_tostring(J, -2); + const char *str_b = js_tostring(J, -1); + int c = strcmp(str_a, str_b); + js_pop(J, 2); + return c; + } + } +} + +static void Ap_sort_swap(js_State *J, int idx_a, int idx_b) +{ + js_Object *obj = js_tovalue(J, 0)->u.object; + if (obj->u.a.simple) { + js_Value tmp = obj->u.a.array[idx_a]; + obj->u.a.array[idx_a] = obj->u.a.array[idx_b]; + obj->u.a.array[idx_b] = tmp; + } else { + int has_a = js_hasindex(J, 0, idx_a); + int has_b = js_hasindex(J, 0, idx_b); + if (has_a && has_b) { + js_setindex(J, 0, idx_a); + js_setindex(J, 0, idx_b); + } else if (has_a && !has_b) { + js_delindex(J, 0, idx_a); + js_setindex(J, 0, idx_b); + } else if (!has_a && has_b) { + js_delindex(J, 0, idx_b); + js_setindex(J, 0, idx_a); + } + } +} + +/* A bottom-up/bouncing heapsort implementation */ + +static int Ap_sort_leaf(js_State *J, int i, int end) +{ + int j = i; + int lc = (j << 1) + 1; /* left child */ + int rc = (j << 1) + 2; /* right child */ + while (rc < end) { + if (Ap_sort_cmp(J, rc, lc) > 0) + j = rc; + else + j = lc; + lc = (j << 1) + 1; + rc = (j << 1) + 2; + } + if (lc < end) + j = lc; + return j; +} + +static void Ap_sort_sift(js_State *J, int i, int end) +{ + int j = Ap_sort_leaf(J, i, end); + while (Ap_sort_cmp(J, i, j) > 0) + j = (j - 1) >> 1; /* parent */ + while (j > i) { + Ap_sort_swap(J, i, j); + j = (j - 1) >> 1; /* parent */ + } +} + +static void Ap_sort_heapsort(js_State *J, int n) +{ + int i; + for (i = n / 2 - 1; i >= 0; --i) + Ap_sort_sift(J, i, n); + for (i = n - 1; i > 0; --i) { + Ap_sort_swap(J, 0, i); + Ap_sort_sift(J, 0, i); + } +} + +static void Ap_sort(js_State *J) +{ + int len; + + len = js_getlength(J, 0); + if (len <= 1) { + js_copy(J, 0); + return; + } + + if (!js_iscallable(J, 1) && !js_isundefined(J, 1)) + js_typeerror(J, "comparison function must be a function or undefined"); + + if (len >= INT_MAX) + js_rangeerror(J, "array is too large to sort"); + + Ap_sort_heapsort(J, len); + + js_copy(J, 0); +} + +static void Ap_splice(js_State *J) +{ + int top = js_gettop(J); + int len, start, del, add, k; + + len = js_getlength(J, 0); + start = js_tointeger(J, 1); + if (start < 0) + start = (len + start) > 0 ? len + start : 0; + else if (start > len) + start = len; + + if (js_isdefined(J, 2)) + del = js_tointeger(J, 2); + else + del = len - start; + if (del > len - start) + del = len - start; + if (del < 0) + del = 0; + + js_newarray(J); + + /* copy deleted items to return array */ + for (k = 0; k < del; ++k) + if (js_hasindex(J, 0, start + k)) + js_setindex(J, -2, k); + js_setlength(J, -1, del); + + /* shift the tail to resize the hole left by deleted items */ + add = top - 3; + if (add < del) { + for (k = start; k < len - del; ++k) { + if (js_hasindex(J, 0, k + del)) + js_setindex(J, 0, k + add); + else + js_delindex(J, 0, k + add); + } + for (k = len; k > len - del + add; --k) + js_delindex(J, 0, k - 1); + } else if (add > del) { + for (k = len - del; k > start; --k) { + if (js_hasindex(J, 0, k + del - 1)) + js_setindex(J, 0, k + add - 1); + else + js_delindex(J, 0, k + add - 1); + } + } + + /* copy new items into the hole */ + for (k = 0; k < add; ++k) { + js_copy(J, 3 + k); + js_setindex(J, 0, start + k); + } + + js_setlength(J, 0, len - del + add); +} + +static void Ap_unshift(js_State *J) +{ + int i, top = js_gettop(J); + int k, len; + + len = js_getlength(J, 0); + + for (k = len; k > 0; --k) { + int from = k - 1; + int to = k + top - 2; + if (js_hasindex(J, 0, from)) + js_setindex(J, 0, to); + else + js_delindex(J, 0, to); + } + + for (i = 1; i < top; ++i) { + js_copy(J, i); + js_setindex(J, 0, i - 1); + } + + js_setlength(J, 0, len + top - 1); + + js_pushnumber(J, len + top - 1); +} + +static void Ap_toString(js_State *J) +{ + if (!js_iscoercible(J, 0)) + js_typeerror(J, "'this' is not an object"); + js_getproperty(J, 0, "join"); + if (!js_iscallable(J, -1)) { + js_pop(J, 1); + /* TODO: call Object.prototype.toString implementation directly */ + js_getglobal(J, "Object"); + js_getproperty(J, -1, "prototype"); + js_rot2pop1(J); + js_getproperty(J, -1, "toString"); + js_rot2pop1(J); + } + js_copy(J, 0); + js_call(J, 0); +} + +static void Ap_indexOf(js_State *J) +{ + int k, len, from; + + len = js_getlength(J, 0); + from = js_isdefined(J, 2) ? js_tointeger(J, 2) : 0; + if (from < 0) from = len + from; + if (from < 0) from = 0; + + js_copy(J, 1); + for (k = from; k < len; ++k) { + if (js_hasindex(J, 0, k)) { + if (js_strictequal(J)) { + js_pushnumber(J, k); + return; + } + js_pop(J, 1); + } + } + + js_pushnumber(J, -1); +} + +static void Ap_lastIndexOf(js_State *J) +{ + int k, len, from; + + len = js_getlength(J, 0); + from = js_isdefined(J, 2) ? js_tointeger(J, 2) : len - 1; + if (from > len - 1) from = len - 1; + if (from < 0) from = len + from; + + js_copy(J, 1); + for (k = from; k >= 0; --k) { + if (js_hasindex(J, 0, k)) { + if (js_strictequal(J)) { + js_pushnumber(J, k); + return; + } + js_pop(J, 1); + } + } + + js_pushnumber(J, -1); +} + +static void Ap_every(js_State *J) +{ + int hasthis = js_gettop(J) >= 3; + int k, len; + + if (!js_iscallable(J, 1)) + js_typeerror(J, "callback is not a function"); + + len = js_getlength(J, 0); + for (k = 0; k < len; ++k) { + if (js_hasindex(J, 0, k)) { + js_copy(J, 1); + if (hasthis) + js_copy(J, 2); + else + js_pushundefined(J); + js_copy(J, -3); + js_pushnumber(J, k); + js_copy(J, 0); + js_call(J, 3); + if (!js_toboolean(J, -1)) + return; + js_pop(J, 2); + } + } + + js_pushboolean(J, 1); +} + +static void Ap_some(js_State *J) +{ + int hasthis = js_gettop(J) >= 3; + int k, len; + + if (!js_iscallable(J, 1)) + js_typeerror(J, "callback is not a function"); + + len = js_getlength(J, 0); + for (k = 0; k < len; ++k) { + if (js_hasindex(J, 0, k)) { + js_copy(J, 1); + if (hasthis) + js_copy(J, 2); + else + js_pushundefined(J); + js_copy(J, -3); + js_pushnumber(J, k); + js_copy(J, 0); + js_call(J, 3); + if (js_toboolean(J, -1)) + return; + js_pop(J, 2); + } + } + + js_pushboolean(J, 0); +} + +static void Ap_forEach(js_State *J) +{ + int hasthis = js_gettop(J) >= 3; + int k, len; + + if (!js_iscallable(J, 1)) + js_typeerror(J, "callback is not a function"); + + len = js_getlength(J, 0); + for (k = 0; k < len; ++k) { + if (js_hasindex(J, 0, k)) { + js_copy(J, 1); + if (hasthis) + js_copy(J, 2); + else + js_pushundefined(J); + js_copy(J, -3); + js_pushnumber(J, k); + js_copy(J, 0); + js_call(J, 3); + js_pop(J, 2); + } + } + + js_pushundefined(J); +} + +static void Ap_map(js_State *J) +{ + int hasthis = js_gettop(J) >= 3; + int k, len; + + if (!js_iscallable(J, 1)) + js_typeerror(J, "callback is not a function"); + + js_newarray(J); + + len = js_getlength(J, 0); + for (k = 0; k < len; ++k) { + if (js_hasindex(J, 0, k)) { + js_copy(J, 1); + if (hasthis) + js_copy(J, 2); + else + js_pushundefined(J); + js_copy(J, -3); + js_pushnumber(J, k); + js_copy(J, 0); + js_call(J, 3); + js_setindex(J, -3, k); + js_pop(J, 1); + } + } + js_setlength(J, -1, len); +} + +static void Ap_filter(js_State *J) +{ + int hasthis = js_gettop(J) >= 3; + int k, to, len; + + if (!js_iscallable(J, 1)) + js_typeerror(J, "callback is not a function"); + + js_newarray(J); + to = 0; + + len = js_getlength(J, 0); + for (k = 0; k < len; ++k) { + if (js_hasindex(J, 0, k)) { + js_copy(J, 1); + if (hasthis) + js_copy(J, 2); + else + js_pushundefined(J); + js_copy(J, -3); + js_pushnumber(J, k); + js_copy(J, 0); + js_call(J, 3); + if (js_toboolean(J, -1)) { + js_pop(J, 1); + js_setindex(J, -2, to++); + } else { + js_pop(J, 2); + } + } + } +} + +static void Ap_reduce(js_State *J) +{ + int hasinitial = js_gettop(J) >= 3; + int k, len; + + if (!js_iscallable(J, 1)) + js_typeerror(J, "callback is not a function"); + + len = js_getlength(J, 0); + k = 0; + + if (len == 0 && !hasinitial) + js_typeerror(J, "no initial value"); + + /* initial value of accumulator */ + if (hasinitial) + js_copy(J, 2); + else { + while (k < len) + if (js_hasindex(J, 0, k++)) + break; + if (k == len) + js_typeerror(J, "no initial value"); + } + + while (k < len) { + if (js_hasindex(J, 0, k)) { + js_copy(J, 1); + js_pushundefined(J); + js_rot(J, 4); /* accumulator on top */ + js_rot(J, 4); /* property on top */ + js_pushnumber(J, k); + js_copy(J, 0); + js_call(J, 4); /* calculate new accumulator */ + } + ++k; + } + + /* return accumulator */ +} + +static void Ap_reduceRight(js_State *J) +{ + int hasinitial = js_gettop(J) >= 3; + int k, len; + + if (!js_iscallable(J, 1)) + js_typeerror(J, "callback is not a function"); + + len = js_getlength(J, 0); + k = len - 1; + + if (len == 0 && !hasinitial) + js_typeerror(J, "no initial value"); + + /* initial value of accumulator */ + if (hasinitial) + js_copy(J, 2); + else { + while (k >= 0) + if (js_hasindex(J, 0, k--)) + break; + if (k < 0) + js_typeerror(J, "no initial value"); + } + + while (k >= 0) { + if (js_hasindex(J, 0, k)) { + js_copy(J, 1); + js_pushundefined(J); + js_rot(J, 4); /* accumulator on top */ + js_rot(J, 4); /* property on top */ + js_pushnumber(J, k); + js_copy(J, 0); + js_call(J, 4); /* calculate new accumulator */ + } + --k; + } + + /* return accumulator */ +} + +static void A_isArray(js_State *J) +{ + if (js_isobject(J, 1)) { + js_Object *T = js_toobject(J, 1); + js_pushboolean(J, T->type == JS_CARRAY); + } else { + js_pushboolean(J, 0); + } +} + +void jsB_initarray(js_State *J) +{ + js_pushobject(J, J->Array_prototype); + { + jsB_propf(J, "Array.prototype.toString", Ap_toString, 0); + jsB_propf(J, "Array.prototype.concat", Ap_concat, 0); /* 1 */ + jsB_propf(J, "Array.prototype.join", Ap_join, 1); + jsB_propf(J, "Array.prototype.pop", Ap_pop, 0); + jsB_propf(J, "Array.prototype.push", Ap_push, 0); /* 1 */ + jsB_propf(J, "Array.prototype.reverse", Ap_reverse, 0); + jsB_propf(J, "Array.prototype.shift", Ap_shift, 0); + jsB_propf(J, "Array.prototype.slice", Ap_slice, 2); + jsB_propf(J, "Array.prototype.sort", Ap_sort, 1); + jsB_propf(J, "Array.prototype.splice", Ap_splice, 2); + jsB_propf(J, "Array.prototype.unshift", Ap_unshift, 0); /* 1 */ + + /* ES5 */ + jsB_propf(J, "Array.prototype.indexOf", Ap_indexOf, 1); + jsB_propf(J, "Array.prototype.lastIndexOf", Ap_lastIndexOf, 1); + jsB_propf(J, "Array.prototype.every", Ap_every, 1); + jsB_propf(J, "Array.prototype.some", Ap_some, 1); + jsB_propf(J, "Array.prototype.forEach", Ap_forEach, 1); + jsB_propf(J, "Array.prototype.map", Ap_map, 1); + jsB_propf(J, "Array.prototype.filter", Ap_filter, 1); + jsB_propf(J, "Array.prototype.reduce", Ap_reduce, 1); + jsB_propf(J, "Array.prototype.reduceRight", Ap_reduceRight, 1); + } + js_newcconstructor(J, jsB_new_Array, jsB_new_Array, "Array", 0); /* 1 */ + { + /* ES5 */ + jsB_propf(J, "Array.isArray", A_isArray, 1); + } + js_defglobal(J, "Array", JS_DONTENUM); +} diff --git a/mujs/jsboolean.c b/mujs/jsboolean.c new file mode 100644 index 0000000..dd9df76 --- /dev/null +++ b/mujs/jsboolean.c @@ -0,0 +1,38 @@ +#include "jsi.h" + +static void jsB_new_Boolean(js_State *J) +{ + js_newboolean(J, js_toboolean(J, 1)); +} + +static void jsB_Boolean(js_State *J) +{ + js_pushboolean(J, js_toboolean(J, 1)); +} + +static void Bp_toString(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean"); + js_pushliteral(J, self->u.boolean ? "true" : "false"); +} + +static void Bp_valueOf(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean"); + js_pushboolean(J, self->u.boolean); +} + +void jsB_initboolean(js_State *J) +{ + J->Boolean_prototype->u.boolean = 0; + + js_pushobject(J, J->Boolean_prototype); + { + jsB_propf(J, "Boolean.prototype.toString", Bp_toString, 0); + jsB_propf(J, "Boolean.prototype.valueOf", Bp_valueOf, 0); + } + js_newcconstructor(J, jsB_Boolean, jsB_new_Boolean, "Boolean", 1); + js_defglobal(J, "Boolean", JS_DONTENUM); +} diff --git a/mujs/jsbuiltin.c b/mujs/jsbuiltin.c new file mode 100644 index 0000000..23ed11f --- /dev/null +++ b/mujs/jsbuiltin.c @@ -0,0 +1,249 @@ +#include "jsi.h" +#include "regexp.h" + +static void jsB_globalf(js_State *J, const char *name, js_CFunction cfun, int n) +{ + js_newcfunction(J, cfun, name, n); + js_defglobal(J, name, JS_DONTENUM); +} + +void jsB_propf(js_State *J, const char *name, js_CFunction cfun, int n) +{ + const char *pname = strrchr(name, '.'); + pname = pname ? pname + 1 : name; + js_newcfunction(J, cfun, name, n); + js_defproperty(J, -2, pname, JS_DONTENUM); +} + +void jsB_propn(js_State *J, const char *name, double number) +{ + js_pushnumber(J, number); + js_defproperty(J, -2, name, JS_READONLY | JS_DONTENUM | JS_DONTCONF); +} + +void jsB_props(js_State *J, const char *name, const char *string) +{ + js_pushliteral(J, string); + js_defproperty(J, -2, name, JS_DONTENUM); +} + +static void jsB_parseInt(js_State *J) +{ + const char *s = js_tostring(J, 1); + int radix = js_isdefined(J, 2) ? js_tointeger(J, 2) : 0; + double sign = 1; + double n; + char *e; + + while (jsY_iswhite(*s) || jsY_isnewline(*s)) + ++s; + if (*s == '-') { + ++s; + sign = -1; + } else if (*s == '+') { + ++s; + } + if (radix == 0) { + radix = 10; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + radix = 16; + } + } else if (radix < 2 || radix > 36) { + js_pushnumber(J, NAN); + return; + } + n = js_strtol(s, &e, radix); + if (s == e) + js_pushnumber(J, NAN); + else + js_pushnumber(J, n * sign); +} + +static void jsB_parseFloat(js_State *J) +{ + const char *s = js_tostring(J, 1); + char *e; + double n; + + while (jsY_iswhite(*s) || jsY_isnewline(*s)) ++s; + if (!strncmp(s, "Infinity", 8)) + js_pushnumber(J, INFINITY); + else if (!strncmp(s, "+Infinity", 9)) + js_pushnumber(J, INFINITY); + else if (!strncmp(s, "-Infinity", 9)) + js_pushnumber(J, -INFINITY); + else { + n = js_stringtofloat(s, &e); + if (e == s) + js_pushnumber(J, NAN); + else + js_pushnumber(J, n); + } +} + +static void jsB_isNaN(js_State *J) +{ + double n = js_tonumber(J, 1); + js_pushboolean(J, isnan(n)); +} + +static void jsB_isFinite(js_State *J) +{ + double n = js_tonumber(J, 1); + js_pushboolean(J, isfinite(n)); +} + +static void Encode(js_State *J, const char *str_, const char *unescaped) +{ + /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */ + const char * volatile str = str_; + js_Buffer *sb = NULL; + + static const char *HEX = "0123456789ABCDEF"; + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + + while (*str) { + int c = (unsigned char) *str++; + if (strchr(unescaped, c)) + js_putc(J, &sb, c); + else { + js_putc(J, &sb, '%'); + js_putc(J, &sb, HEX[(c >> 4) & 0xf]); + js_putc(J, &sb, HEX[c & 0xf]); + } + } + js_putc(J, &sb, 0); + + js_pushstring(J, sb ? sb->s : ""); + js_endtry(J); + js_free(J, sb); +} + +static void Decode(js_State *J, const char *str_, const char *reserved) +{ + /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */ + const char * volatile str = str_; + js_Buffer *sb = NULL; + int a, b; + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + + while (*str) { + int c = (unsigned char) *str++; + if (c != '%') + js_putc(J, &sb, c); + else { + if (!str[0] || !str[1]) + js_urierror(J, "truncated escape sequence"); + a = *str++; + b = *str++; + if (!jsY_ishex(a) || !jsY_ishex(b)) + js_urierror(J, "invalid escape sequence"); + c = jsY_tohex(a) << 4 | jsY_tohex(b); + if (!strchr(reserved, c)) + js_putc(J, &sb, c); + else { + js_putc(J, &sb, '%'); + js_putc(J, &sb, a); + js_putc(J, &sb, b); + } + } + } + js_putc(J, &sb, 0); + + js_pushstring(J, sb ? sb->s : ""); + js_endtry(J); + js_free(J, sb); +} + +#define URIRESERVED ";/?:@&=+$," +#define URIALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define URIDIGIT "0123456789" +#define URIMARK "-_.!~*'()" +#define URIUNESCAPED URIALPHA URIDIGIT URIMARK + +static void jsB_decodeURI(js_State *J) +{ + Decode(J, js_tostring(J, 1), URIRESERVED "#"); +} + +static void jsB_decodeURIComponent(js_State *J) +{ + Decode(J, js_tostring(J, 1), ""); +} + +static void jsB_encodeURI(js_State *J) +{ + Encode(J, js_tostring(J, 1), URIUNESCAPED URIRESERVED "#"); +} + +static void jsB_encodeURIComponent(js_State *J) +{ + Encode(J, js_tostring(J, 1), URIUNESCAPED); +} + +void jsB_init(js_State *J) +{ + /* Create the prototype objects here, before the constructors */ + J->Object_prototype = jsV_newobject(J, JS_COBJECT, NULL); + J->Array_prototype = jsV_newobject(J, JS_CARRAY, J->Object_prototype); + J->Function_prototype = jsV_newobject(J, JS_CCFUNCTION, J->Object_prototype); + J->Boolean_prototype = jsV_newobject(J, JS_CBOOLEAN, J->Object_prototype); + J->Number_prototype = jsV_newobject(J, JS_CNUMBER, J->Object_prototype); + J->String_prototype = jsV_newobject(J, JS_CSTRING, J->Object_prototype); + J->Date_prototype = jsV_newobject(J, JS_CDATE, J->Object_prototype); + + J->RegExp_prototype = jsV_newobject(J, JS_CREGEXP, J->Object_prototype); + J->RegExp_prototype->u.r.prog = js_regcompx(J->alloc, J->actx, "(?:)", 0, NULL); + J->RegExp_prototype->u.r.source = js_strdup(J, "(?:)"); + + /* All the native error types */ + J->Error_prototype = jsV_newobject(J, JS_CERROR, J->Object_prototype); + J->EvalError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); + J->RangeError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); + J->ReferenceError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); + J->SyntaxError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); + J->TypeError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); + J->URIError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); + + /* Create the constructors and fill out the prototype objects */ + jsB_initobject(J); + jsB_initarray(J); + jsB_initfunction(J); + jsB_initboolean(J); + jsB_initnumber(J); + jsB_initstring(J); + jsB_initregexp(J); + jsB_initdate(J); + jsB_initerror(J); + jsB_initmath(J); + jsB_initjson(J); + + /* Initialize the global object */ + js_pushnumber(J, NAN); + js_defglobal(J, "NaN", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + + js_pushnumber(J, INFINITY); + js_defglobal(J, "Infinity", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + + js_pushundefined(J); + js_defglobal(J, "undefined", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + + jsB_globalf(J, "parseInt", jsB_parseInt, 1); + jsB_globalf(J, "parseFloat", jsB_parseFloat, 1); + jsB_globalf(J, "isNaN", jsB_isNaN, 1); + jsB_globalf(J, "isFinite", jsB_isFinite, 1); + + jsB_globalf(J, "decodeURI", jsB_decodeURI, 1); + jsB_globalf(J, "decodeURIComponent", jsB_decodeURIComponent, 1); + jsB_globalf(J, "encodeURI", jsB_encodeURI, 1); + jsB_globalf(J, "encodeURIComponent", jsB_encodeURIComponent, 1); +} diff --git a/mujs/jscompile.c b/mujs/jscompile.c new file mode 100644 index 0000000..c84e4b2 --- /dev/null +++ b/mujs/jscompile.c @@ -0,0 +1,1428 @@ +#include "jsi.h" + +#define cexp jsC_cexp /* collision with math.h */ + +#define JF js_State *J, js_Function *F + +JS_NORETURN void jsC_error(js_State *J, js_Ast *node, const char *fmt, ...) JS_PRINTFLIKE(3,4); + +static void cfunbody(JF, js_Ast *name, js_Ast *params, js_Ast *body, int is_fun_exp); +static void cexp(JF, js_Ast *exp); +static void cstmlist(JF, js_Ast *list); +static void cstm(JF, js_Ast *stm); + +void jsC_error(js_State *J, js_Ast *node, const char *fmt, ...) +{ + va_list ap; + char buf[512]; + char msgbuf[256]; + + va_start(ap, fmt); + vsnprintf(msgbuf, 256, fmt, ap); + va_end(ap); + + snprintf(buf, 256, "%s:%d: ", J->filename, node->line); + strcat(buf, msgbuf); + + js_newsyntaxerror(J, buf); + js_throw(J); +} + +static const char *futurewords[] = { + "class", "const", "enum", "export", "extends", "import", "super", +}; + +static const char *strictfuturewords[] = { + "implements", "interface", "let", "package", "private", "protected", + "public", "static", "yield", +}; + +static void checkfutureword(JF, js_Ast *exp) +{ + if (jsY_findword(exp->string, futurewords, nelem(futurewords)) >= 0) + jsC_error(J, exp, "'%s' is a future reserved word", exp->string); + if (F->strict) { + if (jsY_findword(exp->string, strictfuturewords, nelem(strictfuturewords)) >= 0) + jsC_error(J, exp, "'%s' is a strict mode future reserved word", exp->string); + } +} + +static js_Function *newfun(js_State *J, int line, js_Ast *name, js_Ast *params, js_Ast *body, int script, int default_strict, int is_fun_exp) +{ + js_Function *F = js_malloc(J, sizeof *F); + memset(F, 0, sizeof *F); + F->gcmark = 0; + F->gcnext = J->gcfun; + J->gcfun = F; + ++J->gccounter; + + F->filename = js_intern(J, J->filename); + F->line = line; + F->script = script; + F->strict = default_strict; + F->name = name ? name->string : ""; + + cfunbody(J, F, name, params, body, is_fun_exp); + + return F; +} + +/* Emit opcodes, constants and jumps */ + +static void emitraw(JF, int value) +{ + if (value != (js_Instruction)value) + js_syntaxerror(J, "integer overflow in instruction coding"); + if (F->codelen >= F->codecap) { + F->codecap = F->codecap ? F->codecap * 2 : 64; + F->code = js_realloc(J, F->code, F->codecap * sizeof *F->code); + } + F->code[F->codelen++] = value; +} + +static void emit(JF, int value) +{ + emitraw(J, F, F->lastline); + emitraw(J, F, value); +} + +static void emitarg(JF, int value) +{ + emitraw(J, F, value); +} + +static void emitline(JF, js_Ast *node) +{ + F->lastline = node->line; +} + +static int addfunction(JF, js_Function *value) +{ + if (F->funlen >= F->funcap) { + F->funcap = F->funcap ? F->funcap * 2 : 16; + F->funtab = js_realloc(J, F->funtab, F->funcap * sizeof *F->funtab); + } + F->funtab[F->funlen] = value; + return F->funlen++; +} + +static int addlocal(JF, js_Ast *ident, int reuse) +{ + const char *name = ident->string; + if (F->strict) { + if (!strcmp(name, "arguments")) + jsC_error(J, ident, "redefining 'arguments' is not allowed in strict mode"); + if (!strcmp(name, "eval")) + jsC_error(J, ident, "redefining 'eval' is not allowed in strict mode"); + } else { + if (!strcmp(name, "eval")) + js_evalerror(J, "%s:%d: invalid use of 'eval'", J->filename, ident->line); + } + if (reuse || F->strict) { + int i; + for (i = 0; i < F->varlen; ++i) { + if (!strcmp(F->vartab[i], name)) { + if (reuse) + return i+1; + if (F->strict) + jsC_error(J, ident, "duplicate formal parameter '%s'", name); + } + } + } + if (F->varlen >= F->varcap) { + F->varcap = F->varcap ? F->varcap * 2 : 16; + F->vartab = js_realloc(J, F->vartab, F->varcap * sizeof *F->vartab); + } + F->vartab[F->varlen] = name; + return ++F->varlen; +} + +static int findlocal(JF, const char *name) +{ + int i; + for (i = F->varlen; i > 0; --i) + if (!strcmp(F->vartab[i-1], name)) + return i; + return -1; +} + +static void emitfunction(JF, js_Function *fun) +{ + F->lightweight = 0; + emit(J, F, OP_CLOSURE); + emitarg(J, F, addfunction(J, F, fun)); +} + +static void emitnumber(JF, double num) +{ + if (num == 0) { + emit(J, F, OP_INTEGER); + emitarg(J, F, 32768); + if (signbit(num)) + emit(J, F, OP_NEG); + } else if (num >= SHRT_MIN && num <= SHRT_MAX && num == (int)num) { + emit(J, F, OP_INTEGER); + emitarg(J, F, num + 32768); + } else { +#define N (sizeof(num) / sizeof(js_Instruction)) + js_Instruction x[N]; + size_t i; + emit(J, F, OP_NUMBER); + memcpy(x, &num, sizeof(num)); + for (i = 0; i < N; ++i) + emitarg(J, F, x[i]); +#undef N + } +} + +static void emitstring(JF, int opcode, const char *str) +{ +#define N (sizeof(str) / sizeof(js_Instruction)) + js_Instruction x[N]; + size_t i; + emit(J, F, opcode); + memcpy(x, &str, sizeof(str)); + for (i = 0; i < N; ++i) + emitarg(J, F, x[i]); +#undef N +} + +static void emitlocal(JF, int oploc, int opvar, js_Ast *ident) +{ + int is_arguments = !strcmp(ident->string, "arguments"); + int is_eval = !strcmp(ident->string, "eval"); + int i; + + if (is_arguments) { + F->lightweight = 0; + F->arguments = 1; + } + + checkfutureword(J, F, ident); + if (F->strict && oploc == OP_SETLOCAL) { + if (is_arguments) + jsC_error(J, ident, "'arguments' is read-only in strict mode"); + if (is_eval) + jsC_error(J, ident, "'eval' is read-only in strict mode"); + } + if (is_eval) + js_evalerror(J, "%s:%d: invalid use of 'eval'", J->filename, ident->line); + + i = findlocal(J, F, ident->string); + if (i < 0) { + emitstring(J, F, opvar, ident->string); + } else { + emit(J, F, oploc); + emitarg(J, F, i); + } +} + +static int here(JF) +{ + return F->codelen; +} + +static int emitjump(JF, int opcode) +{ + int inst; + emit(J, F, opcode); + inst = F->codelen; + emitarg(J, F, 0); + return inst; +} + +static void emitjumpto(JF, int opcode, int dest) +{ + emit(J, F, opcode); + if (dest != (js_Instruction)dest) + js_syntaxerror(J, "jump address integer overflow"); + emitarg(J, F, dest); +} + +static void labelto(JF, int inst, int addr) +{ + if (addr != (js_Instruction)addr) + js_syntaxerror(J, "jump address integer overflow"); + F->code[inst] = addr; +} + +static void label(JF, int inst) +{ + labelto(J, F, inst, F->codelen); +} + +/* Expressions */ + +static void ctypeof(JF, js_Ast *exp) +{ + if (exp->a->type == EXP_IDENTIFIER) { + emitline(J, F, exp->a); + emitlocal(J, F, OP_GETLOCAL, OP_HASVAR, exp->a); + } else { + cexp(J, F, exp->a); + } + emitline(J, F, exp); + emit(J, F, OP_TYPEOF); +} + +static void cunary(JF, js_Ast *exp, int opcode) +{ + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, opcode); +} + +static void cbinary(JF, js_Ast *exp, int opcode) +{ + cexp(J, F, exp->a); + cexp(J, F, exp->b); + emitline(J, F, exp); + emit(J, F, opcode); +} + +static void carray(JF, js_Ast *list) +{ + while (list) { + emitline(J, F, list->a); + if (list->a->type == EXP_ELISION) { + emit(J, F, OP_SKIPARRAY); + } else { + cexp(J, F, list->a); + emit(J, F, OP_INITARRAY); + } + list = list->b; + } +} + +static void checkdup(JF, js_Ast *list, js_Ast *end) +{ + char nbuf[32], sbuf[32]; + const char *needle, *straw; + + if (end->a->type == EXP_NUMBER) + needle = jsV_numbertostring(J, nbuf, end->a->number); + else + needle = end->a->string; + + while (list->a != end) { + if (list->a->type == end->type) { + js_Ast *prop = list->a->a; + if (prop->type == EXP_NUMBER) + straw = jsV_numbertostring(J, sbuf, prop->number); + else + straw = prop->string; + if (!strcmp(needle, straw)) + jsC_error(J, list, "duplicate property '%s' in object literal", needle); + } + list = list->b; + } +} + +static void cobject(JF, js_Ast *list) +{ + js_Ast *head = list; + + while (list) { + js_Ast *kv = list->a; + js_Ast *prop = kv->a; + + if (prop->type == AST_IDENTIFIER || prop->type == EXP_STRING) { + emitline(J, F, prop); + emitstring(J, F, OP_STRING, prop->string); + } else if (prop->type == EXP_NUMBER) { + emitline(J, F, prop); + emitnumber(J, F, prop->number); + } else { + jsC_error(J, prop, "invalid property name in object initializer"); + } + + if (F->strict) + checkdup(J, F, head, kv); + + switch (kv->type) { + default: /* impossible */ break; + case EXP_PROP_VAL: + cexp(J, F, kv->b); + emitline(J, F, kv); + emit(J, F, OP_INITPROP); + break; + case EXP_PROP_GET: + emitfunction(J, F, newfun(J, prop->line, NULL, NULL, kv->c, 0, F->strict, 1)); + emitline(J, F, kv); + emit(J, F, OP_INITGETTER); + break; + case EXP_PROP_SET: + emitfunction(J, F, newfun(J, prop->line, NULL, kv->b, kv->c, 0, F->strict, 1)); + emitline(J, F, kv); + emit(J, F, OP_INITSETTER); + break; + } + + list = list->b; + } +} + +static int cargs(JF, js_Ast *list) +{ + int n = 0; + while (list) { + cexp(J, F, list->a); + list = list->b; + ++n; + } + return n; +} + +static void cassign(JF, js_Ast *exp) +{ + js_Ast *lhs = exp->a; + js_Ast *rhs = exp->b; + switch (lhs->type) { + case EXP_IDENTIFIER: + cexp(J, F, rhs); + emitline(J, F, exp); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs); + break; + case EXP_INDEX: + cexp(J, F, lhs->a); + cexp(J, F, lhs->b); + cexp(J, F, rhs); + emitline(J, F, exp); + emit(J, F, OP_SETPROP); + break; + case EXP_MEMBER: + cexp(J, F, lhs->a); + cexp(J, F, rhs); + emitline(J, F, exp); + emitstring(J, F, OP_SETPROP_S, lhs->b->string); + break; + default: + jsC_error(J, lhs, "invalid l-value in assignment"); + } +} + +static void cassignforin(JF, js_Ast *stm) +{ + js_Ast *lhs = stm->a; + + if (stm->type == STM_FOR_IN_VAR) { + if (lhs->b) + jsC_error(J, lhs->b, "more than one loop variable in for-in statement"); + emitline(J, F, lhs->a); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs->a->a); /* list(var-init(ident)) */ + emit(J, F, OP_POP); + return; + } + + switch (lhs->type) { + case EXP_IDENTIFIER: + emitline(J, F, lhs); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs); + emit(J, F, OP_POP); + break; + case EXP_INDEX: + cexp(J, F, lhs->a); + cexp(J, F, lhs->b); + emitline(J, F, lhs); + emit(J, F, OP_ROT3); + emit(J, F, OP_SETPROP); + emit(J, F, OP_POP); + break; + case EXP_MEMBER: + cexp(J, F, lhs->a); + emitline(J, F, lhs); + emit(J, F, OP_ROT2); + emitstring(J, F, OP_SETPROP_S, lhs->b->string); + emit(J, F, OP_POP); + break; + default: + jsC_error(J, lhs, "invalid l-value in for-in loop assignment"); + } +} + +static void cassignop1(JF, js_Ast *lhs) +{ + switch (lhs->type) { + case EXP_IDENTIFIER: + emitline(J, F, lhs); + emitlocal(J, F, OP_GETLOCAL, OP_GETVAR, lhs); + break; + case EXP_INDEX: + cexp(J, F, lhs->a); + cexp(J, F, lhs->b); + emitline(J, F, lhs); + emit(J, F, OP_DUP2); + emit(J, F, OP_GETPROP); + break; + case EXP_MEMBER: + cexp(J, F, lhs->a); + emitline(J, F, lhs); + emit(J, F, OP_DUP); + emitstring(J, F, OP_GETPROP_S, lhs->b->string); + break; + default: + jsC_error(J, lhs, "invalid l-value in assignment"); + } +} + +static void cassignop2(JF, js_Ast *lhs, int postfix) +{ + switch (lhs->type) { + case EXP_IDENTIFIER: + emitline(J, F, lhs); + if (postfix) emit(J, F, OP_ROT2); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs); + break; + case EXP_INDEX: + emitline(J, F, lhs); + if (postfix) emit(J, F, OP_ROT4); + emit(J, F, OP_SETPROP); + break; + case EXP_MEMBER: + emitline(J, F, lhs); + if (postfix) emit(J, F, OP_ROT3); + emitstring(J, F, OP_SETPROP_S, lhs->b->string); + break; + default: + jsC_error(J, lhs, "invalid l-value in assignment"); + } +} + +static void cassignop(JF, js_Ast *exp, int opcode) +{ + js_Ast *lhs = exp->a; + js_Ast *rhs = exp->b; + cassignop1(J, F, lhs); + cexp(J, F, rhs); + emitline(J, F, exp); + emit(J, F, opcode); + cassignop2(J, F, lhs, 0); +} + +static void cdelete(JF, js_Ast *exp) +{ + js_Ast *arg = exp->a; + switch (arg->type) { + case EXP_IDENTIFIER: + if (F->strict) + jsC_error(J, exp, "delete on an unqualified name is not allowed in strict mode"); + emitline(J, F, exp); + emitlocal(J, F, OP_DELLOCAL, OP_DELVAR, arg); + break; + case EXP_INDEX: + cexp(J, F, arg->a); + cexp(J, F, arg->b); + emitline(J, F, exp); + emit(J, F, OP_DELPROP); + break; + case EXP_MEMBER: + cexp(J, F, arg->a); + emitline(J, F, exp); + emitstring(J, F, OP_DELPROP_S, arg->b->string); + break; + default: + jsC_error(J, exp, "invalid l-value in delete expression"); + } +} + +static void ceval(JF, js_Ast *fun, js_Ast *args) +{ + int n = cargs(J, F, args); + F->lightweight = 0; + F->arguments = 1; + if (n == 0) + emit(J, F, OP_UNDEF); + else while (n-- > 1) + emit(J, F, OP_POP); + emit(J, F, OP_EVAL); +} + +static void ccall(JF, js_Ast *fun, js_Ast *args) +{ + int n; + switch (fun->type) { + case EXP_INDEX: + cexp(J, F, fun->a); + emit(J, F, OP_DUP); + cexp(J, F, fun->b); + emit(J, F, OP_GETPROP); + emit(J, F, OP_ROT2); + break; + case EXP_MEMBER: + cexp(J, F, fun->a); + emit(J, F, OP_DUP); + emitstring(J, F, OP_GETPROP_S, fun->b->string); + emit(J, F, OP_ROT2); + break; + case EXP_IDENTIFIER: + if (!strcmp(fun->string, "eval")) { + ceval(J, F, fun, args); + return; + } + /* fallthrough */ + default: + cexp(J, F, fun); + emit(J, F, OP_UNDEF); + break; + } + n = cargs(J, F, args); + emit(J, F, OP_CALL); + emitarg(J, F, n); +} + +static void cexp(JF, js_Ast *exp) +{ + int then, end; + int n; + + switch (exp->type) { + case EXP_STRING: + emitline(J, F, exp); + emitstring(J, F, OP_STRING, exp->string); + break; + case EXP_NUMBER: + emitline(J, F, exp); + emitnumber(J, F, exp->number); + break; + case EXP_ELISION: + break; + case EXP_NULL: + emitline(J, F, exp); + emit(J, F, OP_NULL); + break; + case EXP_TRUE: + emitline(J, F, exp); + emit(J, F, OP_TRUE); + break; + case EXP_FALSE: + emitline(J, F, exp); + emit(J, F, OP_FALSE); + break; + case EXP_THIS: + emitline(J, F, exp); + emit(J, F, OP_THIS); + break; + + case EXP_REGEXP: + emitline(J, F, exp); + emitstring(J, F, OP_NEWREGEXP, exp->string); + emitarg(J, F, exp->number); + break; + + case EXP_OBJECT: + emitline(J, F, exp); + emit(J, F, OP_NEWOBJECT); + cobject(J, F, exp->a); + break; + + case EXP_ARRAY: + emitline(J, F, exp); + emit(J, F, OP_NEWARRAY); + carray(J, F, exp->a); + break; + + case EXP_FUN: + emitline(J, F, exp); + emitfunction(J, F, newfun(J, exp->line, exp->a, exp->b, exp->c, 0, F->strict, 1)); + break; + + case EXP_IDENTIFIER: + emitline(J, F, exp); + emitlocal(J, F, OP_GETLOCAL, OP_GETVAR, exp); + break; + + case EXP_INDEX: + cexp(J, F, exp->a); + cexp(J, F, exp->b); + emitline(J, F, exp); + emit(J, F, OP_GETPROP); + break; + + case EXP_MEMBER: + cexp(J, F, exp->a); + emitline(J, F, exp); + emitstring(J, F, OP_GETPROP_S, exp->b->string); + break; + + case EXP_CALL: + ccall(J, F, exp->a, exp->b); + break; + + case EXP_NEW: + cexp(J, F, exp->a); + n = cargs(J, F, exp->b); + emitline(J, F, exp); + emit(J, F, OP_NEW); + emitarg(J, F, n); + break; + + case EXP_DELETE: + cdelete(J, F, exp); + break; + + case EXP_PREINC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_INC); + cassignop2(J, F, exp->a, 0); + break; + + case EXP_PREDEC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_DEC); + cassignop2(J, F, exp->a, 0); + break; + + case EXP_POSTINC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POSTINC); + cassignop2(J, F, exp->a, 1); + emit(J, F, OP_POP); + break; + + case EXP_POSTDEC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POSTDEC); + cassignop2(J, F, exp->a, 1); + emit(J, F, OP_POP); + break; + + case EXP_VOID: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POP); + emit(J, F, OP_UNDEF); + break; + + case EXP_TYPEOF: ctypeof(J, F, exp); break; + case EXP_POS: cunary(J, F, exp, OP_POS); break; + case EXP_NEG: cunary(J, F, exp, OP_NEG); break; + case EXP_BITNOT: cunary(J, F, exp, OP_BITNOT); break; + case EXP_LOGNOT: cunary(J, F, exp, OP_LOGNOT); break; + + case EXP_BITOR: cbinary(J, F, exp, OP_BITOR); break; + case EXP_BITXOR: cbinary(J, F, exp, OP_BITXOR); break; + case EXP_BITAND: cbinary(J, F, exp, OP_BITAND); break; + case EXP_EQ: cbinary(J, F, exp, OP_EQ); break; + case EXP_NE: cbinary(J, F, exp, OP_NE); break; + case EXP_STRICTEQ: cbinary(J, F, exp, OP_STRICTEQ); break; + case EXP_STRICTNE: cbinary(J, F, exp, OP_STRICTNE); break; + case EXP_LT: cbinary(J, F, exp, OP_LT); break; + case EXP_GT: cbinary(J, F, exp, OP_GT); break; + case EXP_LE: cbinary(J, F, exp, OP_LE); break; + case EXP_GE: cbinary(J, F, exp, OP_GE); break; + case EXP_INSTANCEOF: cbinary(J, F, exp, OP_INSTANCEOF); break; + case EXP_IN: cbinary(J, F, exp, OP_IN); break; + case EXP_SHL: cbinary(J, F, exp, OP_SHL); break; + case EXP_SHR: cbinary(J, F, exp, OP_SHR); break; + case EXP_USHR: cbinary(J, F, exp, OP_USHR); break; + case EXP_ADD: cbinary(J, F, exp, OP_ADD); break; + case EXP_SUB: cbinary(J, F, exp, OP_SUB); break; + case EXP_MUL: cbinary(J, F, exp, OP_MUL); break; + case EXP_DIV: cbinary(J, F, exp, OP_DIV); break; + case EXP_MOD: cbinary(J, F, exp, OP_MOD); break; + + case EXP_ASS: cassign(J, F, exp); break; + case EXP_ASS_MUL: cassignop(J, F, exp, OP_MUL); break; + case EXP_ASS_DIV: cassignop(J, F, exp, OP_DIV); break; + case EXP_ASS_MOD: cassignop(J, F, exp, OP_MOD); break; + case EXP_ASS_ADD: cassignop(J, F, exp, OP_ADD); break; + case EXP_ASS_SUB: cassignop(J, F, exp, OP_SUB); break; + case EXP_ASS_SHL: cassignop(J, F, exp, OP_SHL); break; + case EXP_ASS_SHR: cassignop(J, F, exp, OP_SHR); break; + case EXP_ASS_USHR: cassignop(J, F, exp, OP_USHR); break; + case EXP_ASS_BITAND: cassignop(J, F, exp, OP_BITAND); break; + case EXP_ASS_BITXOR: cassignop(J, F, exp, OP_BITXOR); break; + case EXP_ASS_BITOR: cassignop(J, F, exp, OP_BITOR); break; + + case EXP_COMMA: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POP); + cexp(J, F, exp->b); + break; + + case EXP_LOGOR: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_DUP); + end = emitjump(J, F, OP_JTRUE); + emit(J, F, OP_POP); + cexp(J, F, exp->b); + label(J, F, end); + break; + + case EXP_LOGAND: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_DUP); + end = emitjump(J, F, OP_JFALSE); + emit(J, F, OP_POP); + cexp(J, F, exp->b); + label(J, F, end); + break; + + case EXP_COND: + cexp(J, F, exp->a); + emitline(J, F, exp); + then = emitjump(J, F, OP_JTRUE); + cexp(J, F, exp->c); + end = emitjump(J, F, OP_JUMP); + label(J, F, then); + cexp(J, F, exp->b); + label(J, F, end); + break; + + default: + jsC_error(J, exp, "unknown expression type"); + } +} + +/* Patch break and continue statements */ + +static void addjump(JF, enum js_AstType type, js_Ast *target, int inst) +{ + js_JumpList *jump = js_malloc(J, sizeof *jump); + jump->type = type; + jump->inst = inst; + jump->next = target->jumps; + target->jumps = jump; +} + +static void labeljumps(JF, js_Ast *stm, int baddr, int caddr) +{ + js_JumpList *jump = stm->jumps; + while (jump) { + js_JumpList *next = jump->next; + if (jump->type == STM_BREAK) + labelto(J, F, jump->inst, baddr); + if (jump->type == STM_CONTINUE) + labelto(J, F, jump->inst, caddr); + js_free(J, jump); + jump = next; + } + stm->jumps = NULL; +} + +static int isloop(enum js_AstType T) +{ + return T == STM_DO || T == STM_WHILE || + T == STM_FOR || T == STM_FOR_VAR || + T == STM_FOR_IN || T == STM_FOR_IN_VAR; +} + +static int isfun(enum js_AstType T) +{ + return T == AST_FUNDEC || T == EXP_FUN || T == EXP_PROP_GET || T == EXP_PROP_SET; +} + +static int matchlabel(js_Ast *node, const char *label) +{ + while (node && node->type == STM_LABEL) { + if (!strcmp(node->a->string, label)) + return 1; + node = node->parent; + } + return 0; +} + +static js_Ast *breaktarget(JF, js_Ast *node, const char *label) +{ + while (node) { + if (isfun(node->type)) + break; + if (!label) { + if (isloop(node->type) || node->type == STM_SWITCH) + return node; + } else { + if (matchlabel(node->parent, label)) + return node; + } + node = node->parent; + } + return NULL; +} + +static js_Ast *continuetarget(JF, js_Ast *node, const char *label) +{ + while (node) { + if (isfun(node->type)) + break; + if (isloop(node->type)) { + if (!label) + return node; + else if (matchlabel(node->parent, label)) + return node; + } + node = node->parent; + } + return NULL; +} + +static js_Ast *returntarget(JF, js_Ast *node) +{ + while (node) { + if (isfun(node->type)) + return node; + node = node->parent; + } + return NULL; +} + +/* Emit code to rebalance stack and scopes during an abrupt exit */ + +static void cexit(JF, enum js_AstType T, js_Ast *node, js_Ast *target) +{ + js_Ast *prev; + do { + prev = node, node = node->parent; + switch (node->type) { + default: + /* impossible */ + break; + case STM_WITH: + emitline(J, F, node); + emit(J, F, OP_ENDWITH); + break; + case STM_FOR_IN: + case STM_FOR_IN_VAR: + emitline(J, F, node); + /* pop the iterator if leaving the loop */ + if (F->script) { + if (T == STM_RETURN || T == STM_BREAK || (T == STM_CONTINUE && target != node)) { + /* pop the iterator, save the return or exp value */ + emit(J, F, OP_ROT2); + emit(J, F, OP_POP); + } + if (T == STM_CONTINUE) + emit(J, F, OP_ROT2); /* put the iterator back on top */ + } else { + if (T == STM_RETURN) { + /* pop the iterator, save the return value */ + emit(J, F, OP_ROT2); + emit(J, F, OP_POP); + } + if (T == STM_BREAK || (T == STM_CONTINUE && target != node)) + emit(J, F, OP_POP); /* pop the iterator */ + } + break; + case STM_TRY: + emitline(J, F, node); + /* came from try block */ + if (prev == node->a) { + emit(J, F, OP_ENDTRY); + if (node->d) cstm(J, F, node->d); /* finally */ + } + /* came from catch block */ + if (prev == node->c) { + /* ... with finally */ + if (node->d) { + emit(J, F, OP_ENDCATCH); + emit(J, F, OP_ENDTRY); + cstm(J, F, node->d); /* finally */ + } else { + emit(J, F, OP_ENDCATCH); + } + } + break; + } + } while (node != target); +} + +/* Try/catch/finally */ + +static void ctryfinally(JF, js_Ast *trystm, js_Ast *finallystm) +{ + int L1; + L1 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the try block */ + cstm(J, F, finallystm); /* inline finally block */ + emit(J, F, OP_THROW); /* rethrow exception */ + } + label(J, F, L1); + cstm(J, F, trystm); + emit(J, F, OP_ENDTRY); + cstm(J, F, finallystm); +} + +static void ctrycatch(JF, js_Ast *trystm, js_Ast *catchvar, js_Ast *catchstm) +{ + int L1, L2; + L1 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the try block */ + checkfutureword(J, F, catchvar); + if (F->strict) { + if (!strcmp(catchvar->string, "arguments")) + jsC_error(J, catchvar, "redefining 'arguments' is not allowed in strict mode"); + if (!strcmp(catchvar->string, "eval")) + jsC_error(J, catchvar, "redefining 'eval' is not allowed in strict mode"); + } + emitline(J, F, catchvar); + emitstring(J, F, OP_CATCH, catchvar->string); + cstm(J, F, catchstm); + emit(J, F, OP_ENDCATCH); + L2 = emitjump(J, F, OP_JUMP); /* skip past the try block */ + } + label(J, F, L1); + cstm(J, F, trystm); + emit(J, F, OP_ENDTRY); + label(J, F, L2); +} + +static void ctrycatchfinally(JF, js_Ast *trystm, js_Ast *catchvar, js_Ast *catchstm, js_Ast *finallystm) +{ + int L1, L2, L3; + L1 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the try block */ + L2 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the catch block */ + cstm(J, F, finallystm); /* inline finally block */ + emit(J, F, OP_THROW); /* rethrow exception */ + } + label(J, F, L2); + if (F->strict) { + checkfutureword(J, F, catchvar); + if (!strcmp(catchvar->string, "arguments")) + jsC_error(J, catchvar, "redefining 'arguments' is not allowed in strict mode"); + if (!strcmp(catchvar->string, "eval")) + jsC_error(J, catchvar, "redefining 'eval' is not allowed in strict mode"); + } + emitline(J, F, catchvar); + emitstring(J, F, OP_CATCH, catchvar->string); + cstm(J, F, catchstm); + emit(J, F, OP_ENDCATCH); + emit(J, F, OP_ENDTRY); + L3 = emitjump(J, F, OP_JUMP); /* skip past the try block to the finally block */ + } + label(J, F, L1); + cstm(J, F, trystm); + emit(J, F, OP_ENDTRY); + label(J, F, L3); + cstm(J, F, finallystm); +} + +/* Switch */ + +static void cswitch(JF, js_Ast *ref, js_Ast *head) +{ + js_Ast *node, *clause, *def = NULL; + int end; + + cexp(J, F, ref); + + /* emit an if-else chain of tests for the case clause expressions */ + for (node = head; node; node = node->b) { + clause = node->a; + if (clause->type == STM_DEFAULT) { + if (def) + jsC_error(J, clause, "more than one default label in switch"); + def = clause; + } else { + cexp(J, F, clause->a); + emitline(J, F, clause); + clause->casejump = emitjump(J, F, OP_JCASE); + } + } + emit(J, F, OP_POP); + if (def) { + emitline(J, F, def); + def->casejump = emitjump(J, F, OP_JUMP); + end = 0; + } else { + end = emitjump(J, F, OP_JUMP); + } + + /* emit the case clause bodies */ + for (node = head; node; node = node->b) { + clause = node->a; + label(J, F, clause->casejump); + if (clause->type == STM_DEFAULT) + cstmlist(J, F, clause->a); + else + cstmlist(J, F, clause->b); + } + + if (end) + label(J, F, end); +} + +/* Statements */ + +static void cvarinit(JF, js_Ast *list) +{ + while (list) { + js_Ast *var = list->a; + if (var->b) { + cexp(J, F, var->b); + emitline(J, F, var); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, var->a); + emit(J, F, OP_POP); + } + list = list->b; + } +} + +static void cstm(JF, js_Ast *stm) +{ + js_Ast *target; + int loop, cont, then, end; + + emitline(J, F, stm); + + switch (stm->type) { + case AST_FUNDEC: + break; + + case STM_BLOCK: + cstmlist(J, F, stm->a); + break; + + case STM_EMPTY: + if (F->script) { + emitline(J, F, stm); + emit(J, F, OP_POP); + emit(J, F, OP_UNDEF); + } + break; + + case STM_VAR: + cvarinit(J, F, stm->a); + break; + + case STM_IF: + if (stm->c) { + cexp(J, F, stm->a); + emitline(J, F, stm); + then = emitjump(J, F, OP_JTRUE); + cstm(J, F, stm->c); + emitline(J, F, stm); + end = emitjump(J, F, OP_JUMP); + label(J, F, then); + cstm(J, F, stm->b); + label(J, F, end); + } else { + cexp(J, F, stm->a); + emitline(J, F, stm); + end = emitjump(J, F, OP_JFALSE); + cstm(J, F, stm->b); + label(J, F, end); + } + break; + + case STM_DO: + loop = here(J, F); + cstm(J, F, stm->a); + cont = here(J, F); + cexp(J, F, stm->b); + emitline(J, F, stm); + emitjumpto(J, F, OP_JTRUE, loop); + labeljumps(J, F, stm, here(J,F), cont); + break; + + case STM_WHILE: + loop = here(J, F); + cexp(J, F, stm->a); + emitline(J, F, stm); + end = emitjump(J, F, OP_JFALSE); + cstm(J, F, stm->b); + emitline(J, F, stm); + emitjumpto(J, F, OP_JUMP, loop); + label(J, F, end); + labeljumps(J, F, stm, here(J,F), loop); + break; + + case STM_FOR: + case STM_FOR_VAR: + if (stm->type == STM_FOR_VAR) { + cvarinit(J, F, stm->a); + } else { + if (stm->a) { + cexp(J, F, stm->a); + emit(J, F, OP_POP); + } + } + loop = here(J, F); + if (stm->b) { + cexp(J, F, stm->b); + emitline(J, F, stm); + end = emitjump(J, F, OP_JFALSE); + } else { + end = 0; + } + cstm(J, F, stm->d); + cont = here(J, F); + if (stm->c) { + cexp(J, F, stm->c); + emit(J, F, OP_POP); + } + emitline(J, F, stm); + emitjumpto(J, F, OP_JUMP, loop); + if (end) + label(J, F, end); + labeljumps(J, F, stm, here(J,F), cont); + break; + + case STM_FOR_IN: + case STM_FOR_IN_VAR: + cexp(J, F, stm->b); + emitline(J, F, stm); + emit(J, F, OP_ITERATOR); + loop = here(J, F); + { + emitline(J, F, stm); + emit(J, F, OP_NEXTITER); + end = emitjump(J, F, OP_JFALSE); + cassignforin(J, F, stm); + if (F->script) { + emit(J, F, OP_ROT2); + cstm(J, F, stm->c); + emit(J, F, OP_ROT2); + } else { + cstm(J, F, stm->c); + } + emitline(J, F, stm); + emitjumpto(J, F, OP_JUMP, loop); + } + label(J, F, end); + labeljumps(J, F, stm, here(J,F), loop); + break; + + case STM_SWITCH: + cswitch(J, F, stm->a, stm->b); + labeljumps(J, F, stm, here(J,F), 0); + break; + + case STM_LABEL: + cstm(J, F, stm->b); + /* skip consecutive labels */ + while (stm->type == STM_LABEL) + stm = stm->b; + /* loops and switches have already been labelled */ + if (!isloop(stm->type) && stm->type != STM_SWITCH) + labeljumps(J, F, stm, here(J,F), 0); + break; + + case STM_BREAK: + if (stm->a) { + checkfutureword(J, F, stm->a); + target = breaktarget(J, F, stm->parent, stm->a->string); + if (!target) + jsC_error(J, stm, "break label '%s' not found", stm->a->string); + } else { + target = breaktarget(J, F, stm->parent, NULL); + if (!target) + jsC_error(J, stm, "unlabelled break must be inside loop or switch"); + } + cexit(J, F, STM_BREAK, stm, target); + emitline(J, F, stm); + addjump(J, F, STM_BREAK, target, emitjump(J, F, OP_JUMP)); + break; + + case STM_CONTINUE: + if (stm->a) { + checkfutureword(J, F, stm->a); + target = continuetarget(J, F, stm->parent, stm->a->string); + if (!target) + jsC_error(J, stm, "continue label '%s' not found", stm->a->string); + } else { + target = continuetarget(J, F, stm->parent, NULL); + if (!target) + jsC_error(J, stm, "continue must be inside loop"); + } + cexit(J, F, STM_CONTINUE, stm, target); + emitline(J, F, stm); + addjump(J, F, STM_CONTINUE, target, emitjump(J, F, OP_JUMP)); + break; + + case STM_RETURN: + if (stm->a) + cexp(J, F, stm->a); + else + emit(J, F, OP_UNDEF); + target = returntarget(J, F, stm->parent); + if (!target) + jsC_error(J, stm, "return not in function"); + cexit(J, F, STM_RETURN, stm, target); + emitline(J, F, stm); + emit(J, F, OP_RETURN); + break; + + case STM_THROW: + cexp(J, F, stm->a); + emitline(J, F, stm); + emit(J, F, OP_THROW); + break; + + case STM_WITH: + F->lightweight = 0; + if (F->strict) + jsC_error(J, stm->a, "'with' statements are not allowed in strict mode"); + cexp(J, F, stm->a); + emitline(J, F, stm); + emit(J, F, OP_WITH); + cstm(J, F, stm->b); + emitline(J, F, stm); + emit(J, F, OP_ENDWITH); + break; + + case STM_TRY: + emitline(J, F, stm); + if (stm->b && stm->c) { + F->lightweight = 0; + if (stm->d) + ctrycatchfinally(J, F, stm->a, stm->b, stm->c, stm->d); + else + ctrycatch(J, F, stm->a, stm->b, stm->c); + } else { + ctryfinally(J, F, stm->a, stm->d); + } + break; + + case STM_DEBUGGER: + emitline(J, F, stm); + emit(J, F, OP_DEBUGGER); + break; + + default: + if (F->script) { + emitline(J, F, stm); + emit(J, F, OP_POP); + cexp(J, F, stm); + } else { + cexp(J, F, stm); + emitline(J, F, stm); + emit(J, F, OP_POP); + } + break; + } +} + +static void cstmlist(JF, js_Ast *list) +{ + while (list) { + cstm(J, F, list->a); + list = list->b; + } +} + +/* Declarations and programs */ + +static int listlength(js_Ast *list) +{ + int n = 0; + while (list) ++n, list = list->b; + return n; +} + +static void cparams(JF, js_Ast *list, js_Ast *fname) +{ + F->numparams = listlength(list); + while (list) { + checkfutureword(J, F, list->a); + addlocal(J, F, list->a, 0); + list = list->b; + } +} + +static void cvardecs(JF, js_Ast *node) +{ + if (node->type == AST_LIST) { + while (node) { + cvardecs(J, F, node->a); + node = node->b; + } + return; + } + + if (isfun(node->type)) + return; /* stop at inner functions */ + + if (node->type == EXP_VAR) { + checkfutureword(J, F, node->a); + addlocal(J, F, node->a, 1); + } + + if (node->a) cvardecs(J, F, node->a); + if (node->b) cvardecs(J, F, node->b); + if (node->c) cvardecs(J, F, node->c); + if (node->d) cvardecs(J, F, node->d); +} + +static void cfundecs(JF, js_Ast *list) +{ + while (list) { + js_Ast *stm = list->a; + if (stm->type == AST_FUNDEC) { + emitline(J, F, stm); + emitfunction(J, F, newfun(J, stm->line, stm->a, stm->b, stm->c, 0, F->strict, 0)); + emitline(J, F, stm); + emit(J, F, OP_SETLOCAL); + emitarg(J, F, addlocal(J, F, stm->a, 1)); + emit(J, F, OP_POP); + } + list = list->b; + } +} + +static void cfunbody(JF, js_Ast *name, js_Ast *params, js_Ast *body, int is_fun_exp) +{ + F->lightweight = 1; + F->arguments = 0; + + if (F->script) + F->lightweight = 0; + + /* Check if first statement is 'use strict': */ + if (body && body->type == AST_LIST && body->a && body->a->type == EXP_STRING) + if (!strcmp(body->a->string, "use strict")) + F->strict = 1; + + F->lastline = F->line; + + cparams(J, F, params, name); + + if (body) { + cvardecs(J, F, body); + cfundecs(J, F, body); + } + + if (name) { + checkfutureword(J, F, name); + if (is_fun_exp) { + if (findlocal(J, F, name->string) < 0) { + /* TODO: make this binding immutable! */ + emit(J, F, OP_CURRENT); + emit(J, F, OP_SETLOCAL); + emitarg(J, F, addlocal(J, F, name, 1)); + emit(J, F, OP_POP); + } + } + } + + if (F->script) { + emit(J, F, OP_UNDEF); + cstmlist(J, F, body); + emit(J, F, OP_RETURN); + } else { + cstmlist(J, F, body); + emit(J, F, OP_UNDEF); + emit(J, F, OP_RETURN); + } +} + +js_Function *jsC_compilefunction(js_State *J, js_Ast *prog) +{ + return newfun(J, prog->line, prog->a, prog->b, prog->c, 0, J->default_strict, 1); +} + +js_Function *jsC_compilescript(js_State *J, js_Ast *prog, int default_strict) +{ + return newfun(J, prog ? prog->line : 0, NULL, NULL, prog, 1, default_strict, 0); +} diff --git a/mujs/jsdate.c b/mujs/jsdate.c new file mode 100644 index 0000000..bf1d406 --- /dev/null +++ b/mujs/jsdate.c @@ -0,0 +1,861 @@ +#include "jsi.h" + +#include + +#if defined(__unix__) || defined(__APPLE__) +#include +#elif defined(_WIN32) +#include +#endif + +#define js_optnumber(J,I,V) (js_isdefined(J,I) ? js_tonumber(J,I) : V) + +static double Now(void) +{ +#if defined(__unix__) || defined(__APPLE__) + struct timeval tv; + gettimeofday(&tv, NULL); + return floor(tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0); +#elif defined(_WIN32) + struct _timeb tv; + _ftime(&tv); + return tv.time * 1000.0 + tv.millitm; +#else + return time(NULL) * 1000.0; +#endif +} + +static double LocalTZA(void) +{ + static int once = 1; + static double tza = 0; + if (once) { + time_t now = time(NULL); + time_t utc = mktime(gmtime(&now)); + time_t loc = mktime(localtime(&now)); + tza = (loc - utc) * 1000; + once = 0; + } + return tza; +} + +static double DaylightSavingTA(double t) +{ + return 0; /* TODO */ +} + +/* Helpers from the ECMA 262 specification */ + +#define HoursPerDay 24.0 +#define MinutesPerDay (HoursPerDay * MinutesPerHour) +#define MinutesPerHour 60.0 +#define SecondsPerDay (MinutesPerDay * SecondsPerMinute) +#define SecondsPerHour (MinutesPerHour * SecondsPerMinute) +#define SecondsPerMinute 60.0 + +#define msPerDay (SecondsPerDay * msPerSecond) +#define msPerHour (SecondsPerHour * msPerSecond) +#define msPerMinute (SecondsPerMinute * msPerSecond) +#define msPerSecond 1000.0 + +static double pmod(double x, double y) +{ + x = fmod(x, y); + if (x < 0) + x += y; + return x; +} + +static int Day(double t) +{ + return floor(t / msPerDay); +} + +static double TimeWithinDay(double t) +{ + return pmod(t, msPerDay); +} + +static int DaysInYear(int y) +{ + return y % 4 == 0 && (y % 100 || (y % 400 == 0)) ? 366 : 365; +} + +static int DayFromYear(int y) +{ + return 365 * (y - 1970) + + floor((y - 1969) / 4.0) - + floor((y - 1901) / 100.0) + + floor((y - 1601) / 400.0); +} + +static double TimeFromYear(int y) +{ + return DayFromYear(y) * msPerDay; +} + +static int YearFromTime(double t) +{ + int y = floor(t / (msPerDay * 365.2425)) + 1970; + double t2 = TimeFromYear(y); + if (t2 > t) + --y; + else if (t2 + msPerDay * DaysInYear(y) <= t) + ++y; + return y; +} + +static int InLeapYear(double t) +{ + return DaysInYear(YearFromTime(t)) == 366; +} + +static int DayWithinYear(double t) +{ + return Day(t) - DayFromYear(YearFromTime(t)); +} + +static int MonthFromTime(double t) +{ + int day = DayWithinYear(t); + int leap = InLeapYear(t); + if (day < 31) return 0; + if (day < 59 + leap) return 1; + if (day < 90 + leap) return 2; + if (day < 120 + leap) return 3; + if (day < 151 + leap) return 4; + if (day < 181 + leap) return 5; + if (day < 212 + leap) return 6; + if (day < 243 + leap) return 7; + if (day < 273 + leap) return 8; + if (day < 304 + leap) return 9; + if (day < 334 + leap) return 10; + return 11; +} + +static int DateFromTime(double t) +{ + int day = DayWithinYear(t); + int leap = InLeapYear(t); + switch (MonthFromTime(t)) { + case 0: return day + 1; + case 1: return day - 30; + case 2: return day - 58 - leap; + case 3: return day - 89 - leap; + case 4: return day - 119 - leap; + case 5: return day - 150 - leap; + case 6: return day - 180 - leap; + case 7: return day - 211 - leap; + case 8: return day - 242 - leap; + case 9: return day - 272 - leap; + case 10: return day - 303 - leap; + default : return day - 333 - leap; + } +} + +static int WeekDay(double t) +{ + return pmod(Day(t) + 4, 7); +} + +static double LocalTime(double utc) +{ + return utc + LocalTZA() + DaylightSavingTA(utc); +} + +static double UTC(double loc) +{ + return loc - LocalTZA() - DaylightSavingTA(loc - LocalTZA()); +} + +static int HourFromTime(double t) +{ + return pmod(floor(t / msPerHour), HoursPerDay); +} + +static int MinFromTime(double t) +{ + return pmod(floor(t / msPerMinute), MinutesPerHour); +} + +static int SecFromTime(double t) +{ + return pmod(floor(t / msPerSecond), SecondsPerMinute); +} + +static int msFromTime(double t) +{ + return pmod(t, msPerSecond); +} + +static double MakeTime(double hour, double min, double sec, double ms) +{ + return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) * msPerSecond + ms; +} + +static double MakeDay(double y, double m, double date) +{ + /* + * The following array contains the day of year for the first day of + * each month, where index 0 is January, and day 0 is January 1. + */ + static const double firstDayOfMonth[2][12] = { + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} + }; + + double yd, md; + int im; + + y += floor(m / 12); + m = pmod(m, 12); + + im = (int)m; + if (im < 0 || im >= 12) + return NAN; + + yd = floor(TimeFromYear(y) / msPerDay); + md = firstDayOfMonth[DaysInYear(y) == 366][im]; + + return yd + md + date - 1; +} + +static double MakeDate(double day, double time) +{ + return day * msPerDay + time; +} + +static double TimeClip(double t) +{ + if (!isfinite(t)) + return NAN; + if (fabs(t) > 8.64e15) + return NAN; + return t < 0 ? -floor(-t) : floor(t); +} + +static int toint(const char **sp, int w, int *v) +{ + const char *s = *sp; + *v = 0; + while (w--) { + if (*s < '0' || *s > '9') + return 0; + *v = *v * 10 + (*s++ - '0'); + } + *sp = s; + return 1; +} + +static double parseDateTime(const char *s) +{ + int y = 1970, m = 1, d = 1, H = 0, M = 0, S = 0, ms = 0; + int tza = 0; + double t; + + /* Parse ISO 8601 formatted date and time: */ + /* YYYY("-"MM("-"DD)?)?("T"HH":"mm(":"ss("."sss)?)?("Z"|[+-]HH(":"mm)?)?)? */ + + if (!toint(&s, 4, &y)) return NAN; + if (*s == '-') { + s += 1; + if (!toint(&s, 2, &m)) return NAN; + if (*s == '-') { + s += 1; + if (!toint(&s, 2, &d)) return NAN; + } + } + + if (*s == 'T') { + s += 1; + if (!toint(&s, 2, &H)) return NAN; + if (*s != ':') return NAN; + s += 1; + if (!toint(&s, 2, &M)) return NAN; + if (*s == ':') { + s += 1; + if (!toint(&s, 2, &S)) return NAN; + if (*s == '.') { + s += 1; + if (!toint(&s, 3, &ms)) return NAN; + } + } + if (*s == 'Z') { + s += 1; + tza = 0; + } else if (*s == '+' || *s == '-') { + int tzh = 0, tzm = 0; + int tzs = *s == '+' ? 1 : -1; + s += 1; + if (!toint(&s, 2, &tzh)) return NAN; + if (*s == ':') { + s += 1; + if (!toint(&s, 2, &tzm)) return NAN; + } + if (tzh > 23 || tzm > 59) return NAN; + tza = tzs * (tzh * msPerHour + tzm * msPerMinute); + } else { + tza = LocalTZA(); + } + } + + if (*s) return NAN; + + if (m < 1 || m > 12) return NAN; + if (d < 1 || d > 31) return NAN; + if (H < 0 || H > 24) return NAN; + if (M < 0 || M > 59) return NAN; + if (S < 0 || S > 59) return NAN; + if (ms < 0 || ms > 999) return NAN; + if (H == 24 && (M != 0 || S != 0 || ms != 0)) return NAN; + + /* TODO: DaylightSavingTA on local times */ + t = MakeDate(MakeDay(y, m-1, d), MakeTime(H, M, S, ms)); + return t - tza; +} + +/* date formatting */ + +static char *fmtdate(char *buf, double t) +{ + int y = YearFromTime(t); + int m = MonthFromTime(t); + int d = DateFromTime(t); + if (!isfinite(t)) + return "Invalid Date"; + sprintf(buf, "%04d-%02d-%02d", y, m+1, d); + return buf; +} + +static char *fmttime(char *buf, double t, double tza) +{ + int H = HourFromTime(t); + int M = MinFromTime(t); + int S = SecFromTime(t); + int ms = msFromTime(t); + int tzh = HourFromTime(fabs(tza)); + int tzm = MinFromTime(fabs(tza)); + if (!isfinite(t)) + return "Invalid Date"; + if (tza == 0) + sprintf(buf, "%02d:%02d:%02d.%03dZ", H, M, S, ms); + else if (tza < 0) + sprintf(buf, "%02d:%02d:%02d.%03d-%02d:%02d", H, M, S, ms, tzh, tzm); + else + sprintf(buf, "%02d:%02d:%02d.%03d+%02d:%02d", H, M, S, ms, tzh, tzm); + return buf; +} + +static char *fmtdatetime(char *buf, double t, double tza) +{ + char dbuf[20], tbuf[20]; + if (!isfinite(t)) + return "Invalid Date"; + fmtdate(dbuf, t); + fmttime(tbuf, t, tza); + sprintf(buf, "%sT%s", dbuf, tbuf); + return buf; +} + +/* Date functions */ + +static double js_todate(js_State *J, int idx) +{ + js_Object *self = js_toobject(J, idx); + if (self->type != JS_CDATE) + js_typeerror(J, "not a date"); + return self->u.number; +} + +static void js_setdate(js_State *J, int idx, double t) +{ + js_Object *self = js_toobject(J, idx); + if (self->type != JS_CDATE) + js_typeerror(J, "not a date"); + self->u.number = TimeClip(t); + js_pushnumber(J, self->u.number); +} + +static void D_parse(js_State *J) +{ + double t = parseDateTime(js_tostring(J, 1)); + js_pushnumber(J, t); +} + +static void D_UTC(js_State *J) +{ + double y, m, d, H, M, S, ms, t; + y = js_tonumber(J, 1); + if (y < 100) y += 1900; + m = js_tonumber(J, 2); + d = js_optnumber(J, 3, 1); + H = js_optnumber(J, 4, 0); + M = js_optnumber(J, 5, 0); + S = js_optnumber(J, 6, 0); + ms = js_optnumber(J, 7, 0); + t = MakeDate(MakeDay(y, m, d), MakeTime(H, M, S, ms)); + t = TimeClip(t); + js_pushnumber(J, t); +} + +static void D_now(js_State *J) +{ + js_pushnumber(J, Now()); +} + +static void jsB_Date(js_State *J) +{ + char buf[64]; + js_pushstring(J, fmtdatetime(buf, LocalTime(Now()), LocalTZA())); +} + +static void jsB_new_Date(js_State *J) +{ + int top = js_gettop(J); + js_Object *obj; + double t; + + if (top == 1) + t = Now(); + else if (top == 2) { + js_toprimitive(J, 1, JS_HNONE); + if (js_isstring(J, 1)) + t = parseDateTime(js_tostring(J, 1)); + else + t = TimeClip(js_tonumber(J, 1)); + } else { + double y, m, d, H, M, S, ms; + y = js_tonumber(J, 1); + if (y < 100) y += 1900; + m = js_tonumber(J, 2); + d = js_optnumber(J, 3, 1); + H = js_optnumber(J, 4, 0); + M = js_optnumber(J, 5, 0); + S = js_optnumber(J, 6, 0); + ms = js_optnumber(J, 7, 0); + t = MakeDate(MakeDay(y, m, d), MakeTime(H, M, S, ms)); + t = TimeClip(UTC(t)); + } + + obj = jsV_newobject(J, JS_CDATE, J->Date_prototype); + obj->u.number = t; + + js_pushobject(J, obj); +} + +static void Dp_valueOf(js_State *J) +{ + double t = js_todate(J, 0); + js_pushnumber(J, t); +} + +static void Dp_toString(js_State *J) +{ + char buf[64]; + double t = js_todate(J, 0); + js_pushstring(J, fmtdatetime(buf, LocalTime(t), LocalTZA())); +} + +static void Dp_toDateString(js_State *J) +{ + char buf[64]; + double t = js_todate(J, 0); + js_pushstring(J, fmtdate(buf, LocalTime(t))); +} + +static void Dp_toTimeString(js_State *J) +{ + char buf[64]; + double t = js_todate(J, 0); + js_pushstring(J, fmttime(buf, LocalTime(t), LocalTZA())); +} + +static void Dp_toUTCString(js_State *J) +{ + char buf[64]; + double t = js_todate(J, 0); + js_pushstring(J, fmtdatetime(buf, t, 0)); +} + +static void Dp_toISOString(js_State *J) +{ + char buf[64]; + double t = js_todate(J, 0); + if (!isfinite(t)) + js_rangeerror(J, "invalid date"); + js_pushstring(J, fmtdatetime(buf, t, 0)); +} + +static void Dp_getFullYear(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, YearFromTime(LocalTime(t))); +} + +static void Dp_getMonth(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, MonthFromTime(LocalTime(t))); +} + +static void Dp_getDate(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, DateFromTime(LocalTime(t))); +} + +static void Dp_getDay(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, WeekDay(LocalTime(t))); +} + +static void Dp_getHours(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, HourFromTime(LocalTime(t))); +} + +static void Dp_getMinutes(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, MinFromTime(LocalTime(t))); +} + +static void Dp_getSeconds(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, SecFromTime(LocalTime(t))); +} + +static void Dp_getMilliseconds(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, msFromTime(LocalTime(t))); +} + +static void Dp_getUTCFullYear(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, YearFromTime(t)); +} + +static void Dp_getUTCMonth(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, MonthFromTime(t)); +} + +static void Dp_getUTCDate(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, DateFromTime(t)); +} + +static void Dp_getUTCDay(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, WeekDay(t)); +} + +static void Dp_getUTCHours(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, HourFromTime(t)); +} + +static void Dp_getUTCMinutes(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, MinFromTime(t)); +} + +static void Dp_getUTCSeconds(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, SecFromTime(t)); +} + +static void Dp_getUTCMilliseconds(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, msFromTime(t)); +} + +static void Dp_getTimezoneOffset(js_State *J) +{ + double t = js_todate(J, 0); + if (isnan(t)) + js_pushnumber(J, NAN); + else + js_pushnumber(J, (t - LocalTime(t)) / msPerMinute); +} + +static void Dp_setTime(js_State *J) +{ + js_setdate(J, 0, js_tonumber(J, 1)); +} + +static void Dp_setMilliseconds(js_State *J) +{ + double t = LocalTime(js_todate(J, 0)); + double h = HourFromTime(t); + double m = MinFromTime(t); + double s = SecFromTime(t); + double ms = js_tonumber(J, 1); + js_setdate(J, 0, UTC(MakeDate(Day(t), MakeTime(h, m, s, ms)))); +} + +static void Dp_setSeconds(js_State *J) +{ + double t = LocalTime(js_todate(J, 0)); + double h = HourFromTime(t); + double m = MinFromTime(t); + double s = js_tonumber(J, 1); + double ms = js_optnumber(J, 2, msFromTime(t)); + js_setdate(J, 0, UTC(MakeDate(Day(t), MakeTime(h, m, s, ms)))); +} + +static void Dp_setMinutes(js_State *J) +{ + double t = LocalTime(js_todate(J, 0)); + double h = HourFromTime(t); + double m = js_tonumber(J, 1); + double s = js_optnumber(J, 2, SecFromTime(t)); + double ms = js_optnumber(J, 3, msFromTime(t)); + js_setdate(J, 0, UTC(MakeDate(Day(t), MakeTime(h, m, s, ms)))); +} + +static void Dp_setHours(js_State *J) +{ + double t = LocalTime(js_todate(J, 0)); + double h = js_tonumber(J, 1); + double m = js_optnumber(J, 2, MinFromTime(t)); + double s = js_optnumber(J, 3, SecFromTime(t)); + double ms = js_optnumber(J, 4, msFromTime(t)); + js_setdate(J, 0, UTC(MakeDate(Day(t), MakeTime(h, m, s, ms)))); +} + +static void Dp_setDate(js_State *J) +{ + double t = LocalTime(js_todate(J, 0)); + double y = YearFromTime(t); + double m = MonthFromTime(t); + double d = js_tonumber(J, 1); + js_setdate(J, 0, UTC(MakeDate(MakeDay(y, m, d), TimeWithinDay(t)))); +} + +static void Dp_setMonth(js_State *J) +{ + double t = LocalTime(js_todate(J, 0)); + double y = YearFromTime(t); + double m = js_tonumber(J, 1); + double d = js_optnumber(J, 2, DateFromTime(t)); + js_setdate(J, 0, UTC(MakeDate(MakeDay(y, m, d), TimeWithinDay(t)))); +} + +static void Dp_setFullYear(js_State *J) +{ + double t = LocalTime(js_todate(J, 0)); + double y = js_tonumber(J, 1); + double m = js_optnumber(J, 2, MonthFromTime(t)); + double d = js_optnumber(J, 3, DateFromTime(t)); + js_setdate(J, 0, UTC(MakeDate(MakeDay(y, m, d), TimeWithinDay(t)))); +} + +static void Dp_setUTCMilliseconds(js_State *J) +{ + double t = js_todate(J, 0); + double h = HourFromTime(t); + double m = MinFromTime(t); + double s = SecFromTime(t); + double ms = js_tonumber(J, 1); + js_setdate(J, 0, MakeDate(Day(t), MakeTime(h, m, s, ms))); +} + +static void Dp_setUTCSeconds(js_State *J) +{ + double t = js_todate(J, 0); + double h = HourFromTime(t); + double m = MinFromTime(t); + double s = js_tonumber(J, 1); + double ms = js_optnumber(J, 2, msFromTime(t)); + js_setdate(J, 0, MakeDate(Day(t), MakeTime(h, m, s, ms))); +} + +static void Dp_setUTCMinutes(js_State *J) +{ + double t = js_todate(J, 0); + double h = HourFromTime(t); + double m = js_tonumber(J, 1); + double s = js_optnumber(J, 2, SecFromTime(t)); + double ms = js_optnumber(J, 3, msFromTime(t)); + js_setdate(J, 0, MakeDate(Day(t), MakeTime(h, m, s, ms))); +} + +static void Dp_setUTCHours(js_State *J) +{ + double t = js_todate(J, 0); + double h = js_tonumber(J, 1); + double m = js_optnumber(J, 2, HourFromTime(t)); + double s = js_optnumber(J, 3, SecFromTime(t)); + double ms = js_optnumber(J, 4, msFromTime(t)); + js_setdate(J, 0, MakeDate(Day(t), MakeTime(h, m, s, ms))); +} + +static void Dp_setUTCDate(js_State *J) +{ + double t = js_todate(J, 0); + double y = YearFromTime(t); + double m = MonthFromTime(t); + double d = js_tonumber(J, 1); + js_setdate(J, 0, MakeDate(MakeDay(y, m, d), TimeWithinDay(t))); +} + +static void Dp_setUTCMonth(js_State *J) +{ + double t = js_todate(J, 0); + double y = YearFromTime(t); + double m = js_tonumber(J, 1); + double d = js_optnumber(J, 2, DateFromTime(t)); + js_setdate(J, 0, MakeDate(MakeDay(y, m, d), TimeWithinDay(t))); +} + +static void Dp_setUTCFullYear(js_State *J) +{ + double t = js_todate(J, 0); + double y = js_tonumber(J, 1); + double m = js_optnumber(J, 2, MonthFromTime(t)); + double d = js_optnumber(J, 3, DateFromTime(t)); + js_setdate(J, 0, MakeDate(MakeDay(y, m, d), TimeWithinDay(t))); +} + +static void Dp_toJSON(js_State *J) +{ + js_copy(J, 0); + js_toprimitive(J, -1, JS_HNUMBER); + if (js_isnumber(J, -1) && !isfinite(js_tonumber(J, -1))) { + js_pushnull(J); + return; + } + js_pop(J, 1); + + js_getproperty(J, 0, "toISOString"); + if (!js_iscallable(J, -1)) + js_typeerror(J, "this.toISOString is not a function"); + js_copy(J, 0); + js_call(J, 0); +} + +void jsB_initdate(js_State *J) +{ + J->Date_prototype->u.number = 0; + + js_pushobject(J, J->Date_prototype); + { + jsB_propf(J, "Date.prototype.valueOf", Dp_valueOf, 0); + jsB_propf(J, "Date.prototype.toString", Dp_toString, 0); + jsB_propf(J, "Date.prototype.toDateString", Dp_toDateString, 0); + jsB_propf(J, "Date.prototype.toTimeString", Dp_toTimeString, 0); + jsB_propf(J, "Date.prototype.toLocaleString", Dp_toString, 0); + jsB_propf(J, "Date.prototype.toLocaleDateString", Dp_toDateString, 0); + jsB_propf(J, "Date.prototype.toLocaleTimeString", Dp_toTimeString, 0); + jsB_propf(J, "Date.prototype.toUTCString", Dp_toUTCString, 0); + + jsB_propf(J, "Date.prototype.getTime", Dp_valueOf, 0); + jsB_propf(J, "Date.prototype.getFullYear", Dp_getFullYear, 0); + jsB_propf(J, "Date.prototype.getUTCFullYear", Dp_getUTCFullYear, 0); + jsB_propf(J, "Date.prototype.getMonth", Dp_getMonth, 0); + jsB_propf(J, "Date.prototype.getUTCMonth", Dp_getUTCMonth, 0); + jsB_propf(J, "Date.prototype.getDate", Dp_getDate, 0); + jsB_propf(J, "Date.prototype.getUTCDate", Dp_getUTCDate, 0); + jsB_propf(J, "Date.prototype.getDay", Dp_getDay, 0); + jsB_propf(J, "Date.prototype.getUTCDay", Dp_getUTCDay, 0); + jsB_propf(J, "Date.prototype.getHours", Dp_getHours, 0); + jsB_propf(J, "Date.prototype.getUTCHours", Dp_getUTCHours, 0); + jsB_propf(J, "Date.prototype.getMinutes", Dp_getMinutes, 0); + jsB_propf(J, "Date.prototype.getUTCMinutes", Dp_getUTCMinutes, 0); + jsB_propf(J, "Date.prototype.getSeconds", Dp_getSeconds, 0); + jsB_propf(J, "Date.prototype.getUTCSeconds", Dp_getUTCSeconds, 0); + jsB_propf(J, "Date.prototype.getMilliseconds", Dp_getMilliseconds, 0); + jsB_propf(J, "Date.prototype.getUTCMilliseconds", Dp_getUTCMilliseconds, 0); + jsB_propf(J, "Date.prototype.getTimezoneOffset", Dp_getTimezoneOffset, 0); + + jsB_propf(J, "Date.prototype.setTime", Dp_setTime, 1); + jsB_propf(J, "Date.prototype.setMilliseconds", Dp_setMilliseconds, 1); + jsB_propf(J, "Date.prototype.setUTCMilliseconds", Dp_setUTCMilliseconds, 1); + jsB_propf(J, "Date.prototype.setSeconds", Dp_setSeconds, 2); + jsB_propf(J, "Date.prototype.setUTCSeconds", Dp_setUTCSeconds, 2); + jsB_propf(J, "Date.prototype.setMinutes", Dp_setMinutes, 3); + jsB_propf(J, "Date.prototype.setUTCMinutes", Dp_setUTCMinutes, 3); + jsB_propf(J, "Date.prototype.setHours", Dp_setHours, 4); + jsB_propf(J, "Date.prototype.setUTCHours", Dp_setUTCHours, 4); + jsB_propf(J, "Date.prototype.setDate", Dp_setDate, 1); + jsB_propf(J, "Date.prototype.setUTCDate", Dp_setUTCDate, 1); + jsB_propf(J, "Date.prototype.setMonth", Dp_setMonth, 2); + jsB_propf(J, "Date.prototype.setUTCMonth", Dp_setUTCMonth, 2); + jsB_propf(J, "Date.prototype.setFullYear", Dp_setFullYear, 3); + jsB_propf(J, "Date.prototype.setUTCFullYear", Dp_setUTCFullYear, 3); + + /* ES5 */ + jsB_propf(J, "Date.prototype.toISOString", Dp_toISOString, 0); + jsB_propf(J, "Date.prototype.toJSON", Dp_toJSON, 1); + } + js_newcconstructor(J, jsB_Date, jsB_new_Date, "Date", 0); /* 1 */ + { + jsB_propf(J, "Date.parse", D_parse, 1); + jsB_propf(J, "Date.UTC", D_UTC, 7); + + /* ES5 */ + jsB_propf(J, "Date.now", D_now, 0); + } + js_defglobal(J, "Date", JS_DONTENUM); +} diff --git a/mujs/jsdtoa.c b/mujs/jsdtoa.c new file mode 100644 index 0000000..a80cc6a --- /dev/null +++ b/mujs/jsdtoa.c @@ -0,0 +1,749 @@ +/* Locale-independent implementations of string <-> double conversions. */ + +#include "jsi.h" + +#if defined(_MSC_VER) && (_MSC_VER < 1700) /* VS2012 has stdint.h */ +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +#include +#include + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +/* + * format exponent like sprintf(p, "e%+d", e) + */ +void +js_fmtexp(char *p, int e) +{ + char se[9]; + int i; + + *p++ = 'e'; + if(e < 0) { + *p++ = '-'; + e = -e; + } else + *p++ = '+'; + i = 0; + while(e) { + se[i++] = e % 10 + '0'; + e /= 10; + } + while(i < 1) + se[i++] = '0'; + while(i > 0) + *p++ = se[--i]; + *p++ = '\0'; +} + +/* + * grisu2_59_56.c + * + * Grisu prints the optimal decimal representation of floating-point numbers. + * + * Copyright (c) 2009 Florian Loitsch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +typedef struct diy_fp_t { + uint64_t f; + int e; +} diy_fp_t; + +#define DIY_SIGNIFICAND_SIZE 64 +#define D_1_LOG2_10 0.30102999566398114 /* 1 / lg(10) */ + +static const uint64_t powers_ten[] = { + 0xbf29dcaba82fdeae, 0xeef453d6923bd65a, 0x9558b4661b6565f8, + 0xbaaee17fa23ebf76, 0xe95a99df8ace6f54, 0x91d8a02bb6c10594, + 0xb64ec836a47146fa, 0xe3e27a444d8d98b8, 0x8e6d8c6ab0787f73, + 0xb208ef855c969f50, 0xde8b2b66b3bc4724, 0x8b16fb203055ac76, + 0xaddcb9e83c6b1794, 0xd953e8624b85dd79, 0x87d4713d6f33aa6c, + 0xa9c98d8ccb009506, 0xd43bf0effdc0ba48, 0x84a57695fe98746d, + 0xa5ced43b7e3e9188, 0xcf42894a5dce35ea, 0x818995ce7aa0e1b2, + 0xa1ebfb4219491a1f, 0xca66fa129f9b60a7, 0xfd00b897478238d1, + 0x9e20735e8cb16382, 0xc5a890362fddbc63, 0xf712b443bbd52b7c, + 0x9a6bb0aa55653b2d, 0xc1069cd4eabe89f9, 0xf148440a256e2c77, + 0x96cd2a865764dbca, 0xbc807527ed3e12bd, 0xeba09271e88d976c, + 0x93445b8731587ea3, 0xb8157268fdae9e4c, 0xe61acf033d1a45df, + 0x8fd0c16206306bac, 0xb3c4f1ba87bc8697, 0xe0b62e2929aba83c, + 0x8c71dcd9ba0b4926, 0xaf8e5410288e1b6f, 0xdb71e91432b1a24b, + 0x892731ac9faf056f, 0xab70fe17c79ac6ca, 0xd64d3d9db981787d, + 0x85f0468293f0eb4e, 0xa76c582338ed2622, 0xd1476e2c07286faa, + 0x82cca4db847945ca, 0xa37fce126597973d, 0xcc5fc196fefd7d0c, + 0xff77b1fcbebcdc4f, 0x9faacf3df73609b1, 0xc795830d75038c1e, + 0xf97ae3d0d2446f25, 0x9becce62836ac577, 0xc2e801fb244576d5, + 0xf3a20279ed56d48a, 0x9845418c345644d7, 0xbe5691ef416bd60c, + 0xedec366b11c6cb8f, 0x94b3a202eb1c3f39, 0xb9e08a83a5e34f08, + 0xe858ad248f5c22ca, 0x91376c36d99995be, 0xb58547448ffffb2e, + 0xe2e69915b3fff9f9, 0x8dd01fad907ffc3c, 0xb1442798f49ffb4b, + 0xdd95317f31c7fa1d, 0x8a7d3eef7f1cfc52, 0xad1c8eab5ee43b67, + 0xd863b256369d4a41, 0x873e4f75e2224e68, 0xa90de3535aaae202, + 0xd3515c2831559a83, 0x8412d9991ed58092, 0xa5178fff668ae0b6, + 0xce5d73ff402d98e4, 0x80fa687f881c7f8e, 0xa139029f6a239f72, + 0xc987434744ac874f, 0xfbe9141915d7a922, 0x9d71ac8fada6c9b5, + 0xc4ce17b399107c23, 0xf6019da07f549b2b, 0x99c102844f94e0fb, + 0xc0314325637a193a, 0xf03d93eebc589f88, 0x96267c7535b763b5, + 0xbbb01b9283253ca3, 0xea9c227723ee8bcb, 0x92a1958a7675175f, + 0xb749faed14125d37, 0xe51c79a85916f485, 0x8f31cc0937ae58d3, + 0xb2fe3f0b8599ef08, 0xdfbdcece67006ac9, 0x8bd6a141006042be, + 0xaecc49914078536d, 0xda7f5bf590966849, 0x888f99797a5e012d, + 0xaab37fd7d8f58179, 0xd5605fcdcf32e1d7, 0x855c3be0a17fcd26, + 0xa6b34ad8c9dfc070, 0xd0601d8efc57b08c, 0x823c12795db6ce57, + 0xa2cb1717b52481ed, 0xcb7ddcdda26da269, 0xfe5d54150b090b03, + 0x9efa548d26e5a6e2, 0xc6b8e9b0709f109a, 0xf867241c8cc6d4c1, + 0x9b407691d7fc44f8, 0xc21094364dfb5637, 0xf294b943e17a2bc4, + 0x979cf3ca6cec5b5b, 0xbd8430bd08277231, 0xece53cec4a314ebe, + 0x940f4613ae5ed137, 0xb913179899f68584, 0xe757dd7ec07426e5, + 0x9096ea6f3848984f, 0xb4bca50b065abe63, 0xe1ebce4dc7f16dfc, + 0x8d3360f09cf6e4bd, 0xb080392cc4349ded, 0xdca04777f541c568, + 0x89e42caaf9491b61, 0xac5d37d5b79b6239, 0xd77485cb25823ac7, + 0x86a8d39ef77164bd, 0xa8530886b54dbdec, 0xd267caa862a12d67, + 0x8380dea93da4bc60, 0xa46116538d0deb78, 0xcd795be870516656, + 0x806bd9714632dff6, 0xa086cfcd97bf97f4, 0xc8a883c0fdaf7df0, + 0xfad2a4b13d1b5d6c, 0x9cc3a6eec6311a64, 0xc3f490aa77bd60fd, + 0xf4f1b4d515acb93c, 0x991711052d8bf3c5, 0xbf5cd54678eef0b7, + 0xef340a98172aace5, 0x9580869f0e7aac0f, 0xbae0a846d2195713, + 0xe998d258869facd7, 0x91ff83775423cc06, 0xb67f6455292cbf08, + 0xe41f3d6a7377eeca, 0x8e938662882af53e, 0xb23867fb2a35b28e, + 0xdec681f9f4c31f31, 0x8b3c113c38f9f37f, 0xae0b158b4738705f, + 0xd98ddaee19068c76, 0x87f8a8d4cfa417ca, 0xa9f6d30a038d1dbc, + 0xd47487cc8470652b, 0x84c8d4dfd2c63f3b, 0xa5fb0a17c777cf0a, + 0xcf79cc9db955c2cc, 0x81ac1fe293d599c0, 0xa21727db38cb0030, + 0xca9cf1d206fdc03c, 0xfd442e4688bd304b, 0x9e4a9cec15763e2f, + 0xc5dd44271ad3cdba, 0xf7549530e188c129, 0x9a94dd3e8cf578ba, + 0xc13a148e3032d6e8, 0xf18899b1bc3f8ca2, 0x96f5600f15a7b7e5, + 0xbcb2b812db11a5de, 0xebdf661791d60f56, 0x936b9fcebb25c996, + 0xb84687c269ef3bfb, 0xe65829b3046b0afa, 0x8ff71a0fe2c2e6dc, + 0xb3f4e093db73a093, 0xe0f218b8d25088b8, 0x8c974f7383725573, + 0xafbd2350644eead0, 0xdbac6c247d62a584, 0x894bc396ce5da772, + 0xab9eb47c81f5114f, 0xd686619ba27255a3, 0x8613fd0145877586, + 0xa798fc4196e952e7, 0xd17f3b51fca3a7a1, 0x82ef85133de648c5, + 0xa3ab66580d5fdaf6, 0xcc963fee10b7d1b3, 0xffbbcfe994e5c620, + 0x9fd561f1fd0f9bd4, 0xc7caba6e7c5382c9, 0xf9bd690a1b68637b, + 0x9c1661a651213e2d, 0xc31bfa0fe5698db8, 0xf3e2f893dec3f126, + 0x986ddb5c6b3a76b8, 0xbe89523386091466, 0xee2ba6c0678b597f, + 0x94db483840b717f0, 0xba121a4650e4ddec, 0xe896a0d7e51e1566, + 0x915e2486ef32cd60, 0xb5b5ada8aaff80b8, 0xe3231912d5bf60e6, + 0x8df5efabc5979c90, 0xb1736b96b6fd83b4, 0xddd0467c64bce4a1, + 0x8aa22c0dbef60ee4, 0xad4ab7112eb3929e, 0xd89d64d57a607745, + 0x87625f056c7c4a8b, 0xa93af6c6c79b5d2e, 0xd389b47879823479, + 0x843610cb4bf160cc, 0xa54394fe1eedb8ff, 0xce947a3da6a9273e, + 0x811ccc668829b887, 0xa163ff802a3426a9, 0xc9bcff6034c13053, + 0xfc2c3f3841f17c68, 0x9d9ba7832936edc1, 0xc5029163f384a931, + 0xf64335bcf065d37d, 0x99ea0196163fa42e, 0xc06481fb9bcf8d3a, + 0xf07da27a82c37088, 0x964e858c91ba2655, 0xbbe226efb628afeb, + 0xeadab0aba3b2dbe5, 0x92c8ae6b464fc96f, 0xb77ada0617e3bbcb, + 0xe55990879ddcaabe, 0x8f57fa54c2a9eab7, 0xb32df8e9f3546564, + 0xdff9772470297ebd, 0x8bfbea76c619ef36, 0xaefae51477a06b04, + 0xdab99e59958885c5, 0x88b402f7fd75539b, 0xaae103b5fcd2a882, + 0xd59944a37c0752a2, 0x857fcae62d8493a5, 0xa6dfbd9fb8e5b88f, + 0xd097ad07a71f26b2, 0x825ecc24c8737830, 0xa2f67f2dfa90563b, + 0xcbb41ef979346bca, 0xfea126b7d78186bd, 0x9f24b832e6b0f436, + 0xc6ede63fa05d3144, 0xf8a95fcf88747d94, 0x9b69dbe1b548ce7d, + 0xc24452da229b021c, 0xf2d56790ab41c2a3, 0x97c560ba6b0919a6, + 0xbdb6b8e905cb600f, 0xed246723473e3813, 0x9436c0760c86e30c, + 0xb94470938fa89bcf, 0xe7958cb87392c2c3, 0x90bd77f3483bb9ba, + 0xb4ecd5f01a4aa828, 0xe2280b6c20dd5232, 0x8d590723948a535f, + 0xb0af48ec79ace837, 0xdcdb1b2798182245, 0x8a08f0f8bf0f156b, + 0xac8b2d36eed2dac6, 0xd7adf884aa879177, 0x86ccbb52ea94baeb, + 0xa87fea27a539e9a5, 0xd29fe4b18e88640f, 0x83a3eeeef9153e89, + 0xa48ceaaab75a8e2b, 0xcdb02555653131b6, 0x808e17555f3ebf12, + 0xa0b19d2ab70e6ed6, 0xc8de047564d20a8c, 0xfb158592be068d2f, + 0x9ced737bb6c4183d, 0xc428d05aa4751e4d, 0xf53304714d9265e0, + 0x993fe2c6d07b7fac, 0xbf8fdb78849a5f97, 0xef73d256a5c0f77d, + 0x95a8637627989aae, 0xbb127c53b17ec159, 0xe9d71b689dde71b0, + 0x9226712162ab070e, 0xb6b00d69bb55c8d1, 0xe45c10c42a2b3b06, + 0x8eb98a7a9a5b04e3, 0xb267ed1940f1c61c, 0xdf01e85f912e37a3, + 0x8b61313bbabce2c6, 0xae397d8aa96c1b78, 0xd9c7dced53c72256, + 0x881cea14545c7575, 0xaa242499697392d3, 0xd4ad2dbfc3d07788, + 0x84ec3c97da624ab5, 0xa6274bbdd0fadd62, 0xcfb11ead453994ba, + 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3e, + 0xfd87b5f28300ca0e, 0x9e74d1b791e07e48, 0xc612062576589ddb, + 0xf79687aed3eec551, 0x9abe14cd44753b53, 0xc16d9a0095928a27, + 0xf1c90080baf72cb1, 0x971da05074da7bef, 0xbce5086492111aeb, + 0xec1e4a7db69561a5, 0x9392ee8e921d5d07, 0xb877aa3236a4b449, + 0xe69594bec44de15b, 0x901d7cf73ab0acd9, 0xb424dc35095cd80f, + 0xe12e13424bb40e13, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, + 0xdbe6fecebdedd5bf, 0x89705f4136b4a597, 0xabcc77118461cefd, + 0xd6bf94d5e57a42bc, 0x8637bd05af6c69b6, 0xa7c5ac471b478423, + 0xd1b71758e219652c, 0x83126e978d4fdf3b, 0xa3d70a3d70a3d70a, + 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, + 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, + 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, + 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, + 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, + 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, + 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, + 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, + 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984, + 0xa18f07d736b90be5, 0xc9f2c9cd04674edf, 0xfc6f7c4045812296, + 0x9dc5ada82b70b59e, 0xc5371912364ce305, 0xf684df56c3e01bc7, + 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20, + 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd, + 0x92efd1b8d0cf37be, 0xb7abc627050305ae, 0xe596b7b0c643c719, + 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f, + 0x8c213d9da502de45, 0xaf298d050e4395d7, 0xdaf3f04651d47b4c, + 0x88d8762bf324cd10, 0xab0e93b6efee0054, 0xd5d238a4abe98068, + 0x85a36366eb71f041, 0xa70c3c40a64e6c52, 0xd0cf4b50cfe20766, + 0x82818f1281ed44a0, 0xa321f2d7226895c8, 0xcbea6f8ceb02bb3a, + 0xfee50b7025c36a08, 0x9f4f2726179a2245, 0xc722f0ef9d80aad6, + 0xf8ebad2b84e0d58c, 0x9b934c3b330c8577, 0xc2781f49ffcfa6d5, + 0xf316271c7fc3908b, 0x97edd871cfda3a57, 0xbde94e8e43d0c8ec, + 0xed63a231d4c4fb27, 0x945e455f24fb1cf9, 0xb975d6b6ee39e437, + 0xe7d34c64a9c85d44, 0x90e40fbeea1d3a4b, 0xb51d13aea4a488dd, + 0xe264589a4dcdab15, 0x8d7eb76070a08aed, 0xb0de65388cc8ada8, + 0xdd15fe86affad912, 0x8a2dbf142dfcc7ab, 0xacb92ed9397bf996, + 0xd7e77a8f87daf7fc, 0x86f0ac99b4e8dafd, 0xa8acd7c0222311bd, + 0xd2d80db02aabd62c, 0x83c7088e1aab65db, 0xa4b8cab1a1563f52, + 0xcde6fd5e09abcf27, 0x80b05e5ac60b6178, 0xa0dc75f1778e39d6, + 0xc913936dd571c84c, 0xfb5878494ace3a5f, 0x9d174b2dcec0e47b, + 0xc45d1df942711d9a, 0xf5746577930d6501, 0x9968bf6abbe85f20, + 0xbfc2ef456ae276e9, 0xefb3ab16c59b14a3, 0x95d04aee3b80ece6, + 0xbb445da9ca61281f, 0xea1575143cf97227, 0x924d692ca61be758, + 0xb6e0c377cfa2e12e, 0xe498f455c38b997a, 0x8edf98b59a373fec, + 0xb2977ee300c50fe7, 0xdf3d5e9bc0f653e1, 0x8b865b215899f46d, + 0xae67f1e9aec07188, 0xda01ee641a708dea, 0x884134fe908658b2, + 0xaa51823e34a7eedf, 0xd4e5e2cdc1d1ea96, 0x850fadc09923329e, + 0xa6539930bf6bff46, 0xcfe87f7cef46ff17, 0x81f14fae158c5f6e, + 0xa26da3999aef774a, 0xcb090c8001ab551c, 0xfdcb4fa002162a63, + 0x9e9f11c4014dda7e, 0xc646d63501a1511e, 0xf7d88bc24209a565, + 0x9ae757596946075f, 0xc1a12d2fc3978937, 0xf209787bb47d6b85, + 0x9745eb4d50ce6333, 0xbd176620a501fc00, 0xec5d3fa8ce427b00, + 0x93ba47c980e98ce0, 0xb8a8d9bbe123f018, 0xe6d3102ad96cec1e, + 0x9043ea1ac7e41393, 0xb454e4a179dd1877, 0xe16a1dc9d8545e95, + 0x8ce2529e2734bb1d, 0xb01ae745b101e9e4, 0xdc21a1171d42645d, + 0x899504ae72497eba, 0xabfa45da0edbde69, 0xd6f8d7509292d603, + 0x865b86925b9bc5c2, 0xa7f26836f282b733, 0xd1ef0244af2364ff, + 0x8335616aed761f1f, 0xa402b9c5a8d3a6e7, 0xcd036837130890a1, + 0x802221226be55a65, 0xa02aa96b06deb0fe, 0xc83553c5c8965d3d, + 0xfa42a8b73abbf48d, 0x9c69a97284b578d8, 0xc38413cf25e2d70e, + 0xf46518c2ef5b8cd1, 0x98bf2f79d5993803, 0xbeeefb584aff8604, + 0xeeaaba2e5dbf6785, 0x952ab45cfa97a0b3, 0xba756174393d88e0, + 0xe912b9d1478ceb17, 0x91abb422ccb812ef, 0xb616a12b7fe617aa, + 0xe39c49765fdf9d95, 0x8e41ade9fbebc27d, 0xb1d219647ae6b31c, + 0xde469fbd99a05fe3, 0x8aec23d680043bee, 0xada72ccc20054aea, + 0xd910f7ff28069da4, 0x87aa9aff79042287, 0xa99541bf57452b28, + 0xd3fa922f2d1675f2, 0x847c9b5d7c2e09b7, 0xa59bc234db398c25, + 0xcf02b2c21207ef2f, 0x8161afb94b44f57d, 0xa1ba1ba79e1632dc, + 0xca28a291859bbf93, 0xfcb2cb35e702af78, 0x9defbf01b061adab, + 0xc56baec21c7a1916, 0xf6c69a72a3989f5c, 0x9a3c2087a63f6399, + 0xc0cb28a98fcf3c80, 0xf0fdf2d3f3c30b9f, 0x969eb7c47859e744, + 0xbc4665b596706115, 0xeb57ff22fc0c795a, 0x9316ff75dd87cbd8, + 0xb7dcbf5354e9bece, 0xe5d3ef282a242e82, 0x8fa475791a569d11, + 0xb38d92d760ec4455, 0xe070f78d3927556b, 0x8c469ab843b89563, + 0xaf58416654a6babb, 0xdb2e51bfe9d0696a, 0x88fcf317f22241e2, + 0xab3c2fddeeaad25b, 0xd60b3bd56a5586f2, 0x85c7056562757457, + 0xa738c6bebb12d16d, 0xd106f86e69d785c8, 0x82a45b450226b39d, + 0xa34d721642b06084, 0xcc20ce9bd35c78a5, 0xff290242c83396ce, + 0x9f79a169bd203e41, 0xc75809c42c684dd1, 0xf92e0c3537826146, + 0x9bbcc7a142b17ccc, 0xc2abf989935ddbfe, 0xf356f7ebf83552fe, + 0x98165af37b2153df, 0xbe1bf1b059e9a8d6, 0xeda2ee1c7064130c, + 0x9485d4d1c63e8be8, 0xb9a74a0637ce2ee1, 0xe8111c87c5c1ba9a, + 0x910ab1d4db9914a0, 0xb54d5e4a127f59c8, 0xe2a0b5dc971f303a, + 0x8da471a9de737e24, 0xb10d8e1456105dad, 0xdd50f1996b947519, + 0x8a5296ffe33cc930, 0xace73cbfdc0bfb7b, 0xd8210befd30efa5a, + 0x8714a775e3e95c78, 0xa8d9d1535ce3b396, 0xd31045a8341ca07c, + 0x83ea2b892091e44e, 0xa4e4b66b68b65d61, 0xce1de40642e3f4b9, + 0x80d2ae83e9ce78f4, 0xa1075a24e4421731, 0xc94930ae1d529cfd, + 0xfb9b7cd9a4a7443c, 0x9d412e0806e88aa6, 0xc491798a08a2ad4f, + 0xf5b5d7ec8acb58a3, 0x9991a6f3d6bf1766, 0xbff610b0cc6edd3f, + 0xeff394dcff8a948f, 0x95f83d0a1fb69cd9, 0xbb764c4ca7a44410, + 0xea53df5fd18d5514, 0x92746b9be2f8552c, 0xb7118682dbb66a77, + 0xe4d5e82392a40515, 0x8f05b1163ba6832d, 0xb2c71d5bca9023f8, + 0xdf78e4b2bd342cf7, 0x8bab8eefb6409c1a, 0xae9672aba3d0c321, + 0xda3c0f568cc4f3e9, 0x8865899617fb1871, 0xaa7eebfb9df9de8e, + 0xd51ea6fa85785631, 0x8533285c936b35df, 0xa67ff273b8460357, + 0xd01fef10a657842c, 0x8213f56a67f6b29c, 0xa298f2c501f45f43, + 0xcb3f2f7642717713, 0xfe0efb53d30dd4d8, 0x9ec95d1463e8a507, + 0xc67bb4597ce2ce49, 0xf81aa16fdc1b81db, 0x9b10a4e5e9913129, + 0xc1d4ce1f63f57d73, 0xf24a01a73cf2dcd0, 0x976e41088617ca02, + 0xbd49d14aa79dbc82, 0xec9c459d51852ba3, 0x93e1ab8252f33b46, + 0xb8da1662e7b00a17, 0xe7109bfba19c0c9d, 0x906a617d450187e2, + 0xb484f9dc9641e9db, 0xe1a63853bbd26451, 0x8d07e33455637eb3, + 0xb049dc016abc5e60, 0xdc5c5301c56b75f7, 0x89b9b3e11b6329bb, + 0xac2820d9623bf429, 0xd732290fbacaf134, 0x867f59a9d4bed6c0, + 0xa81f301449ee8c70, 0xd226fc195c6a2f8c, 0x83585d8fd9c25db8, + 0xa42e74f3d032f526, 0xcd3a1230c43fb26f, 0x80444b5e7aa7cf85, + 0xa0555e361951c367, 0xc86ab5c39fa63441, 0xfa856334878fc151, + 0x9c935e00d4b9d8d2, 0xc3b8358109e84f07, 0xf4a642e14c6262c9, + 0x98e7e9cccfbd7dbe, 0xbf21e44003acdd2d, 0xeeea5d5004981478, + 0x95527a5202df0ccb, 0xbaa718e68396cffe, 0xe950df20247c83fd, + 0x91d28b7416cdd27e, 0xb6472e511c81471e, 0xe3d8f9e563a198e5, + 0x8e679c2f5e44ff8f, 0xb201833b35d63f73, 0xde81e40a034bcf50, + 0x8b112e86420f6192, 0xadd57a27d29339f6, 0xd94ad8b1c7380874, + 0x87cec76f1c830549, 0xa9c2794ae3a3c69b, 0xd433179d9c8cb841, + 0x849feec281d7f329, 0xa5c7ea73224deff3, 0xcf39e50feae16bf0, + 0x81842f29f2cce376, 0xa1e53af46f801c53, 0xca5e89b18b602368, + 0xfcf62c1dee382c42, 0x9e19db92b4e31ba9, 0xc5a05277621be294, + 0xf70867153aa2db39, 0x9a65406d44a5c903, 0xc0fe908895cf3b44, + 0xf13e34aabb430a15, 0x96c6e0eab509e64d, 0xbc789925624c5fe1, + 0xeb96bf6ebadf77d9, 0x933e37a534cbaae8, 0xb80dc58e81fe95a1, + 0xe61136f2227e3b0a, 0x8fcac257558ee4e6, 0xb3bd72ed2af29e20, + 0xe0accfa875af45a8, 0x8c6c01c9498d8b89, 0xaf87023b9bf0ee6b, + 0xdb68c2ca82ed2a06, 0x892179be91d43a44, 0xab69d82e364948d4 +}; + +static const int powers_ten_e[] = { + -1203, -1200, -1196, -1193, -1190, -1186, -1183, -1180, -1176, -1173, + -1170, -1166, -1163, -1160, -1156, -1153, -1150, -1146, -1143, -1140, + -1136, -1133, -1130, -1127, -1123, -1120, -1117, -1113, -1110, -1107, + -1103, -1100, -1097, -1093, -1090, -1087, -1083, -1080, -1077, -1073, + -1070, -1067, -1063, -1060, -1057, -1053, -1050, -1047, -1043, -1040, + -1037, -1034, -1030, -1027, -1024, -1020, -1017, -1014, -1010, -1007, + -1004, -1000, -997, -994, -990, -987, -984, -980, -977, -974, -970, + -967, -964, -960, -957, -954, -950, -947, -944, -940, -937, -934, -931, + -927, -924, -921, -917, -914, -911, -907, -904, -901, -897, -894, -891, + -887, -884, -881, -877, -874, -871, -867, -864, -861, -857, -854, -851, + -847, -844, -841, -838, -834, -831, -828, -824, -821, -818, -814, -811, + -808, -804, -801, -798, -794, -791, -788, -784, -781, -778, -774, -771, + -768, -764, -761, -758, -754, -751, -748, -744, -741, -738, -735, -731, + -728, -725, -721, -718, -715, -711, -708, -705, -701, -698, -695, -691, + -688, -685, -681, -678, -675, -671, -668, -665, -661, -658, -655, -651, + -648, -645, -642, -638, -635, -632, -628, -625, -622, -618, -615, -612, + -608, -605, -602, -598, -595, -592, -588, -585, -582, -578, -575, -572, + -568, -565, -562, -558, -555, -552, -549, -545, -542, -539, -535, -532, + -529, -525, -522, -519, -515, -512, -509, -505, -502, -499, -495, -492, + -489, -485, -482, -479, -475, -472, -469, -465, -462, -459, -455, -452, + -449, -446, -442, -439, -436, -432, -429, -426, -422, -419, -416, -412, + -409, -406, -402, -399, -396, -392, -389, -386, -382, -379, -376, -372, + -369, -366, -362, -359, -356, -353, -349, -346, -343, -339, -336, -333, + -329, -326, -323, -319, -316, -313, -309, -306, -303, -299, -296, -293, + -289, -286, -283, -279, -276, -273, -269, -266, -263, -259, -256, -253, + -250, -246, -243, -240, -236, -233, -230, -226, -223, -220, -216, -213, + -210, -206, -203, -200, -196, -193, -190, -186, -183, -180, -176, -173, + -170, -166, -163, -160, -157, -153, -150, -147, -143, -140, -137, -133, + -130, -127, -123, -120, -117, -113, -110, -107, -103, -100, -97, -93, + -90, -87, -83, -80, -77, -73, -70, -67, -63, -60, -57, -54, -50, -47, + -44, -40, -37, -34, -30, -27, -24, -20, -17, -14, -10, -7, -4, 0, 3, 6, + 10, 13, 16, 20, 23, 26, 30, 33, 36, 39, 43, 46, 49, 53, 56, 59, 63, 66, + 69, 73, 76, 79, 83, 86, 89, 93, 96, 99, 103, 106, 109, 113, 116, 119, + 123, 126, 129, 132, 136, 139, 142, 146, 149, 152, 156, 159, 162, 166, + 169, 172, 176, 179, 182, 186, 189, 192, 196, 199, 202, 206, 209, 212, + 216, 219, 222, 226, 229, 232, 235, 239, 242, 245, 249, 252, 255, 259, + 262, 265, 269, 272, 275, 279, 282, 285, 289, 292, 295, 299, 302, 305, + 309, 312, 315, 319, 322, 325, 328, 332, 335, 338, 342, 345, 348, 352, + 355, 358, 362, 365, 368, 372, 375, 378, 382, 385, 388, 392, 395, 398, + 402, 405, 408, 412, 415, 418, 422, 425, 428, 431, 435, 438, 441, 445, + 448, 451, 455, 458, 461, 465, 468, 471, 475, 478, 481, 485, 488, 491, + 495, 498, 501, 505, 508, 511, 515, 518, 521, 524, 528, 531, 534, 538, + 541, 544, 548, 551, 554, 558, 561, 564, 568, 571, 574, 578, 581, 584, + 588, 591, 594, 598, 601, 604, 608, 611, 614, 617, 621, 624, 627, 631, + 634, 637, 641, 644, 647, 651, 654, 657, 661, 664, 667, 671, 674, 677, + 681, 684, 687, 691, 694, 697, 701, 704, 707, 711, 714, 717, 720, 724, + 727, 730, 734, 737, 740, 744, 747, 750, 754, 757, 760, 764, 767, 770, + 774, 777, 780, 784, 787, 790, 794, 797, 800, 804, 807, 810, 813, 817, + 820, 823, 827, 830, 833, 837, 840, 843, 847, 850, 853, 857, 860, 863, + 867, 870, 873, 877, 880, 883, 887, 890, 893, 897, 900, 903, 907, 910, + 913, 916, 920, 923, 926, 930, 933, 936, 940, 943, 946, 950, 953, 956, + 960, 963, 966, 970, 973, 976, 980, 983, 986, 990, 993, 996, 1000, 1003, + 1006, 1009, 1013, 1016, 1019, 1023, 1026, 1029, 1033, 1036, 1039, 1043, + 1046, 1049, 1053, 1056, 1059, 1063, 1066, 1069, 1073, 1076 +}; + +static diy_fp_t cached_power(int k) +{ + diy_fp_t res; + int index = 343 + k; + res.f = powers_ten[index]; + res.e = powers_ten_e[index]; + return res; +} + +static int k_comp(int e, int alpha, int gamma) { + return ceil((alpha-e+63) * D_1_LOG2_10); +} + +static diy_fp_t minus(diy_fp_t x, diy_fp_t y) +{ + diy_fp_t r; + assert(x.e == y.e); + assert(x.f >= y.f); + r.f = x.f - y.f; + r.e = x.e; + return r; +} + +static diy_fp_t multiply(diy_fp_t x, diy_fp_t y) +{ + uint64_t a,b,c,d,ac,bc,ad,bd,tmp; + diy_fp_t r; + uint64_t M32 = 0xFFFFFFFF; + a = x.f >> 32; b = x.f & M32; + c = y.f >> 32; d = y.f & M32; + ac = a*c; bc = b*c; ad = a*d; bd = b*d; + tmp = (bd>>32) + (ad&M32) + (bc&M32); + tmp += 1U << 31; + r.f = ac+(ad>>32)+(bc>>32)+(tmp >>32); + r.e = x.e + y.e + 64; + return r; +} + +static uint64_t double_to_uint64(double d) +{ + uint64_t n; + memcpy(&n, &d, 8); + return n; +} + +#define DP_SIGNIFICAND_SIZE 52 +#define DP_EXPONENT_BIAS (0x3FF + DP_SIGNIFICAND_SIZE) +#define DP_MIN_EXPONENT (-DP_EXPONENT_BIAS) +#define DP_EXPONENT_MASK 0x7FF0000000000000 +#define DP_SIGNIFICAND_MASK 0x000FFFFFFFFFFFFF +#define DP_HIDDEN_BIT 0x0010000000000000 + +static diy_fp_t double2diy_fp(double d) +{ + uint64_t d64 = double_to_uint64(d); + int biased_e = (d64 & DP_EXPONENT_MASK) >> DP_SIGNIFICAND_SIZE; + uint64_t significand = (d64 & DP_SIGNIFICAND_MASK); + diy_fp_t res; + if (biased_e != 0) { + res.f = significand + DP_HIDDEN_BIT; + res.e = biased_e - DP_EXPONENT_BIAS; + } else { + res.f = significand; + res.e = DP_MIN_EXPONENT + 1; + } + return res; +} + +static diy_fp_t normalize_boundary(diy_fp_t in) +{ + diy_fp_t res = in; + /* Normalize now */ + /* the original number could have been a denormal. */ + while (! (res.f & (DP_HIDDEN_BIT << 1))) { + res.f <<= 1; + res.e--; + } + /* do the final shifts in one go. Don't forget the hidden bit (the '-1') */ + res.f <<= (DIY_SIGNIFICAND_SIZE - DP_SIGNIFICAND_SIZE - 2); + res.e = res.e - (DIY_SIGNIFICAND_SIZE - DP_SIGNIFICAND_SIZE - 2); + return res; +} + +static void normalized_boundaries(double d, diy_fp_t* out_m_minus, diy_fp_t* out_m_plus) +{ + diy_fp_t v = double2diy_fp(d); + diy_fp_t pl, mi; + int significand_is_zero = v.f == DP_HIDDEN_BIT; + pl.f = (v.f << 1) + 1; pl.e = v.e - 1; + pl = normalize_boundary(pl); + if (significand_is_zero) { + mi.f = (v.f << 2) - 1; + mi.e = v.e - 2; + } else { + mi.f = (v.f << 1) - 1; + mi.e = v.e - 1; + } + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *out_m_plus = pl; + *out_m_minus = mi; +} + +#define TEN2 100 +static void digit_gen(diy_fp_t Mp, diy_fp_t delta, char* buffer, int* len, int* K) +{ + uint32_t div, p1; + uint64_t p2; + int d,kappa; + diy_fp_t one; + one.f = ((uint64_t) 1) << -Mp.e; one.e = Mp.e; + p1 = Mp.f >> -one.e; + p2 = Mp.f & (one.f - 1); + *len = 0; kappa = 3; div = TEN2; + while (kappa > 0) { + d = p1 / div; + if (d || *len) buffer[(*len)++] = '0' + d; + p1 %= div; kappa--; div /= 10; + if ((((uint64_t)p1)<<-one.e)+p2 <= delta.f) { + *K += kappa; return; + } + } + do { + p2 *= 10; + d = p2 >> -one.e; + if (d || *len) buffer[(*len)++] = '0' + d; + p2 &= one.f - 1; kappa--; delta.f *= 10; + } while (p2 > delta.f); + *K += kappa; +} + +int +js_grisu2(double v, char *buffer, int *K) +{ + int length, mk; + diy_fp_t w_m, w_p, c_mk, Wp, Wm, delta; + int q = 64, alpha = -59, gamma = -56; + normalized_boundaries(v, &w_m, &w_p); + mk = k_comp(w_p.e + q, alpha, gamma); + c_mk = cached_power(mk); + Wp = multiply(w_p, c_mk); + Wm = multiply(w_m, c_mk); + Wm.f++; Wp.f--; + delta = minus(Wp, Wm); + *K = -mk; + digit_gen(Wp, delta, buffer, &length, K); + return length; +} + +/* + * strtod.c + * + * Copyright (c) 1988-1993 The Regents of the University of California. + * Copyright (c) 1994 Sun Microsystems, Inc. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, provided + * that the above copyright notice appear in all copies. The University of + * California makes no representations about the suitability of this software + * for any purpose. It is provided "as is" without express or implied warranty. + */ + +/* Largest possible base 10 exponent. Any exponent larger than this will + * already produce underflow or overflow, so there's no need to worry about + * additional digits. + */ +static int maxExponent = 511; + +/* Table giving binary powers of 10. Entry + * is 10^2^i. Used to convert decimal + * exponents into floating-point numbers. + */ +static double powersOf10[] = { + 10., + 100., + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 +}; + +/* Parse a decimal ASCII floating-point number, optionally preceded by white + * space. Must have form "-I.FE-X", where I is the integer part of the + * mantissa, F is the fractional part of the mantissa, and X is the exponent. + * Either of the signs may be "+", "-", or omitted. Either I or F may be + * omitted, or both. The decimal point isn't necessary unless F is present. + * The "E" may actually be an "e". E and X may both be omitted (but not just + * one). + */ +double +js_strtod(const char *string, char **endPtr) +{ + int sign, expSign = FALSE; + double fraction, dblExp, *d; + register const char *p; + register int c; + + /* Exponent read from "EX" field. */ + int exp = 0; + + /* Exponent that derives from the fractional part. Under normal + * circumstances, it is the negative of the number of digits in F. + * However, if I is very long, the last digits of I get dropped + * (otherwise a long I with a large negative exponent could cause an + * unnecessary overflow on I alone). In this case, fracExp is + * incremented one for each dropped digit. + */ + int fracExp = 0; + + /* Number of digits in mantissa. */ + int mantSize; + + /* Number of mantissa digits BEFORE decimal point. */ + int decPt; + + /* Temporarily holds location of exponent in string. */ + const char *pExp; + + /* + * Strip off leading blanks and check for a sign. + */ + + p = string; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') { + p += 1; + } + if (*p == '-') { + sign = TRUE; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + sign = FALSE; + } + + /* + * Count the number of digits in the mantissa (including the decimal + * point), and also locate the decimal point. + */ + + decPt = -1; + for (mantSize = 0; ; mantSize += 1) + { + c = *p; + if (!(c>='0'&&c<='9')) { + if ((c != '.') || (decPt >= 0)) { + break; + } + decPt = mantSize; + } + p += 1; + } + + /* + * Now suck up the digits in the mantissa. Use two integers to + * collect 9 digits each (this is faster than using floating-point). + * If the mantissa has more than 18 digits, ignore the extras, since + * they can't affect the value anyway. + */ + + pExp = p; + p -= mantSize; + if (decPt < 0) { + decPt = mantSize; + } else { + mantSize -= 1; /* One of the digits was the point. */ + } + if (mantSize > 18) { + fracExp = decPt - 18; + mantSize = 18; + } else { + fracExp = decPt - mantSize; + } + if (mantSize == 0) { + fraction = 0.0; + p = string; + goto done; + } else { + int frac1, frac2; + frac1 = 0; + for ( ; mantSize > 9; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac1 = 10*frac1 + (c - '0'); + } + frac2 = 0; + for (; mantSize > 0; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac2 = 10*frac2 + (c - '0'); + } + fraction = (1.0e9 * frac1) + frac2; + } + + /* + * Skim off the exponent. + */ + + p = pExp; + if ((*p == 'E') || (*p == 'e')) { + p += 1; + if (*p == '-') { + expSign = TRUE; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + expSign = FALSE; + } + while ((*p >= '0') && (*p <= '9') && exp < INT_MAX/100) { + exp = exp * 10 + (*p - '0'); + p += 1; + } + while ((*p >= '0') && (*p <= '9')) + p += 1; + } + if (expSign) { + exp = fracExp - exp; + } else { + exp = fracExp + exp; + } + + /* + * Generate a floating-point number that represents the exponent. + * Do this by processing the exponent one bit at a time to combine + * many powers of 2 of 10. Then combine the exponent with the + * fraction. + */ + + if (exp < -maxExponent) { + exp = maxExponent; + expSign = TRUE; + errno = ERANGE; + } else if (exp > maxExponent) { + exp = maxExponent; + expSign = FALSE; + errno = ERANGE; + } else if (exp < 0) { + expSign = TRUE; + exp = -exp; + } else { + expSign = FALSE; + } + dblExp = 1.0; + for (d = powersOf10; exp != 0; exp >>= 1, d += 1) { + if (exp & 01) { + dblExp *= *d; + } + } + if (expSign) { + fraction /= dblExp; + } else { + fraction *= dblExp; + } + +done: + if (endPtr != NULL) { + *endPtr = (char *) p; + } + + if (sign) { + return -fraction; + } + return fraction; +} diff --git a/mujs/jserror.c b/mujs/jserror.c new file mode 100644 index 0000000..50ce982 --- /dev/null +++ b/mujs/jserror.c @@ -0,0 +1,128 @@ +#include "jsi.h" + +#define QQ(X) #X +#define Q(X) QQ(X) + +static int jsB_stacktrace(js_State *J, int skip) +{ + char buf[256]; + int n = J->tracetop - skip; + if (n <= 0) + return 0; + for (; n > 0; --n) { + const char *name = J->trace[n].name; + const char *file = J->trace[n].file; + int line = J->trace[n].line; + if (line > 0) { + if (name[0]) + snprintf(buf, sizeof buf, "\n\tat %s (%s:%d)", name, file, line); + else + snprintf(buf, sizeof buf, "\n\tat %s:%d", file, line); + } else + snprintf(buf, sizeof buf, "\n\tat %s (%s)", name, file); + js_pushstring(J, buf); + if (n < J->tracetop - skip) + js_concat(J); + } + return 1; +} + +static void Ep_toString(js_State *J) +{ + const char *name = "Error"; + const char *message = ""; + + if (!js_isobject(J, -1)) + js_typeerror(J, "not an object"); + + if (js_hasproperty(J, 0, "name")) + name = js_tostring(J, -1); + if (js_hasproperty(J, 0, "message")) + message = js_tostring(J, -1); + + if (name[0] == 0) + js_pushstring(J, message); + else if (message[0] == 0) + js_pushstring(J, name); + else { + js_pushstring(J, name); + js_pushstring(J, ": "); + js_concat(J); + js_pushstring(J, message); + js_concat(J); + } +} + +static int jsB_ErrorX(js_State *J, js_Object *prototype) +{ + js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype)); + if (js_isdefined(J, 1)) { + js_pushstring(J, js_tostring(J, 1)); + js_defproperty(J, -2, "message", JS_DONTENUM); + } + if (jsB_stacktrace(J, 1)) + js_defproperty(J, -2, "stackTrace", JS_DONTENUM); + return 1; +} + +static void js_newerrorx(js_State *J, const char *message, js_Object *prototype) +{ + js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype)); + js_pushstring(J, message); + js_setproperty(J, -2, "message"); + if (jsB_stacktrace(J, 0)) + js_setproperty(J, -2, "stackTrace"); +} + +#define DERROR(name, Name) \ + static void jsB_##Name(js_State *J) { \ + jsB_ErrorX(J, J->Name##_prototype); \ + } \ + void js_new##name(js_State *J, const char *s) { \ + js_newerrorx(J, s, J->Name##_prototype); \ + } \ + void js_##name(js_State *J, const char *fmt, ...) { \ + va_list ap; \ + char buf[256]; \ + va_start(ap, fmt); \ + vsnprintf(buf, sizeof buf, fmt, ap); \ + va_end(ap); \ + js_newerrorx(J, buf, J->Name##_prototype); \ + js_throw(J); \ + } + +DERROR(error, Error) +DERROR(evalerror, EvalError) +DERROR(rangeerror, RangeError) +DERROR(referenceerror, ReferenceError) +DERROR(syntaxerror, SyntaxError) +DERROR(typeerror, TypeError) +DERROR(urierror, URIError) + +#undef DERROR + +void jsB_initerror(js_State *J) +{ + js_pushobject(J, J->Error_prototype); + { + jsB_props(J, "name", "Error"); + jsB_propf(J, "Error.prototype.toString", Ep_toString, 0); + } + js_newcconstructor(J, jsB_Error, jsB_Error, "Error", 1); + js_defglobal(J, "Error", JS_DONTENUM); + + #define IERROR(NAME) \ + js_pushobject(J, J->NAME##_prototype); \ + jsB_props(J, "name", Q(NAME)); \ + js_newcconstructor(J, jsB_##NAME, jsB_##NAME, Q(NAME), 1); \ + js_defglobal(J, Q(NAME), JS_DONTENUM); + + IERROR(EvalError); + IERROR(RangeError); + IERROR(ReferenceError); + IERROR(SyntaxError); + IERROR(TypeError); + IERROR(URIError); + + #undef IERROR +} diff --git a/mujs/jsfunction.c b/mujs/jsfunction.c new file mode 100644 index 0000000..dea75ac --- /dev/null +++ b/mujs/jsfunction.c @@ -0,0 +1,231 @@ +#include "jsi.h" + +static void jsB_Function(js_State *J) +{ + int i, top = js_gettop(J); + js_Buffer *sb = NULL; + const char *body; + js_Ast *parse; + js_Function *fun; + + if (js_try(J)) { + js_free(J, sb); + jsP_freeparse(J); + js_throw(J); + } + + /* p1, p2, ..., pn */ + if (top > 2) { + for (i = 1; i < top - 1; ++i) { + if (i > 1) + js_putc(J, &sb, ','); + js_puts(J, &sb, js_tostring(J, i)); + } + js_putc(J, &sb, ')'); + js_putc(J, &sb, 0); + } + + /* body */ + body = js_isdefined(J, top - 1) ? js_tostring(J, top - 1) : ""; + + parse = jsP_parsefunction(J, "[string]", sb ? sb->s : NULL, body); + fun = jsC_compilefunction(J, parse); + + js_endtry(J); + js_free(J, sb); + jsP_freeparse(J); + + js_newfunction(J, fun, J->GE); +} + +static void jsB_Function_prototype(js_State *J) +{ + js_pushundefined(J); +} + +static void Fp_toString(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + js_Buffer *sb = NULL; + int i; + + if (!js_iscallable(J, 0)) + js_typeerror(J, "not a function"); + + if (self->type == JS_CFUNCTION || self->type == JS_CSCRIPT) { + js_Function *F = self->u.f.function; + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + + js_puts(J, &sb, "function "); + js_puts(J, &sb, F->name); + js_putc(J, &sb, '('); + for (i = 0; i < F->numparams; ++i) { + if (i > 0) js_putc(J, &sb, ','); + js_puts(J, &sb, F->vartab[i]); + } + js_puts(J, &sb, ") { [byte code] }"); + js_putc(J, &sb, 0); + + js_pushstring(J, sb->s); + js_endtry(J); + js_free(J, sb); + } else if (self->type == JS_CCFUNCTION) { + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + + js_puts(J, &sb, "function "); + js_puts(J, &sb, self->u.c.name); + js_puts(J, &sb, "() { [native code] }"); + js_putc(J, &sb, 0); + + js_pushstring(J, sb->s); + js_endtry(J); + js_free(J, sb); + } else { + js_pushliteral(J, "function () { }"); + } +} + +static void Fp_apply(js_State *J) +{ + int i, n; + + if (!js_iscallable(J, 0)) + js_typeerror(J, "not a function"); + + js_copy(J, 0); + js_copy(J, 1); + + if (js_isnull(J, 2) || js_isundefined(J, 2)) { + n = 0; + } else { + n = js_getlength(J, 2); + if (n < 0) + n = 0; + for (i = 0; i < n; ++i) + js_getindex(J, 2, i); + } + + js_call(J, n); +} + +static void Fp_call(js_State *J) +{ + int i, top = js_gettop(J); + + if (!js_iscallable(J, 0)) + js_typeerror(J, "not a function"); + + for (i = 0; i < top; ++i) + js_copy(J, i); + + js_call(J, top - 2); +} + +static void callbound(js_State *J) +{ + int top = js_gettop(J); + int i, fun, args, n; + + fun = js_gettop(J); + js_currentfunction(J); + js_getproperty(J, fun, "__TargetFunction__"); + js_getproperty(J, fun, "__BoundThis__"); + + args = js_gettop(J); + js_getproperty(J, fun, "__BoundArguments__"); + n = js_getlength(J, args); + if (n < 0) + n = 0; + for (i = 0; i < n; ++i) + js_getindex(J, args, i); + js_remove(J, args); + + for (i = 1; i < top; ++i) + js_copy(J, i); + + js_call(J, n + top - 1); +} + +static void constructbound(js_State *J) +{ + int top = js_gettop(J); + int i, fun, args, n; + + fun = js_gettop(J); + js_currentfunction(J); + js_getproperty(J, fun, "__TargetFunction__"); + + args = js_gettop(J); + js_getproperty(J, fun, "__BoundArguments__"); + n = js_getlength(J, args); + if (n < 0) + n = 0; + for (i = 0; i < n; ++i) + js_getindex(J, args, i); + js_remove(J, args); + + for (i = 1; i < top; ++i) + js_copy(J, i); + + js_construct(J, n + top - 1); +} + +static void Fp_bind(js_State *J) +{ + int i, top = js_gettop(J); + int n; + + if (!js_iscallable(J, 0)) + js_typeerror(J, "not a function"); + + n = js_getlength(J, 0); + if (n > top - 2) + n -= top - 2; + else + n = 0; + + /* Reuse target function's prototype for HasInstance check. */ + js_getproperty(J, 0, "prototype"); + js_newcconstructor(J, callbound, constructbound, "[bind]", n); + + /* target function */ + js_copy(J, 0); + js_defproperty(J, -2, "__TargetFunction__", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + + /* bound this */ + js_copy(J, 1); + js_defproperty(J, -2, "__BoundThis__", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + + /* bound arguments */ + js_newarray(J); + for (i = 2; i < top; ++i) { + js_copy(J, i); + js_setindex(J, -2, i - 2); + } + js_defproperty(J, -2, "__BoundArguments__", JS_READONLY | JS_DONTENUM | JS_DONTCONF); +} + +void jsB_initfunction(js_State *J) +{ + J->Function_prototype->u.c.name = "Function.prototype"; + J->Function_prototype->u.c.function = jsB_Function_prototype; + J->Function_prototype->u.c.constructor = NULL; + J->Function_prototype->u.c.length = 0; + + js_pushobject(J, J->Function_prototype); + { + jsB_propf(J, "Function.prototype.toString", Fp_toString, 2); + jsB_propf(J, "Function.prototype.apply", Fp_apply, 2); + jsB_propf(J, "Function.prototype.call", Fp_call, 1); + jsB_propf(J, "Function.prototype.bind", Fp_bind, 1); + } + js_newcconstructor(J, jsB_Function, jsB_Function, "Function", 1); + js_defglobal(J, "Function", JS_DONTENUM); +} diff --git a/mujs/jsgc.c b/mujs/jsgc.c new file mode 100644 index 0000000..cd0f74d --- /dev/null +++ b/mujs/jsgc.c @@ -0,0 +1,284 @@ +#include "jsi.h" +#include "regexp.h" + +static void jsG_freeenvironment(js_State *J, js_Environment *env) +{ + js_free(J, env); +} + +static void jsG_freefunction(js_State *J, js_Function *fun) +{ + js_free(J, fun->funtab); + js_free(J, fun->vartab); + js_free(J, fun->code); + js_free(J, fun); +} + +static void jsG_freeproperty(js_State *J, js_Property *node) +{ + if (node->left->level) jsG_freeproperty(J, node->left); + if (node->right->level) jsG_freeproperty(J, node->right); + js_free(J, node); +} + +static void jsG_freeiterator(js_State *J, js_Iterator *node) +{ + while (node) { + js_Iterator *next = node->next; + js_free(J, node); + node = next; + } +} + +static void jsG_freeobject(js_State *J, js_Object *obj) +{ + if (obj->properties->level) + jsG_freeproperty(J, obj->properties); + if (obj->type == JS_CREGEXP) { + js_free(J, obj->u.r.source); + js_regfreex(J->alloc, J->actx, obj->u.r.prog); + } + if (obj->type == JS_CSTRING) { + if (obj->u.s.string != obj->u.s.shrstr) + js_free(J, obj->u.s.string); + } + if (obj->type == JS_CARRAY && obj->u.a.simple) + js_free(J, obj->u.a.array); + if (obj->type == JS_CITERATOR) + jsG_freeiterator(J, obj->u.iter.head); + if (obj->type == JS_CUSERDATA && obj->u.user.finalize) + obj->u.user.finalize(J, obj->u.user.data); + if (obj->type == JS_CCFUNCTION && obj->u.c.finalize) + obj->u.c.finalize(J, obj->u.c.data); + js_free(J, obj); +} + +/* Mark and add object to scan queue */ +static void jsG_markobject(js_State *J, int mark, js_Object *obj) +{ + obj->gcmark = mark; + obj->gcroot = J->gcroot; + J->gcroot = obj; +} + +static void jsG_markfunction(js_State *J, int mark, js_Function *fun) +{ + int i; + fun->gcmark = mark; + for (i = 0; i < fun->funlen; ++i) + if (fun->funtab[i]->gcmark != mark) + jsG_markfunction(J, mark, fun->funtab[i]); +} + +static void jsG_markenvironment(js_State *J, int mark, js_Environment *env) +{ + do { + env->gcmark = mark; + if (env->variables->gcmark != mark) + jsG_markobject(J, mark, env->variables); + env = env->outer; + } while (env && env->gcmark != mark); +} + +static void jsG_markproperty(js_State *J, int mark, js_Property *node) +{ + if (node->left->level) jsG_markproperty(J, mark, node->left); + if (node->right->level) jsG_markproperty(J, mark, node->right); + + if (node->value.t.type == JS_TMEMSTR && node->value.u.memstr->gcmark != mark) + node->value.u.memstr->gcmark = mark; + if (node->value.t.type == JS_TOBJECT && node->value.u.object->gcmark != mark) + jsG_markobject(J, mark, node->value.u.object); + if (node->getter && node->getter->gcmark != mark) + jsG_markobject(J, mark, node->getter); + if (node->setter && node->setter->gcmark != mark) + jsG_markobject(J, mark, node->setter); +} + +/* Mark everything the object can reach. */ +static void jsG_scanobject(js_State *J, int mark, js_Object *obj) +{ + if (obj->properties->level) + jsG_markproperty(J, mark, obj->properties); + if (obj->prototype && obj->prototype->gcmark != mark) + jsG_markobject(J, mark, obj->prototype); + if (obj->type == JS_CARRAY && obj->u.a.simple) { + int i; + for (i = 0; i < obj->u.a.flat_length; ++i) { + js_Value *v = &obj->u.a.array[i]; + if (v->t.type == JS_TMEMSTR && v->u.memstr->gcmark != mark) + v->u.memstr->gcmark = mark; + if (v->t.type == JS_TOBJECT && v->u.object->gcmark != mark) + jsG_markobject(J, mark, v->u.object); + } + } + if (obj->type == JS_CITERATOR && obj->u.iter.target->gcmark != mark) { + jsG_markobject(J, mark, obj->u.iter.target); + } + if (obj->type == JS_CFUNCTION || obj->type == JS_CSCRIPT) { + if (obj->u.f.scope && obj->u.f.scope->gcmark != mark) + jsG_markenvironment(J, mark, obj->u.f.scope); + if (obj->u.f.function && obj->u.f.function->gcmark != mark) + jsG_markfunction(J, mark, obj->u.f.function); + } +} + +static void jsG_markstack(js_State *J, int mark) +{ + js_Value *v = J->stack; + int n = J->top; + while (n--) { + if (v->t.type == JS_TMEMSTR && v->u.memstr->gcmark != mark) + v->u.memstr->gcmark = mark; + if (v->t.type == JS_TOBJECT && v->u.object->gcmark != mark) + jsG_markobject(J, mark, v->u.object); + ++v; + } +} + +void js_gc(js_State *J, int report) +{ + js_Function *fun, *nextfun, **prevnextfun; + js_Object *obj, *nextobj, **prevnextobj; + js_String *str, *nextstr, **prevnextstr; + js_Environment *env, *nextenv, **prevnextenv; + unsigned int nenv = 0, nfun = 0, nobj = 0, nstr = 0, nprop = 0; + unsigned int genv = 0, gfun = 0, gobj = 0, gstr = 0, gprop = 0; + int mark; + int i; + + mark = J->gcmark = J->gcmark == 1 ? 2 : 1; + + /* Add initial roots. */ + + jsG_markobject(J, mark, J->Object_prototype); + jsG_markobject(J, mark, J->Array_prototype); + jsG_markobject(J, mark, J->Function_prototype); + jsG_markobject(J, mark, J->Boolean_prototype); + jsG_markobject(J, mark, J->Number_prototype); + jsG_markobject(J, mark, J->String_prototype); + jsG_markobject(J, mark, J->RegExp_prototype); + jsG_markobject(J, mark, J->Date_prototype); + + jsG_markobject(J, mark, J->Error_prototype); + jsG_markobject(J, mark, J->EvalError_prototype); + jsG_markobject(J, mark, J->RangeError_prototype); + jsG_markobject(J, mark, J->ReferenceError_prototype); + jsG_markobject(J, mark, J->SyntaxError_prototype); + jsG_markobject(J, mark, J->TypeError_prototype); + jsG_markobject(J, mark, J->URIError_prototype); + + jsG_markobject(J, mark, J->R); + jsG_markobject(J, mark, J->G); + + jsG_markstack(J, mark); + + jsG_markenvironment(J, mark, J->E); + jsG_markenvironment(J, mark, J->GE); + for (i = 0; i < J->envtop; ++i) + jsG_markenvironment(J, mark, J->envstack[i]); + + /* Scan objects until none remain. */ + + while ((obj = J->gcroot) != NULL) { + J->gcroot = obj->gcroot; + obj->gcroot = NULL; + jsG_scanobject(J, mark, obj); + } + + /* Free everything not marked. */ + + prevnextenv = &J->gcenv; + for (env = J->gcenv; env; env = nextenv) { + nextenv = env->gcnext; + if (env->gcmark != mark) { + *prevnextenv = nextenv; + jsG_freeenvironment(J, env); + ++genv; + } else { + prevnextenv = &env->gcnext; + } + ++nenv; + } + + prevnextfun = &J->gcfun; + for (fun = J->gcfun; fun; fun = nextfun) { + nextfun = fun->gcnext; + if (fun->gcmark != mark) { + *prevnextfun = nextfun; + jsG_freefunction(J, fun); + ++gfun; + } else { + prevnextfun = &fun->gcnext; + } + ++nfun; + } + + prevnextobj = &J->gcobj; + for (obj = J->gcobj; obj; obj = nextobj) { + nprop += obj->count; + nextobj = obj->gcnext; + if (obj->gcmark != mark) { + gprop += obj->count; + *prevnextobj = nextobj; + jsG_freeobject(J, obj); + ++gobj; + } else { + prevnextobj = &obj->gcnext; + } + ++nobj; + } + + prevnextstr = &J->gcstr; + for (str = J->gcstr; str; str = nextstr) { + nextstr = str->gcnext; + if (str->gcmark != mark) { + *prevnextstr = nextstr; + js_free(J, str); + ++gstr; + } else { + prevnextstr = &str->gcnext; + } + ++nstr; + } + + unsigned int ntot = nenv + nfun + nobj + nstr + nprop; + unsigned int gtot = genv + gfun + gobj + gstr + gprop; + unsigned int remaining = ntot - gtot; + + J->gccounter = remaining; + J->gcthresh = remaining * JS_GCFACTOR; + + if (report) { + char buf[256]; + snprintf(buf, sizeof buf, "garbage collected (%d%%): %d/%d envs, %d/%d funs, %d/%d objs, %d/%d props, %d/%d strs", + 100*gtot/ntot, genv, nenv, gfun, nfun, gobj, nobj, gprop, nprop, gstr, nstr); + js_report(J, buf); + } +} + +void js_freestate(js_State *J) +{ + js_Function *fun, *nextfun; + js_Object *obj, *nextobj; + js_Environment *env, *nextenv; + js_String *str, *nextstr; + + if (!J) + return; + + for (env = J->gcenv; env; env = nextenv) + nextenv = env->gcnext, jsG_freeenvironment(J, env); + for (fun = J->gcfun; fun; fun = nextfun) + nextfun = fun->gcnext, jsG_freefunction(J, fun); + for (obj = J->gcobj; obj; obj = nextobj) + nextobj = obj->gcnext, jsG_freeobject(J, obj); + for (str = J->gcstr; str; str = nextstr) + nextstr = str->gcnext, js_free(J, str); + + jsS_freestrings(J); + + js_free(J, J->lexbuf.text); + J->alloc(J->actx, J->stack, 0); + J->alloc(J->actx, J, 0); +} diff --git a/mujs/jsi.h b/mujs/jsi.h new file mode 100644 index 0000000..6956d97 --- /dev/null +++ b/mujs/jsi.h @@ -0,0 +1,867 @@ +#ifndef jsi_h +#define jsi_h + +#include "mujs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* NOTE: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103052 */ +#ifdef __GNUC__ +#if (__GNUC__ >= 6) +#pragma GCC optimize ("no-ipa-pure-const") +#endif +#endif + +/* Microsoft Visual C */ +#ifdef _MSC_VER +#pragma warning(disable:4996) /* _CRT_SECURE_NO_WARNINGS */ +#pragma warning(disable:4244) /* implicit conversion from double to int */ +#pragma warning(disable:4267) /* implicit conversion of int to smaller int */ +#pragma warning(disable:4090) /* broken const warnings */ +#define inline __inline +#if _MSC_VER < 1900 /* MSVC 2015 */ +#define snprintf jsW_snprintf +#define vsnprintf jsW_vsnprintf +static int jsW_vsnprintf(char *str, size_t size, const char *fmt, va_list ap) +{ + int n; + n = _vsnprintf(str, size, fmt, ap); + str[size-1] = 0; + return n; +} +static int jsW_snprintf(char *str, size_t size, const char *fmt, ...) +{ + int n; + va_list ap; + va_start(ap, fmt); + n = jsW_vsnprintf(str, size, fmt, ap); + va_end(ap); + return n; +} +#endif +#if _MSC_VER <= 1700 /* <= MSVC 2012 */ +#define isnan(x) _isnan(x) +#define isinf(x) (!_finite(x)) +#define isfinite(x) _finite(x) +static __inline int signbit(double x) { __int64 i; memcpy(&i, &x, 8); return i>>63; } +#define INFINITY (DBL_MAX+DBL_MAX) +#define NAN (INFINITY-INFINITY) +#endif +#endif + +#define soffsetof(x,y) ((int)offsetof(x,y)) +#define nelem(a) (int)(sizeof (a) / sizeof (a)[0]) + +void *js_malloc(js_State *J, int size); +void *js_realloc(js_State *J, void *ptr, int size); +void js_free(js_State *J, void *ptr); + +typedef union js_Value js_Value; +typedef struct js_Regexp js_Regexp; +typedef struct js_Object js_Object; +typedef struct js_String js_String; +typedef struct js_Ast js_Ast; +typedef struct js_Function js_Function; +typedef struct js_Environment js_Environment; +typedef struct js_StringNode js_StringNode; +typedef struct js_Jumpbuf js_Jumpbuf; +typedef struct js_StackTrace js_StackTrace; + +/* Limits */ + +#ifndef JS_STACKSIZE +#define JS_STACKSIZE 4096 /* value stack size */ +#endif +#ifndef JS_ENVLIMIT +#define JS_ENVLIMIT 1024 /* environment stack size */ +#endif +#ifndef JS_TRYLIMIT +#define JS_TRYLIMIT 64 /* exception stack size */ +#endif + +#ifndef JS_ARRAYLIMIT +#define JS_ARRAYLIMIT (1<<26) /* limit arrays to 64M entries (1G of flat array data) */ +#endif + +#ifndef JS_GCFACTOR +/* + * GC will try to trigger when memory usage is this value times the minimum + * needed memory. E.g. if there are 100 remaining objects after GC and this + * value is 5.0, then the next GC will trigger when the overall number is 500. + * I.e. a value of 5.0 aims at 80% garbage, 20% remain-used on each GC. + * The bigger the value the less impact GC has on overall performance, but more + * memory is used and individual GC pauses are longer (but fewer). + */ +#define JS_GCFACTOR 5.0 /* memory overhead factor >= 1.0 */ +#endif + +#ifndef JS_ASTLIMIT +#define JS_ASTLIMIT 400 /* max nested expressions */ +#endif + +#ifndef JS_STRLIMIT +#define JS_STRLIMIT (1<<28) /* max string length */ +#endif + +/* instruction size -- change to int if you get integer overflow syntax errors */ + +#ifdef JS_INSTRUCTION +typedef JS_INSTRUCTION js_Instruction; +#else +typedef unsigned short js_Instruction; +#endif + +/* String interning */ + +char *js_strdup(js_State *J, const char *s); +const char *js_intern(js_State *J, const char *s); +void jsS_dumpstrings(js_State *J); +void jsS_freestrings(js_State *J); + +/* Portable strtod and printf float formatting */ + +void js_fmtexp(char *p, int e); +int js_grisu2(double v, char *buffer, int *K); +double js_strtod(const char *as, char **aas); + +double js_strtol(const char *s, char **ep, int radix); + +/* Private stack functions */ + +void js_newarguments(js_State *J); +void js_newfunction(js_State *J, js_Function *function, js_Environment *scope); +void js_newscript(js_State *J, js_Function *function, js_Environment *scope); +void js_loadeval(js_State *J, const char *filename, const char *source); + +js_Regexp *js_toregexp(js_State *J, int idx); +int js_isarrayindex(js_State *J, const char *str, int *idx); +int js_runeat(js_State *J, const char *s, int i); +int js_utflen(const char *s); +int js_utfptrtoidx(const char *s, const char *p); + +void js_dup(js_State *J); +void js_dup2(js_State *J); +void js_rot2(js_State *J); +void js_rot3(js_State *J); +void js_rot4(js_State *J); +void js_rot2pop1(js_State *J); +void js_rot3pop2(js_State *J); +void js_dup1rot3(js_State *J); +void js_dup1rot4(js_State *J); + +void js_RegExp_prototype_exec(js_State *J, js_Regexp *re, const char *text); + +void js_trap(js_State *J, int pc); /* dump stack and environment to stdout */ + +struct js_StackTrace +{ + const char *name; + const char *file; + int line; +}; + +/* Exception handling */ + +struct js_Jumpbuf +{ + jmp_buf buf; + js_Environment *E; + int envtop; + int tracetop; + int top, bot; + int strict; + js_Instruction *pc; +}; + +void *js_savetrypc(js_State *J, js_Instruction *pc); + +#define js_trypc(J, PC) \ + setjmp(js_savetrypc(J, PC)) + +/* String buffer */ + +typedef struct js_Buffer { int n, m; char s[64]; } js_Buffer; + +void js_putc(js_State *J, js_Buffer **sbp, int c); +void js_puts(js_State *J, js_Buffer **sb, const char *s); +void js_putm(js_State *J, js_Buffer **sb, const char *s, const char *e); + +/* State struct */ + +struct js_State +{ + void *actx; + void *uctx; + js_Alloc alloc; + js_Report report; + js_Panic panic; + + js_StringNode *strings; + + int default_strict; + int strict; + + /* parser input source */ + const char *filename; + const char *source; + int line; + + /* lexer state */ + struct { char *text; int len, cap; } lexbuf; + int lexline; + int lexchar; + int lasttoken; + int newline; + + /* parser state */ + int astdepth; + int lookahead; + const char *text; + double number; + js_Ast *gcast; /* list of allocated nodes to free after parsing */ + + /* runtime environment */ + js_Object *Object_prototype; + js_Object *Array_prototype; + js_Object *Function_prototype; + js_Object *Boolean_prototype; + js_Object *Number_prototype; + js_Object *String_prototype; + js_Object *RegExp_prototype; + js_Object *Date_prototype; + + js_Object *Error_prototype; + js_Object *EvalError_prototype; + js_Object *RangeError_prototype; + js_Object *ReferenceError_prototype; + js_Object *SyntaxError_prototype; + js_Object *TypeError_prototype; + js_Object *URIError_prototype; + + unsigned int seed; /* Math.random seed */ + + char scratch[12]; /* scratch buffer for iterating over array indices */ + + int nextref; /* for js_ref use */ + js_Object *R; /* registry of hidden values */ + js_Object *G; /* the global object */ + js_Environment *E; /* current environment scope */ + js_Environment *GE; /* global environment scope (at the root) */ + + /* execution stack */ + int top, bot; + js_Value *stack; + + /* garbage collector list */ + int gcmark; + unsigned int gccounter, gcthresh; + js_Environment *gcenv; + js_Function *gcfun; + js_Object *gcobj; + js_String *gcstr; + + js_Object *gcroot; /* gc scan list */ + + /* environments on the call stack but currently not in scope */ + int envtop; + js_Environment *envstack[JS_ENVLIMIT]; + + /* debug info stack trace */ + int tracetop; + js_StackTrace trace[JS_ENVLIMIT]; + + /* exception stack */ + int trytop; + js_Jumpbuf trybuf[JS_TRYLIMIT]; +}; + +/* Values */ + +typedef struct js_Property js_Property; +typedef struct js_Iterator js_Iterator; + +/* Hint to ToPrimitive() */ +enum { + JS_HNONE, + JS_HNUMBER, + JS_HSTRING +}; + +enum js_Type { + JS_TSHRSTR, /* type tag doubles as string zero-terminator */ + JS_TUNDEFINED, + JS_TNULL, + JS_TBOOLEAN, + JS_TNUMBER, + JS_TLITSTR, + JS_TMEMSTR, + JS_TOBJECT, +}; + +enum js_Class { + JS_COBJECT, + JS_CARRAY, + JS_CFUNCTION, + JS_CSCRIPT, /* function created from global/eval code */ + JS_CCFUNCTION, /* built-in function */ + JS_CERROR, + JS_CBOOLEAN, + JS_CNUMBER, + JS_CSTRING, + JS_CREGEXP, + JS_CDATE, + JS_CMATH, + JS_CJSON, + JS_CARGUMENTS, + JS_CITERATOR, + JS_CUSERDATA, +}; + +/* + Short strings abuse the js_Value struct. By putting the type tag in the + last byte, and using 0 as the tag for short strings, we can use the + entire js_Value as string storage by letting the type tag serve double + purpose as the string zero terminator. +*/ + +union js_Value +{ + struct { + char pad[15]; + char type; /* type tag overlaps with final byte of shrstr */ + } t; + union { + char shrstr[16]; + int boolean; + double number; + const char *litstr; + js_String *memstr; + js_Object *object; + } u; +}; + +struct js_String +{ + js_String *gcnext; + char gcmark; + char p[1]; +}; + +struct js_Regexp +{ + void *prog; + char *source; + unsigned short flags; + unsigned short last; +}; + +struct js_Object +{ + enum js_Class type; + int extensible; + js_Property *properties; + int count; /* number of properties, for array sparseness check */ + js_Object *prototype; + union { + int boolean; + double number; + struct { + int length; + char *string; + char shrstr[16]; + } s; + struct { + int length; /* actual length */ + int simple; /* true if array has only non-sparse array properties */ + int flat_length; /* used length of simple array part */ + int flat_capacity; /* allocated length of simple array part */ + js_Value *array; + } a; + struct { + js_Function *function; + js_Environment *scope; + } f; + struct { + const char *name; + js_CFunction function; + js_CFunction constructor; + int length; + void *data; + js_Finalize finalize; + } c; + js_Regexp r; + struct { + js_Object *target; + int i, n; /* for array part */ + js_Iterator *head, *current; /* for object part */ + } iter; + struct { + const char *tag; + void *data; + js_HasProperty has; + js_Put put; + js_Delete delete; + js_Finalize finalize; + } user; + } u; + js_Object *gcnext; /* allocation list */ + js_Object *gcroot; /* scan list */ + int gcmark; +}; + +struct js_Property +{ + js_Property *left, *right; + int level; + int atts; + js_Value value; + js_Object *getter; + js_Object *setter; + char name[1]; +}; + +struct js_Iterator +{ + js_Iterator *next; + char name[1]; +}; + +struct js_Environment +{ + js_Environment *outer; + js_Object *variables; + + js_Environment *gcnext; + int gcmark; +}; + +/* jsrun.c */ +js_Environment *jsR_newenvironment(js_State *J, js_Object *variables, js_Environment *outer); +js_String *jsV_newmemstring(js_State *J, const char *s, int n); +js_Value *js_tovalue(js_State *J, int idx); +void js_toprimitive(js_State *J, int idx, int hint); +js_Object *js_toobject(js_State *J, int idx); +void js_pushvalue(js_State *J, js_Value v); +void js_pushobject(js_State *J, js_Object *v); +void jsR_unflattenarray(js_State *J, js_Object *obj); + +/* jsvalue.c */ +int jsV_toboolean(js_State *J, js_Value *v); +double jsV_tonumber(js_State *J, js_Value *v); +double jsV_tointeger(js_State *J, js_Value *v); +const char *jsV_tostring(js_State *J, js_Value *v); +js_Object *jsV_toobject(js_State *J, js_Value *v); +void jsV_toprimitive(js_State *J, js_Value *v, int preferred); + +const char *js_itoa(char *buf, int a); +double js_stringtofloat(const char *s, char **ep); +int jsV_numbertointeger(double n); +int jsV_numbertoint32(double n); +unsigned int jsV_numbertouint32(double n); +short jsV_numbertoint16(double n); +unsigned short jsV_numbertouint16(double n); +const char *jsV_numbertostring(js_State *J, char buf[32], double number); +double jsV_stringtonumber(js_State *J, const char *string); + +/* jsproperty.c */ +js_Object *jsV_newobject(js_State *J, enum js_Class type, js_Object *prototype); +js_Property *jsV_getownproperty(js_State *J, js_Object *obj, const char *name); +js_Property *jsV_getpropertyx(js_State *J, js_Object *obj, const char *name, int *own); +js_Property *jsV_getproperty(js_State *J, js_Object *obj, const char *name); +js_Property *jsV_setproperty(js_State *J, js_Object *obj, const char *name); +js_Property *jsV_nextproperty(js_State *J, js_Object *obj, const char *name); +void jsV_delproperty(js_State *J, js_Object *obj, const char *name); + +js_Object *jsV_newiterator(js_State *J, js_Object *obj, int own); +const char *jsV_nextiterator(js_State *J, js_Object *iter); + +void jsV_resizearray(js_State *J, js_Object *obj, int newlen); + +void jsV_unflattenarray(js_State *J, js_Object *obj); +void jsV_growarray(js_State *J, js_Object *obj); + +/* Lexer */ + +enum +{ + TK_IDENTIFIER = 256, + TK_NUMBER, + TK_STRING, + TK_REGEXP, + + /* multi-character punctuators */ + TK_LE, + TK_GE, + TK_EQ, + TK_NE, + TK_STRICTEQ, + TK_STRICTNE, + TK_SHL, + TK_SHR, + TK_USHR, + TK_AND, + TK_OR, + TK_ADD_ASS, + TK_SUB_ASS, + TK_MUL_ASS, + TK_DIV_ASS, + TK_MOD_ASS, + TK_SHL_ASS, + TK_SHR_ASS, + TK_USHR_ASS, + TK_AND_ASS, + TK_OR_ASS, + TK_XOR_ASS, + TK_INC, + TK_DEC, + + /* keywords */ + TK_BREAK, + TK_CASE, + TK_CATCH, + TK_CONTINUE, + TK_DEBUGGER, + TK_DEFAULT, + TK_DELETE, + TK_DO, + TK_ELSE, + TK_FALSE, + TK_FINALLY, + TK_FOR, + TK_FUNCTION, + TK_IF, + TK_IN, + TK_INSTANCEOF, + TK_NEW, + TK_NULL, + TK_RETURN, + TK_SWITCH, + TK_THIS, + TK_THROW, + TK_TRUE, + TK_TRY, + TK_TYPEOF, + TK_VAR, + TK_VOID, + TK_WHILE, + TK_WITH, +}; + +int jsY_iswhite(int c); +int jsY_isnewline(int c); +int jsY_ishex(int c); +int jsY_tohex(int c); + +const char *jsY_tokenstring(int token); +int jsY_findword(const char *s, const char **list, int num); + +void jsY_initlex(js_State *J, const char *filename, const char *source); +int jsY_lex(js_State *J); +int jsY_lexjson(js_State *J); + +/* Parser */ + +enum js_AstType +{ + AST_LIST, + AST_FUNDEC, + AST_IDENTIFIER, + + EXP_IDENTIFIER, + EXP_NUMBER, + EXP_STRING, + EXP_REGEXP, + + /* literals */ + EXP_ELISION, /* for array elisions */ + EXP_NULL, + EXP_TRUE, + EXP_FALSE, + EXP_THIS, + + EXP_ARRAY, + EXP_OBJECT, + EXP_PROP_VAL, + EXP_PROP_GET, + EXP_PROP_SET, + + EXP_FUN, + + /* expressions */ + EXP_INDEX, + EXP_MEMBER, + EXP_CALL, + EXP_NEW, + + EXP_POSTINC, + EXP_POSTDEC, + + EXP_DELETE, + EXP_VOID, + EXP_TYPEOF, + EXP_PREINC, + EXP_PREDEC, + EXP_POS, + EXP_NEG, + EXP_BITNOT, + EXP_LOGNOT, + + EXP_MOD, + EXP_DIV, + EXP_MUL, + EXP_SUB, + EXP_ADD, + EXP_USHR, + EXP_SHR, + EXP_SHL, + EXP_IN, + EXP_INSTANCEOF, + EXP_GE, + EXP_LE, + EXP_GT, + EXP_LT, + EXP_STRICTNE, + EXP_STRICTEQ, + EXP_NE, + EXP_EQ, + EXP_BITAND, + EXP_BITXOR, + EXP_BITOR, + EXP_LOGAND, + EXP_LOGOR, + + EXP_COND, + + EXP_ASS, + EXP_ASS_MUL, + EXP_ASS_DIV, + EXP_ASS_MOD, + EXP_ASS_ADD, + EXP_ASS_SUB, + EXP_ASS_SHL, + EXP_ASS_SHR, + EXP_ASS_USHR, + EXP_ASS_BITAND, + EXP_ASS_BITXOR, + EXP_ASS_BITOR, + + EXP_COMMA, + + EXP_VAR, /* var initializer */ + + /* statements */ + STM_BLOCK, + STM_EMPTY, + STM_VAR, + STM_IF, + STM_DO, + STM_WHILE, + STM_FOR, + STM_FOR_VAR, + STM_FOR_IN, + STM_FOR_IN_VAR, + STM_CONTINUE, + STM_BREAK, + STM_RETURN, + STM_WITH, + STM_SWITCH, + STM_THROW, + STM_TRY, + STM_DEBUGGER, + + STM_LABEL, + STM_CASE, + STM_DEFAULT, +}; + +typedef struct js_JumpList js_JumpList; + +struct js_JumpList +{ + enum js_AstType type; + int inst; + js_JumpList *next; +}; + +struct js_Ast +{ + enum js_AstType type; + int line; + js_Ast *parent, *a, *b, *c, *d; + double number; + const char *string; + js_JumpList *jumps; /* list of break/continue jumps to patch */ + int casejump; /* for switch case clauses */ + js_Ast *gcnext; /* next in alloc list */ +}; + +js_Ast *jsP_parsefunction(js_State *J, const char *filename, const char *params, const char *body); +js_Ast *jsP_parse(js_State *J, const char *filename, const char *source); +void jsP_freeparse(js_State *J); + +/* Compiler */ + +enum js_OpCode +{ + OP_POP, /* A -- */ + OP_DUP, /* A -- A A */ + OP_DUP2, /* A B -- A B A B */ + OP_ROT2, /* A B -- B A */ + OP_ROT3, /* A B C -- C A B */ + OP_ROT4, /* A B C D -- D A B C */ + + OP_INTEGER, /* -K- (number-32768) */ + OP_NUMBER, /* -N- */ + OP_STRING, /* -S- */ + OP_CLOSURE, /* -F- */ + + OP_NEWARRAY, + OP_NEWOBJECT, + OP_NEWREGEXP, /* -S,opts- */ + + OP_UNDEF, + OP_NULL, + OP_TRUE, + OP_FALSE, + + OP_THIS, + OP_CURRENT, /* currently executing function object */ + + OP_GETLOCAL, /* -K- */ + OP_SETLOCAL, /* -K- */ + OP_DELLOCAL, /* -K- false */ + + OP_HASVAR, /* -S- ( | undefined ) */ + OP_GETVAR, /* -S- */ + OP_SETVAR, /* -S- */ + OP_DELVAR, /* -S- */ + + OP_IN, /* -- */ + + OP_SKIPARRAY, /* -- */ + OP_INITARRAY, /* -- */ + OP_INITPROP, /* -- */ + OP_INITGETTER, /* -- */ + OP_INITSETTER, /* -- */ + + OP_GETPROP, /* -- */ + OP_GETPROP_S, /* -S- */ + OP_SETPROP, /* -- */ + OP_SETPROP_S, /* -S- */ + OP_DELPROP, /* -- */ + OP_DELPROP_S, /* -S- */ + + OP_ITERATOR, /* -- */ + OP_NEXTITER, /* -- ( true | false ) */ + + OP_EVAL, /* -(numargs)- */ + OP_CALL, /* -(numargs)- */ + OP_NEW, /* -(numargs)- */ + + OP_TYPEOF, + OP_POS, + OP_NEG, + OP_BITNOT, + OP_LOGNOT, + OP_INC, /* -- ToNumber(x)+1 */ + OP_DEC, /* -- ToNumber(x)-1 */ + OP_POSTINC, /* -- ToNumber(x)+1 ToNumber(x) */ + OP_POSTDEC, /* -- ToNumber(x)-1 ToNumber(x) */ + + OP_MUL, + OP_DIV, + OP_MOD, + OP_ADD, + OP_SUB, + OP_SHL, + OP_SHR, + OP_USHR, + OP_LT, + OP_GT, + OP_LE, + OP_GE, + OP_EQ, + OP_NE, + OP_STRICTEQ, + OP_STRICTNE, + OP_JCASE, + OP_BITAND, + OP_BITXOR, + OP_BITOR, + + OP_INSTANCEOF, + + OP_THROW, + + OP_TRY, /* -ADDR- /jump/ or -ADDR- */ + OP_ENDTRY, + + OP_CATCH, /* push scope chain with exception variable */ + OP_ENDCATCH, + + OP_WITH, + OP_ENDWITH, + + OP_DEBUGGER, + OP_JUMP, + OP_JTRUE, + OP_JFALSE, + OP_RETURN, +}; + +struct js_Function +{ + const char *name; + int script; + int lightweight; + int strict; + int arguments; + int numparams; + + js_Instruction *code; + int codecap, codelen; + + js_Function **funtab; + int funcap, funlen; + + const char **vartab; + int varcap, varlen; + + const char *filename; + int line, lastline; + + js_Function *gcnext; + int gcmark; +}; + +js_Function *jsC_compilefunction(js_State *J, js_Ast *prog); +js_Function *jsC_compilescript(js_State *J, js_Ast *prog, int default_strict); + +/* Builtins */ + +void jsB_init(js_State *J); +void jsB_initobject(js_State *J); +void jsB_initarray(js_State *J); +void jsB_initfunction(js_State *J); +void jsB_initboolean(js_State *J); +void jsB_initnumber(js_State *J); +void jsB_initstring(js_State *J); +void jsB_initregexp(js_State *J); +void jsB_initerror(js_State *J); +void jsB_initmath(js_State *J); +void jsB_initjson(js_State *J); +void jsB_initdate(js_State *J); + +void jsB_propf(js_State *J, const char *name, js_CFunction cfun, int n); +void jsB_propn(js_State *J, const char *name, double number); +void jsB_props(js_State *J, const char *name, const char *string); + +#endif diff --git a/mujs/jsintern.c b/mujs/jsintern.c new file mode 100644 index 0000000..42e6984 --- /dev/null +++ b/mujs/jsintern.c @@ -0,0 +1,137 @@ +#include "jsi.h" + +/* Dynamically grown string buffer */ + +void js_putc(js_State *J, js_Buffer **sbp, int c) +{ + js_Buffer *sb = *sbp; + if (!sb) { + sb = js_malloc(J, sizeof *sb); + sb->n = 0; + sb->m = sizeof sb->s; + *sbp = sb; + } else if (sb->n == sb->m) { + sb = js_realloc(J, sb, (sb->m *= 2) + soffsetof(js_Buffer, s)); + *sbp = sb; + } + sb->s[sb->n++] = c; +} + +void js_puts(js_State *J, js_Buffer **sb, const char *s) +{ + while (*s) + js_putc(J, sb, *s++); +} + +void js_putm(js_State *J, js_Buffer **sb, const char *s, const char *e) +{ + while (s < e) + js_putc(J, sb, *s++); +} + +/* Use an AA-tree to quickly look up interned strings. */ + +struct js_StringNode +{ + js_StringNode *left, *right; + int level; + char string[1]; +}; + +static js_StringNode jsS_sentinel = { &jsS_sentinel, &jsS_sentinel, 0, ""}; + +static js_StringNode *jsS_newstringnode(js_State *J, const char *string, const char **result) +{ + size_t n = strlen(string); + if (n > JS_STRLIMIT) + js_rangeerror(J, "invalid string length"); + js_StringNode *node = js_malloc(J, soffsetof(js_StringNode, string) + n + 1); + node->left = node->right = &jsS_sentinel; + node->level = 1; + memcpy(node->string, string, n + 1); + return *result = node->string, node; +} + +static js_StringNode *jsS_skew(js_StringNode *node) +{ + if (node->left->level == node->level) { + js_StringNode *temp = node; + node = node->left; + temp->left = node->right; + node->right = temp; + } + return node; +} + +static js_StringNode *jsS_split(js_StringNode *node) +{ + if (node->right->right->level == node->level) { + js_StringNode *temp = node; + node = node->right; + temp->right = node->left; + node->left = temp; + ++node->level; + } + return node; +} + +static js_StringNode *jsS_insert(js_State *J, js_StringNode *node, const char *string, const char **result) +{ + if (node != &jsS_sentinel) { + int c = strcmp(string, node->string); + if (c < 0) + node->left = jsS_insert(J, node->left, string, result); + else if (c > 0) + node->right = jsS_insert(J, node->right, string, result); + else + return *result = node->string, node; + node = jsS_skew(node); + node = jsS_split(node); + return node; + } + return jsS_newstringnode(J, string, result); +} + +static void dumpstringnode(js_StringNode *node, int level) +{ + int i; + if (node->left != &jsS_sentinel) + dumpstringnode(node->left, level + 1); + printf("%d: ", node->level); + for (i = 0; i < level; ++i) + putchar('\t'); + printf("'%s'\n", node->string); + if (node->right != &jsS_sentinel) + dumpstringnode(node->right, level + 1); +} + +void jsS_dumpstrings(js_State *J) +{ + js_StringNode *root = J->strings; + printf("interned strings {\n"); + if (root && root != &jsS_sentinel) + dumpstringnode(root, 1); + printf("}\n"); +} + +static void jsS_freestringnode(js_State *J, js_StringNode *node) +{ + if (node->left != &jsS_sentinel) jsS_freestringnode(J, node->left); + if (node->right != &jsS_sentinel) jsS_freestringnode(J, node->right); + js_free(J, node); +} + +void jsS_freestrings(js_State *J) +{ + if (J->strings && J->strings != &jsS_sentinel) + jsS_freestringnode(J, J->strings); +} + +const char *js_intern(js_State *J, const char *s) +{ + const char *result; + if (!J->strings) + J->strings = &jsS_sentinel; + J->strings = jsS_insert(J, J->strings, s, &result); + return result; +} diff --git a/mujs/jslex.c b/mujs/jslex.c new file mode 100644 index 0000000..6e3e298 --- /dev/null +++ b/mujs/jslex.c @@ -0,0 +1,878 @@ +#include "jsi.h" +#include "utf.h" + +JS_NORETURN static void jsY_error(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); + +static void jsY_error(js_State *J, const char *fmt, ...) +{ + va_list ap; + char buf[512]; + char msgbuf[256]; + + va_start(ap, fmt); + vsnprintf(msgbuf, 256, fmt, ap); + va_end(ap); + + snprintf(buf, 256, "%s:%d: ", J->filename, J->lexline); + strcat(buf, msgbuf); + + js_newsyntaxerror(J, buf); + js_throw(J); +} + +static const char *tokenstring[] = { + "(end-of-file)", + "'\\x01'", "'\\x02'", "'\\x03'", "'\\x04'", "'\\x05'", "'\\x06'", "'\\x07'", + "'\\x08'", "'\\x09'", "'\\x0A'", "'\\x0B'", "'\\x0C'", "'\\x0D'", "'\\x0E'", "'\\x0F'", + "'\\x10'", "'\\x11'", "'\\x12'", "'\\x13'", "'\\x14'", "'\\x15'", "'\\x16'", "'\\x17'", + "'\\x18'", "'\\x19'", "'\\x1A'", "'\\x1B'", "'\\x1C'", "'\\x1D'", "'\\x1E'", "'\\x1F'", + "' '", "'!'", "'\"'", "'#'", "'$'", "'%'", "'&'", "'\\''", + "'('", "')'", "'*'", "'+'", "','", "'-'", "'.'", "'/'", + "'0'", "'1'", "'2'", "'3'", "'4'", "'5'", "'6'", "'7'", + "'8'", "'9'", "':'", "';'", "'<'", "'='", "'>'", "'?'", + "'@'", "'A'", "'B'", "'C'", "'D'", "'E'", "'F'", "'G'", + "'H'", "'I'", "'J'", "'K'", "'L'", "'M'", "'N'", "'O'", + "'P'", "'Q'", "'R'", "'S'", "'T'", "'U'", "'V'", "'W'", + "'X'", "'Y'", "'Z'", "'['", "'\'", "']'", "'^'", "'_'", + "'`'", "'a'", "'b'", "'c'", "'d'", "'e'", "'f'", "'g'", + "'h'", "'i'", "'j'", "'k'", "'l'", "'m'", "'n'", "'o'", + "'p'", "'q'", "'r'", "'s'", "'t'", "'u'", "'v'", "'w'", + "'x'", "'y'", "'z'", "'{'", "'|'", "'}'", "'~'", "'\\x7F'", + + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + + "(identifier)", "(number)", "(string)", "(regexp)", + + "'<='", "'>='", "'=='", "'!='", "'==='", "'!=='", + "'<<'", "'>>'", "'>>>'", "'&&'", "'||'", + "'+='", "'-='", "'*='", "'/='", "'%='", + "'<<='", "'>>='", "'>>>='", "'&='", "'|='", "'^='", + "'++'", "'--'", + + "'break'", "'case'", "'catch'", "'continue'", "'debugger'", + "'default'", "'delete'", "'do'", "'else'", "'false'", "'finally'", "'for'", + "'function'", "'if'", "'in'", "'instanceof'", "'new'", "'null'", "'return'", + "'switch'", "'this'", "'throw'", "'true'", "'try'", "'typeof'", "'var'", + "'void'", "'while'", "'with'", +}; + +const char *jsY_tokenstring(int token) +{ + if (token >= 0 && token < (int)nelem(tokenstring)) + if (tokenstring[token]) + return tokenstring[token]; + return ""; +} + +static const char *keywords[] = { + "break", "case", "catch", "continue", "debugger", "default", "delete", + "do", "else", "false", "finally", "for", "function", "if", "in", + "instanceof", "new", "null", "return", "switch", "this", "throw", + "true", "try", "typeof", "var", "void", "while", "with", +}; + +int jsY_findword(const char *s, const char **list, int num) +{ + int l = 0; + int r = num - 1; + while (l <= r) { + int m = (l + r) >> 1; + int c = strcmp(s, list[m]); + if (c < 0) + r = m - 1; + else if (c > 0) + l = m + 1; + else + return m; + } + return -1; +} + +static int jsY_findkeyword(js_State *J, const char *s) +{ + int i = jsY_findword(s, keywords, nelem(keywords)); + if (i >= 0) { + J->text = keywords[i]; + return TK_BREAK + i; /* first keyword + i */ + } + J->text = js_intern(J, s); + return TK_IDENTIFIER; +} + +int jsY_iswhite(int c) +{ + return c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0 || c == 0xFEFF; +} + +int jsY_isnewline(int c) +{ + return c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029; +} + +#ifndef isalpha +#define isalpha(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) +#endif +#ifndef isdigit +#define isdigit(c) (c >= '0' && c <= '9') +#endif +#ifndef ishex +#define ishex(c) ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) +#endif + +static int jsY_isidentifierstart(int c) +{ + return isalpha(c) || c == '$' || c == '_' || isalpharune(c); +} + +static int jsY_isidentifierpart(int c) +{ + return isdigit(c) || isalpha(c) || c == '$' || c == '_' || isalpharune(c); +} + +static int jsY_isdec(int c) +{ + return isdigit(c); +} + +int jsY_ishex(int c) +{ + return isdigit(c) || ishex(c); +} + +int jsY_tohex(int c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 0xA; + if (c >= 'A' && c <= 'F') return c - 'A' + 0xA; + return 0; +} + +static void jsY_next(js_State *J) +{ + Rune c; + if (*J->source == 0) { + J->lexchar = EOF; + return; + } + J->source += chartorune(&c, J->source); + /* consume CR LF as one unit */ + if (c == '\r' && *J->source == '\n') + ++J->source; + if (jsY_isnewline(c)) { + J->line++; + c = '\n'; + } + J->lexchar = c; +} + +#define jsY_accept(J, x) (J->lexchar == x ? (jsY_next(J), 1) : 0) + +#define jsY_expect(J, x) if (!jsY_accept(J, x)) jsY_error(J, "expected '%c'", x) + +static void jsY_unescape(js_State *J) +{ + if (jsY_accept(J, '\\')) { + if (jsY_accept(J, 'u')) { + int x = 0; + if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar) << 12; jsY_next(J); + if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar) << 8; jsY_next(J); + if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); + if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar); + J->lexchar = x; + return; + } +error: + jsY_error(J, "unexpected escape sequence"); + } +} + +static void textinit(js_State *J) +{ + if (!J->lexbuf.text) { + J->lexbuf.cap = 4096; + J->lexbuf.text = js_malloc(J, J->lexbuf.cap); + } + J->lexbuf.len = 0; +} + +static void textpush(js_State *J, Rune c) +{ + int n; + if (c == EOF) + n = 1; + else + n = runelen(c); + if (J->lexbuf.len + n > J->lexbuf.cap) { + J->lexbuf.cap = J->lexbuf.cap * 2; + J->lexbuf.text = js_realloc(J, J->lexbuf.text, J->lexbuf.cap); + } + if (c == EOF) + J->lexbuf.text[J->lexbuf.len++] = 0; + else + J->lexbuf.len += runetochar(J->lexbuf.text + J->lexbuf.len, &c); +} + +static char *textend(js_State *J) +{ + textpush(J, EOF); + return J->lexbuf.text; +} + +static void lexlinecomment(js_State *J) +{ + while (J->lexchar != EOF && J->lexchar != '\n') + jsY_next(J); +} + +static int lexcomment(js_State *J) +{ + /* already consumed initial '/' '*' sequence */ + while (J->lexchar != EOF) { + if (jsY_accept(J, '*')) { + while (J->lexchar == '*') + jsY_next(J); + if (jsY_accept(J, '/')) + return 0; + } + else + jsY_next(J); + } + return -1; +} + +static double lexhex(js_State *J) +{ + double n = 0; + if (!jsY_ishex(J->lexchar)) + jsY_error(J, "malformed hexadecimal number"); + while (jsY_ishex(J->lexchar)) { + n = n * 16 + jsY_tohex(J->lexchar); + jsY_next(J); + } + return n; +} + +#if 0 + +static double lexinteger(js_State *J) +{ + double n = 0; + if (!jsY_isdec(J->lexchar)) + jsY_error(J, "malformed number"); + while (jsY_isdec(J->lexchar)) { + n = n * 10 + (J->lexchar - '0'); + jsY_next(J); + } + return n; +} + +static double lexfraction(js_State *J) +{ + double n = 0; + double d = 1; + while (jsY_isdec(J->lexchar)) { + n = n * 10 + (J->lexchar - '0'); + d = d * 10; + jsY_next(J); + } + return n / d; +} + +static double lexexponent(js_State *J) +{ + double sign; + if (jsY_accept(J, 'e') || jsY_accept(J, 'E')) { + if (jsY_accept(J, '-')) sign = -1; + else if (jsY_accept(J, '+')) sign = 1; + else sign = 1; + return sign * lexinteger(J); + } + return 0; +} + +static int lexnumber(js_State *J) +{ + double n; + double e; + + if (jsY_accept(J, '0')) { + if (jsY_accept(J, 'x') || jsY_accept(J, 'X')) { + J->number = lexhex(J); + return TK_NUMBER; + } + if (jsY_isdec(J->lexchar)) + jsY_error(J, "number with leading zero"); + n = 0; + if (jsY_accept(J, '.')) + n += lexfraction(J); + } else if (jsY_accept(J, '.')) { + if (!jsY_isdec(J->lexchar)) + return '.'; + n = lexfraction(J); + } else { + n = lexinteger(J); + if (jsY_accept(J, '.')) + n += lexfraction(J); + } + + e = lexexponent(J); + if (e < 0) + n /= pow(10, -e); + else if (e > 0) + n *= pow(10, e); + + if (jsY_isidentifierstart(J->lexchar)) + jsY_error(J, "number with letter suffix"); + + J->number = n; + return TK_NUMBER; +} + +#else + +static int lexnumber(js_State *J) +{ + const char *s = J->source - 1; + + if (jsY_accept(J, '0')) { + if (jsY_accept(J, 'x') || jsY_accept(J, 'X')) { + J->number = lexhex(J); + return TK_NUMBER; + } + if (jsY_isdec(J->lexchar)) + jsY_error(J, "number with leading zero"); + if (jsY_accept(J, '.')) { + while (jsY_isdec(J->lexchar)) + jsY_next(J); + } + } else if (jsY_accept(J, '.')) { + if (!jsY_isdec(J->lexchar)) + return '.'; + while (jsY_isdec(J->lexchar)) + jsY_next(J); + } else { + while (jsY_isdec(J->lexchar)) + jsY_next(J); + if (jsY_accept(J, '.')) { + while (jsY_isdec(J->lexchar)) + jsY_next(J); + } + } + + if (jsY_accept(J, 'e') || jsY_accept(J, 'E')) { + if (J->lexchar == '-' || J->lexchar == '+') + jsY_next(J); + if (jsY_isdec(J->lexchar)) + while (jsY_isdec(J->lexchar)) + jsY_next(J); + else + jsY_error(J, "missing exponent"); + } + + if (jsY_isidentifierstart(J->lexchar)) + jsY_error(J, "number with letter suffix"); + + J->number = js_strtod(s, NULL); + return TK_NUMBER; +} + +#endif + +static int lexescape(js_State *J) +{ + int x = 0; + + /* already consumed '\' */ + + if (jsY_accept(J, '\n')) + return 0; + + switch (J->lexchar) { + case EOF: jsY_error(J, "unterminated escape sequence"); + case 'u': + jsY_next(J); + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 12; jsY_next(J); } + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 8; jsY_next(J); } + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); } + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar); jsY_next(J); } + textpush(J, x); + break; + case 'x': + jsY_next(J); + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); } + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar); jsY_next(J); } + textpush(J, x); + break; + case '0': textpush(J, 0); jsY_next(J); break; + case '\\': textpush(J, '\\'); jsY_next(J); break; + case '\'': textpush(J, '\''); jsY_next(J); break; + case '"': textpush(J, '"'); jsY_next(J); break; + case 'b': textpush(J, '\b'); jsY_next(J); break; + case 'f': textpush(J, '\f'); jsY_next(J); break; + case 'n': textpush(J, '\n'); jsY_next(J); break; + case 'r': textpush(J, '\r'); jsY_next(J); break; + case 't': textpush(J, '\t'); jsY_next(J); break; + case 'v': textpush(J, '\v'); jsY_next(J); break; + default: textpush(J, J->lexchar); jsY_next(J); break; + } + return 0; +} + +static int lexstring(js_State *J) +{ + const char *s; + + int q = J->lexchar; + jsY_next(J); + + textinit(J); + + while (J->lexchar != q) { + if (J->lexchar == EOF || J->lexchar == '\n') + jsY_error(J, "string not terminated"); + if (jsY_accept(J, '\\')) { + if (lexescape(J)) + jsY_error(J, "malformed escape sequence"); + } else { + textpush(J, J->lexchar); + jsY_next(J); + } + } + jsY_expect(J, q); + + s = textend(J); + + J->text = js_intern(J, s); + return TK_STRING; +} + +/* the ugliest language wart ever... */ +static int isregexpcontext(int last) +{ + switch (last) { + case ']': + case ')': + case '}': + case TK_IDENTIFIER: + case TK_NUMBER: + case TK_STRING: + case TK_FALSE: + case TK_NULL: + case TK_THIS: + case TK_TRUE: + return 0; + default: + return 1; + } +} + +static int lexregexp(js_State *J) +{ + const char *s; + int g, m, i; + int inclass = 0; + + /* already consumed initial '/' */ + + textinit(J); + + /* regexp body */ + while (J->lexchar != '/' || inclass) { + if (J->lexchar == EOF || J->lexchar == '\n') { + jsY_error(J, "regular expression not terminated"); + } else if (jsY_accept(J, '\\')) { + if (jsY_accept(J, '/')) { + textpush(J, '/'); + } else { + textpush(J, '\\'); + if (J->lexchar == EOF || J->lexchar == '\n') + jsY_error(J, "regular expression not terminated"); + textpush(J, J->lexchar); + jsY_next(J); + } + } else { + if (J->lexchar == '[' && !inclass) + inclass = 1; + if (J->lexchar == ']' && inclass) + inclass = 0; + textpush(J, J->lexchar); + jsY_next(J); + } + } + jsY_expect(J, '/'); + + s = textend(J); + + /* regexp flags */ + g = i = m = 0; + + while (jsY_isidentifierpart(J->lexchar)) { + if (jsY_accept(J, 'g')) ++g; + else if (jsY_accept(J, 'i')) ++i; + else if (jsY_accept(J, 'm')) ++m; + else jsY_error(J, "illegal flag in regular expression: %c", J->lexchar); + } + + if (g > 1 || i > 1 || m > 1) + jsY_error(J, "duplicated flag in regular expression"); + + J->text = js_intern(J, s); + J->number = 0; + if (g) J->number += JS_REGEXP_G; + if (i) J->number += JS_REGEXP_I; + if (m) J->number += JS_REGEXP_M; + return TK_REGEXP; +} + +/* simple "return [no Line Terminator here] ..." contexts */ +static int isnlthcontext(int last) +{ + switch (last) { + case TK_BREAK: + case TK_CONTINUE: + case TK_RETURN: + case TK_THROW: + return 1; + default: + return 0; + } +} + +static int jsY_lexx(js_State *J) +{ + J->newline = 0; + + while (1) { + J->lexline = J->line; /* save location of beginning of token */ + + while (jsY_iswhite(J->lexchar)) + jsY_next(J); + + if (jsY_accept(J, '\n')) { + J->newline = 1; + if (isnlthcontext(J->lasttoken)) + return ';'; + continue; + } + + if (jsY_accept(J, '/')) { + if (jsY_accept(J, '/')) { + lexlinecomment(J); + continue; + } else if (jsY_accept(J, '*')) { + if (lexcomment(J)) + jsY_error(J, "multi-line comment not terminated"); + continue; + } else if (isregexpcontext(J->lasttoken)) { + return lexregexp(J); + } else if (jsY_accept(J, '=')) { + return TK_DIV_ASS; + } else { + return '/'; + } + } + + if (J->lexchar >= '0' && J->lexchar <= '9') { + return lexnumber(J); + } + + switch (J->lexchar) { + case '(': jsY_next(J); return '('; + case ')': jsY_next(J); return ')'; + case ',': jsY_next(J); return ','; + case ':': jsY_next(J); return ':'; + case ';': jsY_next(J); return ';'; + case '?': jsY_next(J); return '?'; + case '[': jsY_next(J); return '['; + case ']': jsY_next(J); return ']'; + case '{': jsY_next(J); return '{'; + case '}': jsY_next(J); return '}'; + case '~': jsY_next(J); return '~'; + + case '\'': + case '"': + return lexstring(J); + + case '.': + return lexnumber(J); + + case '<': + jsY_next(J); + if (jsY_accept(J, '<')) { + if (jsY_accept(J, '=')) + return TK_SHL_ASS; + return TK_SHL; + } + if (jsY_accept(J, '=')) + return TK_LE; + return '<'; + + case '>': + jsY_next(J); + if (jsY_accept(J, '>')) { + if (jsY_accept(J, '>')) { + if (jsY_accept(J, '=')) + return TK_USHR_ASS; + return TK_USHR; + } + if (jsY_accept(J, '=')) + return TK_SHR_ASS; + return TK_SHR; + } + if (jsY_accept(J, '=')) + return TK_GE; + return '>'; + + case '=': + jsY_next(J); + if (jsY_accept(J, '=')) { + if (jsY_accept(J, '=')) + return TK_STRICTEQ; + return TK_EQ; + } + return '='; + + case '!': + jsY_next(J); + if (jsY_accept(J, '=')) { + if (jsY_accept(J, '=')) + return TK_STRICTNE; + return TK_NE; + } + return '!'; + + case '+': + jsY_next(J); + if (jsY_accept(J, '+')) + return TK_INC; + if (jsY_accept(J, '=')) + return TK_ADD_ASS; + return '+'; + + case '-': + jsY_next(J); + if (jsY_accept(J, '-')) + return TK_DEC; + if (jsY_accept(J, '=')) + return TK_SUB_ASS; + return '-'; + + case '*': + jsY_next(J); + if (jsY_accept(J, '=')) + return TK_MUL_ASS; + return '*'; + + case '%': + jsY_next(J); + if (jsY_accept(J, '=')) + return TK_MOD_ASS; + return '%'; + + case '&': + jsY_next(J); + if (jsY_accept(J, '&')) + return TK_AND; + if (jsY_accept(J, '=')) + return TK_AND_ASS; + return '&'; + + case '|': + jsY_next(J); + if (jsY_accept(J, '|')) + return TK_OR; + if (jsY_accept(J, '=')) + return TK_OR_ASS; + return '|'; + + case '^': + jsY_next(J); + if (jsY_accept(J, '=')) + return TK_XOR_ASS; + return '^'; + + case EOF: + return 0; /* EOF */ + } + + /* Handle \uXXXX escapes in identifiers */ + jsY_unescape(J); + if (jsY_isidentifierstart(J->lexchar)) { + textinit(J); + textpush(J, J->lexchar); + + jsY_next(J); + jsY_unescape(J); + while (jsY_isidentifierpart(J->lexchar)) { + textpush(J, J->lexchar); + jsY_next(J); + jsY_unescape(J); + } + + textend(J); + + return jsY_findkeyword(J, J->lexbuf.text); + } + + if (J->lexchar >= 0x20 && J->lexchar <= 0x7E) + jsY_error(J, "unexpected character: '%c'", J->lexchar); + jsY_error(J, "unexpected character: \\u%04X", J->lexchar); + } +} + +void jsY_initlex(js_State *J, const char *filename, const char *source) +{ + J->filename = filename; + J->source = source; + J->line = 1; + J->lasttoken = 0; + jsY_next(J); /* load first lookahead character */ +} + +int jsY_lex(js_State *J) +{ + return J->lasttoken = jsY_lexx(J); +} + +static int lexjsonnumber(js_State *J) +{ + const char *s = J->source - 1; + + if (J->lexchar == '-') + jsY_next(J); + + if (J->lexchar == '0') + jsY_next(J); + else if (J->lexchar >= '1' && J->lexchar <= '9') + while (isdigit(J->lexchar)) + jsY_next(J); + else + jsY_error(J, "unexpected non-digit"); + + if (jsY_accept(J, '.')) { + if (isdigit(J->lexchar)) + while (isdigit(J->lexchar)) + jsY_next(J); + else + jsY_error(J, "missing digits after decimal point"); + } + + if (jsY_accept(J, 'e') || jsY_accept(J, 'E')) { + if (J->lexchar == '-' || J->lexchar == '+') + jsY_next(J); + if (isdigit(J->lexchar)) + while (isdigit(J->lexchar)) + jsY_next(J); + else + jsY_error(J, "missing digits after exponent indicator"); + } + + J->number = js_strtod(s, NULL); + return TK_NUMBER; +} + +static int lexjsonescape(js_State *J) +{ + int x = 0; + + /* already consumed '\' */ + + switch (J->lexchar) { + default: jsY_error(J, "invalid escape sequence"); + case 'u': + jsY_next(J); + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 12; jsY_next(J); } + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 8; jsY_next(J); } + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); } + if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar); jsY_next(J); } + textpush(J, x); + break; + case '"': textpush(J, '"'); jsY_next(J); break; + case '\\': textpush(J, '\\'); jsY_next(J); break; + case '/': textpush(J, '/'); jsY_next(J); break; + case 'b': textpush(J, '\b'); jsY_next(J); break; + case 'f': textpush(J, '\f'); jsY_next(J); break; + case 'n': textpush(J, '\n'); jsY_next(J); break; + case 'r': textpush(J, '\r'); jsY_next(J); break; + case 't': textpush(J, '\t'); jsY_next(J); break; + } + return 0; +} + +static int lexjsonstring(js_State *J) +{ + const char *s; + + textinit(J); + + while (J->lexchar != '"') { + if (J->lexchar == EOF) + jsY_error(J, "unterminated string"); + else if (J->lexchar < 32) + jsY_error(J, "invalid control character in string"); + else if (jsY_accept(J, '\\')) + lexjsonescape(J); + else { + textpush(J, J->lexchar); + jsY_next(J); + } + } + jsY_expect(J, '"'); + + s = textend(J); + + J->text = js_intern(J, s); + return TK_STRING; +} + +int jsY_lexjson(js_State *J) +{ + while (1) { + J->lexline = J->line; /* save location of beginning of token */ + + while (jsY_iswhite(J->lexchar) || J->lexchar == '\n') + jsY_next(J); + + if ((J->lexchar >= '0' && J->lexchar <= '9') || J->lexchar == '-') + return lexjsonnumber(J); + + switch (J->lexchar) { + case ',': jsY_next(J); return ','; + case ':': jsY_next(J); return ':'; + case '[': jsY_next(J); return '['; + case ']': jsY_next(J); return ']'; + case '{': jsY_next(J); return '{'; + case '}': jsY_next(J); return '}'; + + case '"': + jsY_next(J); + return lexjsonstring(J); + + case 'f': + jsY_next(J); jsY_expect(J, 'a'); jsY_expect(J, 'l'); jsY_expect(J, 's'); jsY_expect(J, 'e'); + return TK_FALSE; + + case 'n': + jsY_next(J); jsY_expect(J, 'u'); jsY_expect(J, 'l'); jsY_expect(J, 'l'); + return TK_NULL; + + case 't': + jsY_next(J); jsY_expect(J, 'r'); jsY_expect(J, 'u'); jsY_expect(J, 'e'); + return TK_TRUE; + + case EOF: + return 0; /* EOF */ + } + + if (J->lexchar >= 0x20 && J->lexchar <= 0x7E) + jsY_error(J, "unexpected character: '%c'", J->lexchar); + jsY_error(J, "unexpected character: \\u%04X", J->lexchar); + } +} diff --git a/mujs/jsmath.c b/mujs/jsmath.c new file mode 100644 index 0000000..6d33883 --- /dev/null +++ b/mujs/jsmath.c @@ -0,0 +1,194 @@ +#include "jsi.h" + +#if defined(_MSC_VER) && (_MSC_VER < 1700) /* VS2012 has stdint.h */ +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +#include + +static double jsM_round(double x) +{ + if (isnan(x)) return x; + if (isinf(x)) return x; + if (x == 0) return x; + if (x > 0 && x < 0.5) return 0; + if (x < 0 && x >= -0.5) return -0; + return floor(x + 0.5); +} + +static void Math_abs(js_State *J) +{ + js_pushnumber(J, fabs(js_tonumber(J, 1))); +} + +static void Math_acos(js_State *J) +{ + js_pushnumber(J, acos(js_tonumber(J, 1))); +} + +static void Math_asin(js_State *J) +{ + js_pushnumber(J, asin(js_tonumber(J, 1))); +} + +static void Math_atan(js_State *J) +{ + js_pushnumber(J, atan(js_tonumber(J, 1))); +} + +static void Math_atan2(js_State *J) +{ + double y = js_tonumber(J, 1); + double x = js_tonumber(J, 2); + js_pushnumber(J, atan2(y, x)); +} + +static void Math_ceil(js_State *J) +{ + js_pushnumber(J, ceil(js_tonumber(J, 1))); +} + +static void Math_cos(js_State *J) +{ + js_pushnumber(J, cos(js_tonumber(J, 1))); +} + +static void Math_exp(js_State *J) +{ + js_pushnumber(J, exp(js_tonumber(J, 1))); +} + +static void Math_floor(js_State *J) +{ + js_pushnumber(J, floor(js_tonumber(J, 1))); +} + +static void Math_log(js_State *J) +{ + js_pushnumber(J, log(js_tonumber(J, 1))); +} + +static void Math_pow(js_State *J) +{ + double x = js_tonumber(J, 1); + double y = js_tonumber(J, 2); + if (!isfinite(y) && fabs(x) == 1) + js_pushnumber(J, NAN); + else + js_pushnumber(J, pow(x,y)); +} + +static void Math_random(js_State *J) +{ + /* Lehmer generator with a=48271 and m=2^31-1 */ + /* Park & Miller (1988). Random Number Generators: Good ones are hard to find. */ + J->seed = (uint64_t) J->seed * 48271 % 0x7fffffff; + js_pushnumber(J, (double) J->seed / 0x7fffffff); +} + +static void Math_init_random(js_State *J) +{ + /* Pick initial seed by scrambling current time with Xorshift. */ + /* Marsaglia (2003). Xorshift RNGs. */ + J->seed = time(0) + 123; + J->seed ^= J->seed << 13; + J->seed ^= J->seed >> 17; + J->seed ^= J->seed << 5; + J->seed %= 0x7fffffff; +} + +static void Math_round(js_State *J) +{ + double x = js_tonumber(J, 1); + js_pushnumber(J, jsM_round(x)); +} + +static void Math_sin(js_State *J) +{ + js_pushnumber(J, sin(js_tonumber(J, 1))); +} + +static void Math_sqrt(js_State *J) +{ + js_pushnumber(J, sqrt(js_tonumber(J, 1))); +} + +static void Math_tan(js_State *J) +{ + js_pushnumber(J, tan(js_tonumber(J, 1))); +} + +static void Math_max(js_State *J) +{ + int i, n = js_gettop(J); + double x = -INFINITY; + for (i = 1; i < n; ++i) { + double y = js_tonumber(J, i); + if (isnan(y)) { + x = y; + break; + } + if (signbit(x) == signbit(y)) + x = x > y ? x : y; + else if (signbit(x)) + x = y; + } + js_pushnumber(J, x); +} + +static void Math_min(js_State *J) +{ + int i, n = js_gettop(J); + double x = INFINITY; + for (i = 1; i < n; ++i) { + double y = js_tonumber(J, i); + if (isnan(y)) { + x = y; + break; + } + if (signbit(x) == signbit(y)) + x = x < y ? x : y; + else if (signbit(y)) + x = y; + } + js_pushnumber(J, x); +} + +void jsB_initmath(js_State *J) +{ + Math_init_random(J); + js_pushobject(J, jsV_newobject(J, JS_CMATH, J->Object_prototype)); + { + jsB_propn(J, "E", 2.7182818284590452354); + jsB_propn(J, "LN10", 2.302585092994046); + jsB_propn(J, "LN2", 0.6931471805599453); + jsB_propn(J, "LOG2E", 1.4426950408889634); + jsB_propn(J, "LOG10E", 0.4342944819032518); + jsB_propn(J, "PI", 3.1415926535897932); + jsB_propn(J, "SQRT1_2", 0.7071067811865476); + jsB_propn(J, "SQRT2", 1.4142135623730951); + + jsB_propf(J, "Math.abs", Math_abs, 1); + jsB_propf(J, "Math.acos", Math_acos, 1); + jsB_propf(J, "Math.asin", Math_asin, 1); + jsB_propf(J, "Math.atan", Math_atan, 1); + jsB_propf(J, "Math.atan2", Math_atan2, 2); + jsB_propf(J, "Math.ceil", Math_ceil, 1); + jsB_propf(J, "Math.cos", Math_cos, 1); + jsB_propf(J, "Math.exp", Math_exp, 1); + jsB_propf(J, "Math.floor", Math_floor, 1); + jsB_propf(J, "Math.log", Math_log, 1); + jsB_propf(J, "Math.max", Math_max, 0); /* 2 */ + jsB_propf(J, "Math.min", Math_min, 0); /* 2 */ + jsB_propf(J, "Math.pow", Math_pow, 2); + jsB_propf(J, "Math.random", Math_random, 0); + jsB_propf(J, "Math.round", Math_round, 1); + jsB_propf(J, "Math.sin", Math_sin, 1); + jsB_propf(J, "Math.sqrt", Math_sqrt, 1); + jsB_propf(J, "Math.tan", Math_tan, 1); + } + js_defglobal(J, "Math", JS_DONTENUM); +} diff --git a/mujs/jsnumber.c b/mujs/jsnumber.c new file mode 100644 index 0000000..8e79d34 --- /dev/null +++ b/mujs/jsnumber.c @@ -0,0 +1,198 @@ +#include "jsi.h" + +#if defined(_MSC_VER) && (_MSC_VER < 1700) /* VS2012 has stdint.h */ +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +static void jsB_new_Number(js_State *J) +{ + js_newnumber(J, js_gettop(J) > 1 ? js_tonumber(J, 1) : 0); +} + +static void jsB_Number(js_State *J) +{ + js_pushnumber(J, js_gettop(J) > 1 ? js_tonumber(J, 1) : 0); +} + +static void Np_valueOf(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); + js_pushnumber(J, self->u.number); +} + +static void Np_toString(js_State *J) +{ + char buf[100]; + js_Object *self = js_toobject(J, 0); + int radix = js_isundefined(J, 1) ? 10 : js_tointeger(J, 1); + double x = 0; + if (self->type != JS_CNUMBER) + js_typeerror(J, "not a number"); + x = self->u.number; + if (radix == 10) { + js_pushstring(J, jsV_numbertostring(J, buf, x)); + return; + } + if (radix < 2 || radix > 36) + js_rangeerror(J, "invalid radix"); + + /* lame number to string conversion for any radix from 2 to 36 */ + { + static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + double number = x; + int sign = x < 0; + js_Buffer *sb = NULL; + uint64_t u, limit = ((uint64_t)1<<52); + + int ndigits, exp, point; + + if (number == 0) { js_pushstring(J, "0"); return; } + if (isnan(number)) { js_pushstring(J, "NaN"); return; } + if (isinf(number)) { js_pushstring(J, sign ? "-Infinity" : "Infinity"); return; } + + if (sign) + number = -number; + + /* fit as many digits as we want in an int */ + exp = 0; + while (number * pow(radix, exp) > limit) + --exp; + while (number * pow(radix, exp+1) < limit) + ++exp; + u = number * pow(radix, exp) + 0.5; + + /* trim trailing zeros */ + while (u > 0 && (u % radix) == 0) { + u /= radix; + --exp; + } + + /* serialize digits */ + ndigits = 0; + while (u > 0) { + buf[ndigits++] = digits[u % radix]; + u /= radix; + } + point = ndigits - exp; + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + + if (sign) + js_putc(J, &sb, '-'); + + if (point <= 0) { + js_putc(J, &sb, '0'); + js_putc(J, &sb, '.'); + while (point++ < 0) + js_putc(J, &sb, '0'); + while (ndigits-- > 0) + js_putc(J, &sb, buf[ndigits]); + } else { + while (ndigits-- > 0) { + js_putc(J, &sb, buf[ndigits]); + if (--point == 0 && ndigits > 0) + js_putc(J, &sb, '.'); + } + while (point-- > 0) + js_putc(J, &sb, '0'); + } + + js_putc(J, &sb, 0); + js_pushstring(J, sb->s); + + js_endtry(J); + js_free(J, sb); + } +} + +/* Customized ToString() on a number */ +static void numtostr(js_State *J, const char *fmt, int w, double n) +{ + /* buf needs to fit printf("%.20f", 1e20) */ + char buf[50], *e; + sprintf(buf, fmt, w, n); + e = strchr(buf, 'e'); + if (e) { + int exp = atoi(e+1); + sprintf(e, "e%+d", exp); + } + js_pushstring(J, buf); +} + +static void Np_toFixed(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + int width = js_tointeger(J, 1); + char buf[32]; + double x; + if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); + if (width < 0) js_rangeerror(J, "precision %d out of range", width); + if (width > 20) js_rangeerror(J, "precision %d out of range", width); + x = self->u.number; + if (isnan(x) || isinf(x) || x <= -1e21 || x >= 1e21) + js_pushstring(J, jsV_numbertostring(J, buf, x)); + else + numtostr(J, "%.*f", width, x); +} + +static void Np_toExponential(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + int width = js_tointeger(J, 1); + char buf[32]; + double x; + if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); + if (width < 0) js_rangeerror(J, "precision %d out of range", width); + if (width > 20) js_rangeerror(J, "precision %d out of range", width); + x = self->u.number; + if (isnan(x) || isinf(x)) + js_pushstring(J, jsV_numbertostring(J, buf, x)); + else + numtostr(J, "%.*e", width, x); +} + +static void Np_toPrecision(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + int width = js_tointeger(J, 1); + char buf[32]; + double x; + if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); + if (width < 1) js_rangeerror(J, "precision %d out of range", width); + if (width > 21) js_rangeerror(J, "precision %d out of range", width); + x = self->u.number; + if (isnan(x) || isinf(x)) + js_pushstring(J, jsV_numbertostring(J, buf, x)); + else + numtostr(J, "%.*g", width, x); +} + +void jsB_initnumber(js_State *J) +{ + J->Number_prototype->u.number = 0; + + js_pushobject(J, J->Number_prototype); + { + jsB_propf(J, "Number.prototype.valueOf", Np_valueOf, 0); + jsB_propf(J, "Number.prototype.toString", Np_toString, 1); + jsB_propf(J, "Number.prototype.toLocaleString", Np_toString, 0); + jsB_propf(J, "Number.prototype.toFixed", Np_toFixed, 1); + jsB_propf(J, "Number.prototype.toExponential", Np_toExponential, 1); + jsB_propf(J, "Number.prototype.toPrecision", Np_toPrecision, 1); + } + js_newcconstructor(J, jsB_Number, jsB_new_Number, "Number", 0); /* 1 */ + { + jsB_propn(J, "MAX_VALUE", 1.7976931348623157e+308); + jsB_propn(J, "MIN_VALUE", 5e-324); + jsB_propn(J, "NaN", NAN); + jsB_propn(J, "NEGATIVE_INFINITY", -INFINITY); + jsB_propn(J, "POSITIVE_INFINITY", INFINITY); + } + js_defglobal(J, "Number", JS_DONTENUM); +} diff --git a/mujs/jsobject.c b/mujs/jsobject.c new file mode 100644 index 0000000..efb571b --- /dev/null +++ b/mujs/jsobject.c @@ -0,0 +1,560 @@ +#include "jsi.h" + +static void jsB_new_Object(js_State *J) +{ + if (js_isundefined(J, 1) || js_isnull(J, 1)) + js_newobject(J); + else + js_pushobject(J, js_toobject(J, 1)); +} + +static void jsB_Object(js_State *J) +{ + if (js_isundefined(J, 1) || js_isnull(J, 1)) + js_newobject(J); + else + js_pushobject(J, js_toobject(J, 1)); +} + +static void Op_toString(js_State *J) +{ + if (js_isundefined(J, 0)) + js_pushliteral(J, "[object Undefined]"); + else if (js_isnull(J, 0)) + js_pushliteral(J, "[object Null]"); + else { + js_Object *self = js_toobject(J, 0); + switch (self->type) { + case JS_COBJECT: js_pushliteral(J, "[object Object]"); break; + case JS_CARRAY: js_pushliteral(J, "[object Array]"); break; + case JS_CFUNCTION: js_pushliteral(J, "[object Function]"); break; + case JS_CSCRIPT: js_pushliteral(J, "[object Function]"); break; + case JS_CCFUNCTION: js_pushliteral(J, "[object Function]"); break; + case JS_CERROR: js_pushliteral(J, "[object Error]"); break; + case JS_CBOOLEAN: js_pushliteral(J, "[object Boolean]"); break; + case JS_CNUMBER: js_pushliteral(J, "[object Number]"); break; + case JS_CSTRING: js_pushliteral(J, "[object String]"); break; + case JS_CREGEXP: js_pushliteral(J, "[object RegExp]"); break; + case JS_CDATE: js_pushliteral(J, "[object Date]"); break; + case JS_CMATH: js_pushliteral(J, "[object Math]"); break; + case JS_CJSON: js_pushliteral(J, "[object JSON]"); break; + case JS_CARGUMENTS: js_pushliteral(J, "[object Arguments]"); break; + case JS_CITERATOR: js_pushliteral(J, "[object Iterator]"); break; + case JS_CUSERDATA: + js_pushliteral(J, "[object "); + js_pushliteral(J, self->u.user.tag); + js_concat(J); + js_pushliteral(J, "]"); + js_concat(J); + break; + } + } +} + +static void Op_valueOf(js_State *J) +{ + js_copy(J, 0); +} + +static void Op_hasOwnProperty(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + const char *name = js_tostring(J, 1); + js_Property *ref; + int k; + + if (self->type == JS_CSTRING) { + if (js_isarrayindex(J, name, &k) && k >= 0 && k < self->u.s.length) { + js_pushboolean(J, 1); + return; + } + } + + if (self->type == JS_CARRAY && self->u.a.simple) { + if (js_isarrayindex(J, name, &k) && k >= 0 && k < self->u.a.flat_length) { + js_pushboolean(J, 1); + return; + } + } + + ref = jsV_getownproperty(J, self, name); + js_pushboolean(J, ref != NULL); +} + +static void Op_isPrototypeOf(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + if (js_isobject(J, 1)) { + js_Object *V = js_toobject(J, 1); + do { + V = V->prototype; + if (V == self) { + js_pushboolean(J, 1); + return; + } + } while (V); + } + js_pushboolean(J, 0); +} + +static void Op_propertyIsEnumerable(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + const char *name = js_tostring(J, 1); + js_Property *ref = jsV_getownproperty(J, self, name); + js_pushboolean(J, ref && !(ref->atts & JS_DONTENUM)); +} + +static void O_getPrototypeOf(js_State *J) +{ + js_Object *obj; + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + obj = js_toobject(J, 1); + if (obj->prototype) + js_pushobject(J, obj->prototype); + else + js_pushnull(J); +} + +static void O_getOwnPropertyDescriptor(js_State *J) +{ + js_Object *obj; + js_Property *ref; + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + obj = js_toobject(J, 1); + ref = jsV_getproperty(J, obj, js_tostring(J, 2)); + if (!ref) { + /* TODO: builtin properties (string and array index and length, regexp flags, etc) */ + js_pushundefined(J); + } else { + js_newobject(J); + if (!ref->getter && !ref->setter) { + js_pushvalue(J, ref->value); + js_defproperty(J, -2, "value", 0); + js_pushboolean(J, !(ref->atts & JS_READONLY)); + js_defproperty(J, -2, "writable", 0); + } else { + if (ref->getter) + js_pushobject(J, ref->getter); + else + js_pushundefined(J); + js_defproperty(J, -2, "get", 0); + if (ref->setter) + js_pushobject(J, ref->setter); + else + js_pushundefined(J); + js_defproperty(J, -2, "set", 0); + } + js_pushboolean(J, !(ref->atts & JS_DONTENUM)); + js_defproperty(J, -2, "enumerable", 0); + js_pushboolean(J, !(ref->atts & JS_DONTCONF)); + js_defproperty(J, -2, "configurable", 0); + } +} + +static int O_getOwnPropertyNames_walk(js_State *J, js_Property *ref, int i) +{ + if (ref->left->level) + i = O_getOwnPropertyNames_walk(J, ref->left, i); + js_pushstring(J, ref->name); + js_setindex(J, -2, i++); + if (ref->right->level) + i = O_getOwnPropertyNames_walk(J, ref->right, i); + return i; +} + +static void O_getOwnPropertyNames(js_State *J) +{ + js_Object *obj; + char name[32]; + int k; + int i; + + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + obj = js_toobject(J, 1); + + js_newarray(J); + + if (obj->properties->level) + i = O_getOwnPropertyNames_walk(J, obj->properties, 0); + else + i = 0; + + if (obj->type == JS_CARRAY) { + js_pushliteral(J, "length"); + js_setindex(J, -2, i++); + if (obj->u.a.simple) { + for (k = 0; k < obj->u.a.flat_length; ++k) { + js_itoa(name, k); + js_pushstring(J, name); + js_setindex(J, -2, i++); + } + } + } + + if (obj->type == JS_CSTRING) { + js_pushliteral(J, "length"); + js_setindex(J, -2, i++); + for (k = 0; k < obj->u.s.length; ++k) { + js_itoa(name, k); + js_pushstring(J, name); + js_setindex(J, -2, i++); + } + } + + if (obj->type == JS_CREGEXP) { + js_pushliteral(J, "source"); + js_setindex(J, -2, i++); + js_pushliteral(J, "global"); + js_setindex(J, -2, i++); + js_pushliteral(J, "ignoreCase"); + js_setindex(J, -2, i++); + js_pushliteral(J, "multiline"); + js_setindex(J, -2, i++); + js_pushliteral(J, "lastIndex"); + js_setindex(J, -2, i++); + } +} + +static void ToPropertyDescriptor(js_State *J, js_Object *obj, const char *name, js_Object *desc) +{ + int haswritable = 0; + int hasvalue = 0; + int enumerable = 0; + int configurable = 0; + int writable = 0; + int atts = 0; + + js_pushobject(J, obj); + js_pushobject(J, desc); + + if (js_hasproperty(J, -1, "writable")) { + haswritable = 1; + writable = js_toboolean(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, -1, "enumerable")) { + enumerable = js_toboolean(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, -1, "configurable")) { + configurable = js_toboolean(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, -1, "value")) { + hasvalue = 1; + js_defproperty(J, -3, name, 0); + } + + if (!writable) atts |= JS_READONLY; + if (!enumerable) atts |= JS_DONTENUM; + if (!configurable) atts |= JS_DONTCONF; + + if (js_hasproperty(J, -1, "get")) { + if (haswritable || hasvalue) + js_typeerror(J, "value/writable and get/set attributes are exclusive"); + } else { + js_pushundefined(J); + } + + if (js_hasproperty(J, -2, "set")) { + if (haswritable || hasvalue) + js_typeerror(J, "value/writable and get/set attributes are exclusive"); + } else { + js_pushundefined(J); + } + + js_defaccessor(J, -4, name, atts); + + js_pop(J, 2); +} + +static void O_defineProperty(js_State *J) +{ + if (!js_isobject(J, 1)) js_typeerror(J, "not an object"); + if (!js_isobject(J, 3)) js_typeerror(J, "not an object"); + ToPropertyDescriptor(J, js_toobject(J, 1), js_tostring(J, 2), js_toobject(J, 3)); + js_copy(J, 1); +} + +static void O_defineProperties_walk(js_State *J, js_Property *ref) +{ + if (ref->left->level) + O_defineProperties_walk(J, ref->left); + if (!(ref->atts & JS_DONTENUM)) { + js_pushvalue(J, ref->value); + ToPropertyDescriptor(J, js_toobject(J, 1), ref->name, js_toobject(J, -1)); + js_pop(J, 1); + } + if (ref->right->level) + O_defineProperties_walk(J, ref->right); +} + +static void O_defineProperties(js_State *J) +{ + js_Object *props; + + if (!js_isobject(J, 1)) js_typeerror(J, "not an object"); + if (!js_isobject(J, 2)) js_typeerror(J, "not an object"); + + props = js_toobject(J, 2); + if (props->properties->level) + O_defineProperties_walk(J, props->properties); + + js_copy(J, 1); +} + +static void O_create_walk(js_State *J, js_Object *obj, js_Property *ref) +{ + if (ref->left->level) + O_create_walk(J, obj, ref->left); + if (!(ref->atts & JS_DONTENUM)) { + if (ref->value.t.type != JS_TOBJECT) + js_typeerror(J, "not an object"); + ToPropertyDescriptor(J, obj, ref->name, ref->value.u.object); + } + if (ref->right->level) + O_create_walk(J, obj, ref->right); +} + +static void O_create(js_State *J) +{ + js_Object *obj; + js_Object *proto; + js_Object *props; + + if (js_isobject(J, 1)) + proto = js_toobject(J, 1); + else if (js_isnull(J, 1)) + proto = NULL; + else + js_typeerror(J, "not an object or null"); + + obj = jsV_newobject(J, JS_COBJECT, proto); + js_pushobject(J, obj); + + if (js_isdefined(J, 2)) { + if (!js_isobject(J, 2)) + js_typeerror(J, "not an object"); + props = js_toobject(J, 2); + if (props->properties->level) + O_create_walk(J, obj, props->properties); + } +} + +static int O_keys_walk(js_State *J, js_Property *ref, int i) +{ + if (ref->left->level) + i = O_keys_walk(J, ref->left, i); + if (!(ref->atts & JS_DONTENUM)) { + js_pushstring(J, ref->name); + js_setindex(J, -2, i++); + } + if (ref->right->level) + i = O_keys_walk(J, ref->right, i); + return i; +} + +static void O_keys(js_State *J) +{ + js_Object *obj; + char name[32]; + int i, k; + + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + obj = js_toobject(J, 1); + + js_newarray(J); + + if (obj->properties->level) + i = O_keys_walk(J, obj->properties, 0); + else + i = 0; + + if (obj->type == JS_CSTRING) { + for (k = 0; k < obj->u.s.length; ++k) { + js_itoa(name, k); + js_pushstring(J, name); + js_setindex(J, -2, i++); + } + } + + if (obj->type == JS_CARRAY && obj->u.a.simple) { + for (k = 0; k < obj->u.a.flat_length; ++k) { + js_itoa(name, k); + js_pushstring(J, name); + js_setindex(J, -2, i++); + } + } +} + +static void O_preventExtensions(js_State *J) +{ + js_Object *obj; + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + obj = js_toobject(J, 1); + jsR_unflattenarray(J, obj); + obj->extensible = 0; + js_copy(J, 1); +} + +static void O_isExtensible(js_State *J) +{ + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + js_pushboolean(J, js_toobject(J, 1)->extensible); +} + +static void O_seal_walk(js_State *J, js_Property *ref) +{ + if (ref->left->level) + O_seal_walk(J, ref->left); + ref->atts |= JS_DONTCONF; + if (ref->right->level) + O_seal_walk(J, ref->right); +} + +static void O_seal(js_State *J) +{ + js_Object *obj; + + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + + obj = js_toobject(J, 1); + jsR_unflattenarray(J, obj); + obj->extensible = 0; + + if (obj->properties->level) + O_seal_walk(J, obj->properties); + + js_copy(J, 1); +} + +static int O_isSealed_walk(js_State *J, js_Property *ref) +{ + if (ref->left->level) + if (!O_isSealed_walk(J, ref->left)) + return 0; + if (!(ref->atts & JS_DONTCONF)) + return 0; + if (ref->right->level) + if (!O_isSealed_walk(J, ref->right)) + return 0; + return 1; +} + +static void O_isSealed(js_State *J) +{ + js_Object *obj; + + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + + obj = js_toobject(J, 1); + if (obj->extensible) { + js_pushboolean(J, 0); + return; + } + + if (obj->properties->level) + js_pushboolean(J, O_isSealed_walk(J, obj->properties)); + else + js_pushboolean(J, 1); +} + +static void O_freeze_walk(js_State *J, js_Property *ref) +{ + if (ref->left->level) + O_freeze_walk(J, ref->left); + ref->atts |= JS_READONLY | JS_DONTCONF; + if (ref->right->level) + O_freeze_walk(J, ref->right); +} + +static void O_freeze(js_State *J) +{ + js_Object *obj; + + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + + obj = js_toobject(J, 1); + jsR_unflattenarray(J, obj); + obj->extensible = 0; + + if (obj->properties->level) + O_freeze_walk(J, obj->properties); + + js_copy(J, 1); +} + +static int O_isFrozen_walk(js_State *J, js_Property *ref) +{ + if (ref->left->level) + if (!O_isFrozen_walk(J, ref->left)) + return 0; + if (!(ref->atts & JS_READONLY)) + return 0; + if (!(ref->atts & JS_DONTCONF)) + return 0; + if (ref->right->level) + if (!O_isFrozen_walk(J, ref->right)) + return 0; + return 1; +} + +static void O_isFrozen(js_State *J) +{ + js_Object *obj; + + if (!js_isobject(J, 1)) + js_typeerror(J, "not an object"); + + obj = js_toobject(J, 1); + + if (obj->properties->level) { + if (!O_isFrozen_walk(J, obj->properties)) { + js_pushboolean(J, 0); + return; + } + } + + js_pushboolean(J, !obj->extensible); +} + +void jsB_initobject(js_State *J) +{ + js_pushobject(J, J->Object_prototype); + { + jsB_propf(J, "Object.prototype.toString", Op_toString, 0); + jsB_propf(J, "Object.prototype.toLocaleString", Op_toString, 0); + jsB_propf(J, "Object.prototype.valueOf", Op_valueOf, 0); + jsB_propf(J, "Object.prototype.hasOwnProperty", Op_hasOwnProperty, 1); + jsB_propf(J, "Object.prototype.isPrototypeOf", Op_isPrototypeOf, 1); + jsB_propf(J, "Object.prototype.propertyIsEnumerable", Op_propertyIsEnumerable, 1); + } + js_newcconstructor(J, jsB_Object, jsB_new_Object, "Object", 1); + { + /* ES5 */ + jsB_propf(J, "Object.getPrototypeOf", O_getPrototypeOf, 1); + jsB_propf(J, "Object.getOwnPropertyDescriptor", O_getOwnPropertyDescriptor, 2); + jsB_propf(J, "Object.getOwnPropertyNames", O_getOwnPropertyNames, 1); + jsB_propf(J, "Object.create", O_create, 2); + jsB_propf(J, "Object.defineProperty", O_defineProperty, 3); + jsB_propf(J, "Object.defineProperties", O_defineProperties, 2); + jsB_propf(J, "Object.seal", O_seal, 1); + jsB_propf(J, "Object.freeze", O_freeze, 1); + jsB_propf(J, "Object.preventExtensions", O_preventExtensions, 1); + jsB_propf(J, "Object.isSealed", O_isSealed, 1); + jsB_propf(J, "Object.isFrozen", O_isFrozen, 1); + jsB_propf(J, "Object.isExtensible", O_isExtensible, 1); + jsB_propf(J, "Object.keys", O_keys, 1); + } + js_defglobal(J, "Object", JS_DONTENUM); +} diff --git a/mujs/json.c b/mujs/json.c new file mode 100644 index 0000000..6dd8f09 --- /dev/null +++ b/mujs/json.c @@ -0,0 +1,422 @@ +#include "jsi.h" +#include "utf.h" + +int js_isnumberobject(js_State *J, int idx) +{ + return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CNUMBER; +} + +int js_isstringobject(js_State *J, int idx) +{ + return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CSTRING; +} + +int js_isbooleanobject(js_State *J, int idx) +{ + return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CBOOLEAN; +} + +int js_isdateobject(js_State *J, int idx) +{ + return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CDATE; +} + +static void jsonnext(js_State *J) +{ + J->lookahead = jsY_lexjson(J); +} + +static int jsonaccept(js_State *J, int t) +{ + if (J->lookahead == t) { + jsonnext(J); + return 1; + } + return 0; +} + +static void jsonexpect(js_State *J, int t) +{ + if (!jsonaccept(J, t)) + js_syntaxerror(J, "JSON: unexpected token: %s (expected %s)", + jsY_tokenstring(J->lookahead), jsY_tokenstring(t)); +} + +static void jsonvalue(js_State *J) +{ + int i; + const char *name; + + switch (J->lookahead) { + case TK_STRING: + js_pushstring(J, J->text); + jsonnext(J); + break; + + case TK_NUMBER: + js_pushnumber(J, J->number); + jsonnext(J); + break; + + case '{': + js_newobject(J); + jsonnext(J); + if (jsonaccept(J, '}')) + return; + do { + if (J->lookahead != TK_STRING) + js_syntaxerror(J, "JSON: unexpected token: %s (expected string)", jsY_tokenstring(J->lookahead)); + name = J->text; + jsonnext(J); + jsonexpect(J, ':'); + jsonvalue(J); + js_setproperty(J, -2, name); + } while (jsonaccept(J, ',')); + jsonexpect(J, '}'); + break; + + case '[': + js_newarray(J); + jsonnext(J); + i = 0; + if (jsonaccept(J, ']')) + return; + do { + jsonvalue(J); + js_setindex(J, -2, i++); + } while (jsonaccept(J, ',')); + jsonexpect(J, ']'); + break; + + case TK_TRUE: + js_pushboolean(J, 1); + jsonnext(J); + break; + + case TK_FALSE: + js_pushboolean(J, 0); + jsonnext(J); + break; + + case TK_NULL: + js_pushnull(J); + jsonnext(J); + break; + + default: + js_syntaxerror(J, "JSON: unexpected token: %s", jsY_tokenstring(J->lookahead)); + } +} + +static void jsonrevive(js_State *J, const char *name) +{ + const char *key; + char buf[32]; + + /* revive is in 2 */ + /* holder is in -1 */ + + js_getproperty(J, -1, name); /* get value from holder */ + + if (js_isobject(J, -1)) { + if (js_isarray(J, -1)) { + int i = 0; + int n = js_getlength(J, -1); + for (i = 0; i < n; ++i) { + jsonrevive(J, js_itoa(buf, i)); + if (js_isundefined(J, -1)) { + js_pop(J, 1); + js_delproperty(J, -1, buf); + } else { + js_setproperty(J, -2, buf); + } + } + } else { + js_pushiterator(J, -1, 1); + while ((key = js_nextiterator(J, -1))) { + js_rot2(J); + jsonrevive(J, key); + if (js_isundefined(J, -1)) { + js_pop(J, 1); + js_delproperty(J, -1, key); + } else { + js_setproperty(J, -2, key); + } + js_rot2(J); + } + js_pop(J, 1); + } + } + + js_copy(J, 2); /* reviver function */ + js_copy(J, -3); /* holder as this */ + js_pushstring(J, name); /* name */ + js_copy(J, -4); /* value */ + js_call(J, 2); + js_rot2pop1(J); /* pop old value, leave new value on stack */ +} + +static void JSON_parse(js_State *J) +{ + const char *source = js_tostring(J, 1); + jsY_initlex(J, "JSON", source); + jsonnext(J); + + if (js_iscallable(J, 2)) { + js_newobject(J); + jsonvalue(J); + js_defproperty(J, -2, "", 0); + jsonrevive(J, ""); + } else { + jsonvalue(J); + } +} + +static void fmtnum(js_State *J, js_Buffer **sb, double n) +{ + if (isnan(n)) js_puts(J, sb, "null"); + else if (isinf(n)) js_puts(J, sb, "null"); + else if (n == 0) js_puts(J, sb, "0"); + else { + char buf[40]; + js_puts(J, sb, jsV_numbertostring(J, buf, n)); + } +} + +static void fmtstr(js_State *J, js_Buffer **sb, const char *s) +{ + static const char *HEX = "0123456789abcdef"; + int i, n; + Rune c; + js_putc(J, sb, '"'); + while (*s) { + n = chartorune(&c, s); + switch (c) { + case '"': js_puts(J, sb, "\\\""); break; + case '\\': js_puts(J, sb, "\\\\"); break; + case '\b': js_puts(J, sb, "\\b"); break; + case '\f': js_puts(J, sb, "\\f"); break; + case '\n': js_puts(J, sb, "\\n"); break; + case '\r': js_puts(J, sb, "\\r"); break; + case '\t': js_puts(J, sb, "\\t"); break; + default: + if (c < ' ' || (c >= 0xd800 && c <= 0xdfff)) { + js_putc(J, sb, '\\'); + js_putc(J, sb, 'u'); + js_putc(J, sb, HEX[(c>>12)&15]); + js_putc(J, sb, HEX[(c>>8)&15]); + js_putc(J, sb, HEX[(c>>4)&15]); + js_putc(J, sb, HEX[c&15]); + } else if (c < 128) { + js_putc(J, sb, c); + } else { + for (i = 0; i < n; ++i) + js_putc(J, sb, s[i]); + } + break; + } + s += n; + } + js_putc(J, sb, '"'); +} + +static void fmtindent(js_State *J, js_Buffer **sb, const char *gap, int level) +{ + js_putc(J, sb, '\n'); + while (level--) + js_puts(J, sb, gap); +} + +static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level); + +static int filterprop(js_State *J, const char *key) +{ + int i, n, found; + /* replacer/property-list is in stack slot 2 */ + if (js_isarray(J, 2)) { + found = 0; + n = js_getlength(J, 2); + for (i = 0; i < n && !found; ++i) { + js_getindex(J, 2, i); + if (js_isstring(J, -1) || js_isnumber(J, -1) || + js_isstringobject(J, -1) || js_isnumberobject(J, -1)) + found = !strcmp(key, js_tostring(J, -1)); + js_pop(J, 1); + } + return found; + } + return 1; +} + +static void fmtobject(js_State *J, js_Buffer **sb, js_Object *obj, const char *gap, int level) +{ + const char *key; + int save; + int i, n; + + n = js_gettop(J) - 1; + for (i = 4; i < n; ++i) + if (js_isobject(J, i)) + if (js_toobject(J, i) == js_toobject(J, -1)) + js_typeerror(J, "cyclic object value"); + + n = 0; + js_putc(J, sb, '{'); + js_pushiterator(J, -1, 1); + while ((key = js_nextiterator(J, -1))) { + if (filterprop(J, key)) { + save = (*sb)->n; + if (n) js_putc(J, sb, ','); + if (gap) fmtindent(J, sb, gap, level + 1); + fmtstr(J, sb, key); + js_putc(J, sb, ':'); + if (gap) + js_putc(J, sb, ' '); + js_rot2(J); + if (!fmtvalue(J, sb, key, gap, level + 1)) + (*sb)->n = save; + else + ++n; + js_rot2(J); + } + } + js_pop(J, 1); + if (gap && n) fmtindent(J, sb, gap, level); + js_putc(J, sb, '}'); +} + +static void fmtarray(js_State *J, js_Buffer **sb, const char *gap, int level) +{ + int n, i; + char buf[32]; + + n = js_gettop(J) - 1; + for (i = 4; i < n; ++i) + if (js_isobject(J, i)) + if (js_toobject(J, i) == js_toobject(J, -1)) + js_typeerror(J, "cyclic object value"); + + js_putc(J, sb, '['); + n = js_getlength(J, -1); + for (i = 0; i < n; ++i) { + if (i) js_putc(J, sb, ','); + if (gap) fmtindent(J, sb, gap, level + 1); + if (!fmtvalue(J, sb, js_itoa(buf, i), gap, level + 1)) + js_puts(J, sb, "null"); + } + if (gap && n) fmtindent(J, sb, gap, level); + js_putc(J, sb, ']'); +} + +static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level) +{ + /* replacer/property-list is in 2 */ + /* holder is in -1 */ + + js_getproperty(J, -1, key); + + if (js_isobject(J, -1)) { + if (js_hasproperty(J, -1, "toJSON")) { + if (js_iscallable(J, -1)) { + js_copy(J, -2); + js_pushstring(J, key); + js_call(J, 1); + js_rot2pop1(J); + } else { + js_pop(J, 1); + } + } + } + + if (js_iscallable(J, 2)) { + js_copy(J, 2); /* replacer function */ + js_copy(J, -3); /* holder as this */ + js_pushstring(J, key); /* name */ + js_copy(J, -4); /* old value */ + js_call(J, 2); + js_rot2pop1(J); /* pop old value, leave new value on stack */ + } + + if (js_isobject(J, -1) && !js_iscallable(J, -1)) { + js_Object *obj = js_toobject(J, -1); + switch (obj->type) { + case JS_CNUMBER: fmtnum(J, sb, obj->u.number); break; + case JS_CSTRING: fmtstr(J, sb, obj->u.s.string); break; + case JS_CBOOLEAN: js_puts(J, sb, obj->u.boolean ? "true" : "false"); break; + case JS_CARRAY: fmtarray(J, sb, gap, level); break; + default: fmtobject(J, sb, obj, gap, level); break; + } + } + else if (js_isboolean(J, -1)) + js_puts(J, sb, js_toboolean(J, -1) ? "true" : "false"); + else if (js_isnumber(J, -1)) + fmtnum(J, sb, js_tonumber(J, -1)); + else if (js_isstring(J, -1)) + fmtstr(J, sb, js_tostring(J, -1)); + else if (js_isnull(J, -1)) + js_puts(J, sb, "null"); + else { + js_pop(J, 1); + return 0; + } + + js_pop(J, 1); + return 1; +} + +static void JSON_stringify(js_State *J) +{ + js_Buffer *sb = NULL; + char buf[12]; + /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */ + const char * volatile gap; + const char *s; + int n; + + gap = NULL; + + if (js_isnumber(J, 3) || js_isnumberobject(J, 3)) { + n = js_tointeger(J, 3); + if (n < 0) n = 0; + if (n > 10) n = 10; + memset(buf, ' ', n); + buf[n] = 0; + if (n > 0) gap = buf; + } else if (js_isstring(J, 3) || js_isstringobject(J, 3)) { + s = js_tostring(J, 3); + n = strlen(s); + if (n > 10) n = 10; + memcpy(buf, s, n); + buf[n] = 0; + if (n > 0) gap = buf; + } + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + + js_newobject(J); /* wrapper */ + js_copy(J, 1); + js_defproperty(J, -2, "", 0); + if (!fmtvalue(J, &sb, "", gap, 0)) { + js_pushundefined(J); + } else { + js_putc(J, &sb, 0); + js_pushstring(J, sb ? sb->s : ""); + js_rot2pop1(J); + } + + js_endtry(J); + js_free(J, sb); +} + +void jsB_initjson(js_State *J) +{ + js_pushobject(J, jsV_newobject(J, JS_CJSON, J->Object_prototype)); + { + jsB_propf(J, "JSON.parse", JSON_parse, 2); + jsB_propf(J, "JSON.stringify", JSON_stringify, 3); + } + js_defglobal(J, "JSON", JS_DONTENUM); +} diff --git a/mujs/jsparse.c b/mujs/jsparse.c new file mode 100644 index 0000000..3f55f10 --- /dev/null +++ b/mujs/jsparse.c @@ -0,0 +1,1065 @@ +#include "jsi.h" + +#define LIST(h) jsP_newnode(J, AST_LIST, 0, h, 0, 0, 0) + +#define EXP0(x) jsP_newnode(J, EXP_ ## x, line, 0, 0, 0, 0) +#define EXP1(x,a) jsP_newnode(J, EXP_ ## x, line, a, 0, 0, 0) +#define EXP2(x,a,b) jsP_newnode(J, EXP_ ## x, line, a, b, 0, 0) +#define EXP3(x,a,b,c) jsP_newnode(J, EXP_ ## x, line, a, b, c, 0) + +#define STM0(x) jsP_newnode(J, STM_ ## x, line, 0, 0, 0, 0) +#define STM1(x,a) jsP_newnode(J, STM_ ## x, line, a, 0, 0, 0) +#define STM2(x,a,b) jsP_newnode(J, STM_ ## x, line, a, b, 0, 0) +#define STM3(x,a,b,c) jsP_newnode(J, STM_ ## x, line, a, b, c, 0) +#define STM4(x,a,b,c,d) jsP_newnode(J, STM_ ## x, line, a, b, c, d) + +static js_Ast *expression(js_State *J, int notin); +static js_Ast *assignment(js_State *J, int notin); +static js_Ast *memberexp(js_State *J); +static js_Ast *statement(js_State *J); +static js_Ast *funbody(js_State *J); + +JS_NORETURN static void jsP_error(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); + +#define INCREC() if (++J->astdepth > JS_ASTLIMIT) jsP_error(J, "too much recursion") +#define DECREC() --J->astdepth +#define SAVEREC() int SAVE=J->astdepth +#define POPREC() J->astdepth=SAVE + +static void jsP_error(js_State *J, const char *fmt, ...) +{ + va_list ap; + char buf[512]; + char msgbuf[256]; + + va_start(ap, fmt); + vsnprintf(msgbuf, 256, fmt, ap); + va_end(ap); + + snprintf(buf, 256, "%s:%d: ", J->filename, J->lexline); + strcat(buf, msgbuf); + + js_newsyntaxerror(J, buf); + js_throw(J); +} + +static void jsP_warning(js_State *J, const char *fmt, ...) +{ + va_list ap; + char buf[512]; + char msg[256]; + + va_start(ap, fmt); + vsnprintf(msg, sizeof msg, fmt, ap); + va_end(ap); + + snprintf(buf, sizeof buf, "%s:%d: warning: %s", J->filename, J->lexline, msg); + js_report(J, buf); +} + +static js_Ast *jsP_newnode(js_State *J, enum js_AstType type, int line, js_Ast *a, js_Ast *b, js_Ast *c, js_Ast *d) +{ + js_Ast *node = js_malloc(J, sizeof *node); + + node->type = type; + node->line = line; + node->a = a; + node->b = b; + node->c = c; + node->d = d; + node->number = 0; + node->string = NULL; + node->jumps = NULL; + node->casejump = 0; + + node->parent = NULL; + if (a) a->parent = node; + if (b) b->parent = node; + if (c) c->parent = node; + if (d) d->parent = node; + + node->gcnext = J->gcast; + J->gcast = node; + + return node; +} + +static js_Ast *jsP_list(js_Ast *head) +{ + /* set parent pointers in list nodes */ + js_Ast *prev = head, *node = head->b; + while (node) { + node->parent = prev; + prev = node; + node = node->b; + } + return head; +} + +static js_Ast *jsP_newstrnode(js_State *J, enum js_AstType type, const char *s) +{ + js_Ast *node = jsP_newnode(J, type, J->lexline, 0, 0, 0, 0); + node->string = s; + return node; +} + +static js_Ast *jsP_newnumnode(js_State *J, enum js_AstType type, double n) +{ + js_Ast *node = jsP_newnode(J, type, J->lexline, 0, 0, 0, 0); + node->number = n; + return node; +} + +static void jsP_freejumps(js_State *J, js_JumpList *node) +{ + while (node) { + js_JumpList *next = node->next; + js_free(J, node); + node = next; + } +} + +void jsP_freeparse(js_State *J) +{ + js_Ast *node = J->gcast; + while (node) { + js_Ast *next = node->gcnext; + jsP_freejumps(J, node->jumps); + js_free(J, node); + node = next; + } + J->gcast = NULL; +} + +/* Lookahead */ + +static void jsP_next(js_State *J) +{ + J->lookahead = jsY_lex(J); +} + +#define jsP_accept(J,x) (J->lookahead == x ? (jsP_next(J), 1) : 0) + +#define jsP_expect(J,x) if (!jsP_accept(J, x)) jsP_error(J, "unexpected token: %s (expected %s)", jsY_tokenstring(J->lookahead), jsY_tokenstring(x)) + +static void semicolon(js_State *J) +{ + if (J->lookahead == ';') { + jsP_next(J); + return; + } + if (J->newline || J->lookahead == '}' || J->lookahead == 0) + return; + jsP_error(J, "unexpected token: %s (expected ';')", jsY_tokenstring(J->lookahead)); +} + +/* Literals */ + +static js_Ast *identifier(js_State *J) +{ + js_Ast *a; + if (J->lookahead == TK_IDENTIFIER) { + a = jsP_newstrnode(J, AST_IDENTIFIER, J->text); + jsP_next(J); + return a; + } + jsP_error(J, "unexpected token: %s (expected identifier)", jsY_tokenstring(J->lookahead)); +} + +static js_Ast *identifieropt(js_State *J) +{ + if (J->lookahead == TK_IDENTIFIER) + return identifier(J); + return NULL; +} + +static js_Ast *identifiername(js_State *J) +{ + if (J->lookahead == TK_IDENTIFIER || J->lookahead >= TK_BREAK) { + js_Ast *a = jsP_newstrnode(J, AST_IDENTIFIER, J->text); + jsP_next(J); + return a; + } + jsP_error(J, "unexpected token: %s (expected identifier or keyword)", jsY_tokenstring(J->lookahead)); +} + +static js_Ast *arrayelement(js_State *J) +{ + int line = J->lexline; + if (J->lookahead == ',') + return EXP0(ELISION); + return assignment(J, 0); +} + +static js_Ast *arrayliteral(js_State *J) +{ + js_Ast *head, *tail; + if (J->lookahead == ']') + return NULL; + head = tail = LIST(arrayelement(J)); + while (jsP_accept(J, ',')) { + if (J->lookahead != ']') + tail = tail->b = LIST(arrayelement(J)); + } + return jsP_list(head); +} + +static js_Ast *propname(js_State *J) +{ + js_Ast *name; + if (J->lookahead == TK_NUMBER) { + name = jsP_newnumnode(J, EXP_NUMBER, J->number); + jsP_next(J); + } else if (J->lookahead == TK_STRING) { + name = jsP_newstrnode(J, EXP_STRING, J->text); + jsP_next(J); + } else { + name = identifiername(J); + } + return name; +} + +static js_Ast *propassign(js_State *J) +{ + js_Ast *name, *value, *arg, *body; + int line = J->lexline; + + name = propname(J); + + if (J->lookahead != ':' && name->type == AST_IDENTIFIER) { + if (!strcmp(name->string, "get")) { + name = propname(J); + jsP_expect(J, '('); + jsP_expect(J, ')'); + body = funbody(J); + return EXP3(PROP_GET, name, NULL, body); + } + if (!strcmp(name->string, "set")) { + name = propname(J); + jsP_expect(J, '('); + arg = identifier(J); + jsP_expect(J, ')'); + body = funbody(J); + return EXP3(PROP_SET, name, LIST(arg), body); + } + } + + jsP_expect(J, ':'); + value = assignment(J, 0); + return EXP2(PROP_VAL, name, value); +} + +static js_Ast *objectliteral(js_State *J) +{ + js_Ast *head, *tail; + if (J->lookahead == '}') + return NULL; + head = tail = LIST(propassign(J)); + while (jsP_accept(J, ',')) { + if (J->lookahead == '}') + break; + tail = tail->b = LIST(propassign(J)); + } + return jsP_list(head); +} + +/* Functions */ + +static js_Ast *parameters(js_State *J) +{ + js_Ast *head, *tail; + if (J->lookahead == ')') + return NULL; + head = tail = LIST(identifier(J)); + while (jsP_accept(J, ',')) { + tail = tail->b = LIST(identifier(J)); + } + return jsP_list(head); +} + +static js_Ast *fundec(js_State *J, int line) +{ + js_Ast *a, *b, *c; + a = identifier(J); + jsP_expect(J, '('); + b = parameters(J); + jsP_expect(J, ')'); + c = funbody(J); + return jsP_newnode(J, AST_FUNDEC, line, a, b, c, 0); +} + +static js_Ast *funstm(js_State *J, int line) +{ + js_Ast *a, *b, *c; + a = identifier(J); + jsP_expect(J, '('); + b = parameters(J); + jsP_expect(J, ')'); + c = funbody(J); + /* rewrite function statement as "var X = function X() {}" */ + return STM1(VAR, LIST(EXP2(VAR, a, EXP3(FUN, a, b, c)))); +} + +static js_Ast *funexp(js_State *J, int line) +{ + js_Ast *a, *b, *c; + a = identifieropt(J); + jsP_expect(J, '('); + b = parameters(J); + jsP_expect(J, ')'); + c = funbody(J); + return EXP3(FUN, a, b, c); +} + +/* Expressions */ + +static js_Ast *primary(js_State *J) +{ + js_Ast *a; + int line = J->lexline; + + if (J->lookahead == TK_IDENTIFIER) { + a = jsP_newstrnode(J, EXP_IDENTIFIER, J->text); + jsP_next(J); + return a; + } + if (J->lookahead == TK_STRING) { + a = jsP_newstrnode(J, EXP_STRING, J->text); + jsP_next(J); + return a; + } + if (J->lookahead == TK_REGEXP) { + a = jsP_newstrnode(J, EXP_REGEXP, J->text); + a->number = J->number; + jsP_next(J); + return a; + } + if (J->lookahead == TK_NUMBER) { + a = jsP_newnumnode(J, EXP_NUMBER, J->number); + jsP_next(J); + return a; + } + + if (jsP_accept(J, TK_THIS)) return EXP0(THIS); + if (jsP_accept(J, TK_NULL)) return EXP0(NULL); + if (jsP_accept(J, TK_TRUE)) return EXP0(TRUE); + if (jsP_accept(J, TK_FALSE)) return EXP0(FALSE); + if (jsP_accept(J, '{')) { + a = EXP1(OBJECT, objectliteral(J)); + jsP_expect(J, '}'); + return a; + } + if (jsP_accept(J, '[')) { + a = EXP1(ARRAY, arrayliteral(J)); + jsP_expect(J, ']'); + return a; + } + if (jsP_accept(J, '(')) { + a = expression(J, 0); + jsP_expect(J, ')'); + return a; + } + + jsP_error(J, "unexpected token in expression: %s", jsY_tokenstring(J->lookahead)); +} + +static js_Ast *arguments(js_State *J) +{ + js_Ast *head, *tail; + if (J->lookahead == ')') + return NULL; + head = tail = LIST(assignment(J, 0)); + while (jsP_accept(J, ',')) { + tail = tail->b = LIST(assignment(J, 0)); + } + return jsP_list(head); +} + +static js_Ast *newexp(js_State *J) +{ + js_Ast *a, *b; + int line = J->lexline; + + if (jsP_accept(J, TK_NEW)) { + a = memberexp(J); + if (jsP_accept(J, '(')) { + b = arguments(J); + jsP_expect(J, ')'); + return EXP2(NEW, a, b); + } + return EXP1(NEW, a); + } + + if (jsP_accept(J, TK_FUNCTION)) + return funexp(J, line); + + return primary(J); +} + +static js_Ast *memberexp(js_State *J) +{ + js_Ast *a = newexp(J); + int line; + SAVEREC(); +loop: + INCREC(); + line = J->lexline; + if (jsP_accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; } + if (jsP_accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); jsP_expect(J, ']'); goto loop; } + POPREC(); + return a; +} + +static js_Ast *callexp(js_State *J) +{ + js_Ast *a = newexp(J); + int line; + SAVEREC(); +loop: + INCREC(); + line = J->lexline; + if (jsP_accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; } + if (jsP_accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); jsP_expect(J, ']'); goto loop; } + if (jsP_accept(J, '(')) { a = EXP2(CALL, a, arguments(J)); jsP_expect(J, ')'); goto loop; } + POPREC(); + return a; +} + +static js_Ast *postfix(js_State *J) +{ + js_Ast *a = callexp(J); + int line = J->lexline; + if (!J->newline && jsP_accept(J, TK_INC)) return EXP1(POSTINC, a); + if (!J->newline && jsP_accept(J, TK_DEC)) return EXP1(POSTDEC, a); + return a; +} + +static js_Ast *unary(js_State *J) +{ + js_Ast *a; + int line = J->lexline; + INCREC(); + if (jsP_accept(J, TK_DELETE)) a = EXP1(DELETE, unary(J)); + else if (jsP_accept(J, TK_VOID)) a = EXP1(VOID, unary(J)); + else if (jsP_accept(J, TK_TYPEOF)) a = EXP1(TYPEOF, unary(J)); + else if (jsP_accept(J, TK_INC)) a = EXP1(PREINC, unary(J)); + else if (jsP_accept(J, TK_DEC)) a = EXP1(PREDEC, unary(J)); + else if (jsP_accept(J, '+')) a = EXP1(POS, unary(J)); + else if (jsP_accept(J, '-')) a = EXP1(NEG, unary(J)); + else if (jsP_accept(J, '~')) a = EXP1(BITNOT, unary(J)); + else if (jsP_accept(J, '!')) a = EXP1(LOGNOT, unary(J)); + else a = postfix(J); + DECREC(); + return a; +} + +static js_Ast *multiplicative(js_State *J) +{ + js_Ast *a = unary(J); + int line; + SAVEREC(); +loop: + INCREC(); + line = J->lexline; + if (jsP_accept(J, '*')) { a = EXP2(MUL, a, unary(J)); goto loop; } + if (jsP_accept(J, '/')) { a = EXP2(DIV, a, unary(J)); goto loop; } + if (jsP_accept(J, '%')) { a = EXP2(MOD, a, unary(J)); goto loop; } + POPREC(); + return a; +} + +static js_Ast *additive(js_State *J) +{ + js_Ast *a = multiplicative(J); + int line; + SAVEREC(); +loop: + INCREC(); + line = J->lexline; + if (jsP_accept(J, '+')) { a = EXP2(ADD, a, multiplicative(J)); goto loop; } + if (jsP_accept(J, '-')) { a = EXP2(SUB, a, multiplicative(J)); goto loop; } + POPREC(); + return a; +} + +static js_Ast *shift(js_State *J) +{ + js_Ast *a = additive(J); + int line; + SAVEREC(); +loop: + INCREC(); + line = J->lexline; + if (jsP_accept(J, TK_SHL)) { a = EXP2(SHL, a, additive(J)); goto loop; } + if (jsP_accept(J, TK_SHR)) { a = EXP2(SHR, a, additive(J)); goto loop; } + if (jsP_accept(J, TK_USHR)) { a = EXP2(USHR, a, additive(J)); goto loop; } + POPREC(); + return a; +} + +static js_Ast *relational(js_State *J, int notin) +{ + js_Ast *a = shift(J); + int line; + SAVEREC(); +loop: + INCREC(); + line = J->lexline; + if (jsP_accept(J, '<')) { a = EXP2(LT, a, shift(J)); goto loop; } + if (jsP_accept(J, '>')) { a = EXP2(GT, a, shift(J)); goto loop; } + if (jsP_accept(J, TK_LE)) { a = EXP2(LE, a, shift(J)); goto loop; } + if (jsP_accept(J, TK_GE)) { a = EXP2(GE, a, shift(J)); goto loop; } + if (jsP_accept(J, TK_INSTANCEOF)) { a = EXP2(INSTANCEOF, a, shift(J)); goto loop; } + if (!notin && jsP_accept(J, TK_IN)) { a = EXP2(IN, a, shift(J)); goto loop; } + POPREC(); + return a; +} + +static js_Ast *equality(js_State *J, int notin) +{ + js_Ast *a = relational(J, notin); + int line; + SAVEREC(); +loop: + INCREC(); + line = J->lexline; + if (jsP_accept(J, TK_EQ)) { a = EXP2(EQ, a, relational(J, notin)); goto loop; } + if (jsP_accept(J, TK_NE)) { a = EXP2(NE, a, relational(J, notin)); goto loop; } + if (jsP_accept(J, TK_STRICTEQ)) { a = EXP2(STRICTEQ, a, relational(J, notin)); goto loop; } + if (jsP_accept(J, TK_STRICTNE)) { a = EXP2(STRICTNE, a, relational(J, notin)); goto loop; } + POPREC(); + return a; +} + +static js_Ast *bitand(js_State *J, int notin) +{ + js_Ast *a = equality(J, notin); + SAVEREC(); + int line = J->lexline; + while (jsP_accept(J, '&')) { + INCREC(); + a = EXP2(BITAND, a, equality(J, notin)); + line = J->lexline; + } + POPREC(); + return a; +} + +static js_Ast *bitxor(js_State *J, int notin) +{ + js_Ast *a = bitand(J, notin); + SAVEREC(); + int line = J->lexline; + while (jsP_accept(J, '^')) { + INCREC(); + a = EXP2(BITXOR, a, bitand(J, notin)); + line = J->lexline; + } + POPREC(); + return a; +} + +static js_Ast *bitor(js_State *J, int notin) +{ + js_Ast *a = bitxor(J, notin); + SAVEREC(); + int line = J->lexline; + while (jsP_accept(J, '|')) { + INCREC(); + a = EXP2(BITOR, a, bitxor(J, notin)); + line = J->lexline; + } + POPREC(); + return a; +} + +static js_Ast *logand(js_State *J, int notin) +{ + js_Ast *a = bitor(J, notin); + int line = J->lexline; + if (jsP_accept(J, TK_AND)) { + INCREC(); + a = EXP2(LOGAND, a, logand(J, notin)); + DECREC(); + } + return a; +} + +static js_Ast *logor(js_State *J, int notin) +{ + js_Ast *a = logand(J, notin); + int line = J->lexline; + if (jsP_accept(J, TK_OR)) { + INCREC(); + a = EXP2(LOGOR, a, logor(J, notin)); + DECREC(); + } + return a; +} + +static js_Ast *conditional(js_State *J, int notin) +{ + js_Ast *a = logor(J, notin); + int line = J->lexline; + if (jsP_accept(J, '?')) { + js_Ast *b, *c; + INCREC(); + b = assignment(J, 0); + jsP_expect(J, ':'); + c = assignment(J, notin); + DECREC(); + return EXP3(COND, a, b, c); + } + return a; +} + +static js_Ast *assignment(js_State *J, int notin) +{ + js_Ast *a = conditional(J, notin); + int line = J->lexline; + INCREC(); + if (jsP_accept(J, '=')) a = EXP2(ASS, a, assignment(J, notin)); + else if (jsP_accept(J, TK_MUL_ASS)) a = EXP2(ASS_MUL, a, assignment(J, notin)); + else if (jsP_accept(J, TK_DIV_ASS)) a = EXP2(ASS_DIV, a, assignment(J, notin)); + else if (jsP_accept(J, TK_MOD_ASS)) a = EXP2(ASS_MOD, a, assignment(J, notin)); + else if (jsP_accept(J, TK_ADD_ASS)) a = EXP2(ASS_ADD, a, assignment(J, notin)); + else if (jsP_accept(J, TK_SUB_ASS)) a = EXP2(ASS_SUB, a, assignment(J, notin)); + else if (jsP_accept(J, TK_SHL_ASS)) a = EXP2(ASS_SHL, a, assignment(J, notin)); + else if (jsP_accept(J, TK_SHR_ASS)) a = EXP2(ASS_SHR, a, assignment(J, notin)); + else if (jsP_accept(J, TK_USHR_ASS)) a = EXP2(ASS_USHR, a, assignment(J, notin)); + else if (jsP_accept(J, TK_AND_ASS)) a = EXP2(ASS_BITAND, a, assignment(J, notin)); + else if (jsP_accept(J, TK_XOR_ASS)) a = EXP2(ASS_BITXOR, a, assignment(J, notin)); + else if (jsP_accept(J, TK_OR_ASS)) a = EXP2(ASS_BITOR, a, assignment(J, notin)); + DECREC(); + return a; +} + +static js_Ast *expression(js_State *J, int notin) +{ + js_Ast *a = assignment(J, notin); + SAVEREC(); + int line = J->lexline; + while (jsP_accept(J, ',')) { + INCREC(); + a = EXP2(COMMA, a, assignment(J, notin)); + line = J->lexline; + } + POPREC(); + return a; +} + +/* Statements */ + +static js_Ast *vardec(js_State *J, int notin) +{ + js_Ast *a = identifier(J); + int line = J->lexline; + if (jsP_accept(J, '=')) + return EXP2(VAR, a, assignment(J, notin)); + return EXP1(VAR, a); +} + +static js_Ast *vardeclist(js_State *J, int notin) +{ + js_Ast *head, *tail; + head = tail = LIST(vardec(J, notin)); + while (jsP_accept(J, ',')) + tail = tail->b = LIST(vardec(J, notin)); + return jsP_list(head); +} + +static js_Ast *statementlist(js_State *J) +{ + js_Ast *head, *tail; + if (J->lookahead == '}' || J->lookahead == TK_CASE || J->lookahead == TK_DEFAULT) + return NULL; + head = tail = LIST(statement(J)); + while (J->lookahead != '}' && J->lookahead != TK_CASE && J->lookahead != TK_DEFAULT) + tail = tail->b = LIST(statement(J)); + return jsP_list(head); +} + +static js_Ast *caseclause(js_State *J) +{ + js_Ast *a, *b; + int line = J->lexline; + + if (jsP_accept(J, TK_CASE)) { + a = expression(J, 0); + jsP_expect(J, ':'); + b = statementlist(J); + return STM2(CASE, a, b); + } + + if (jsP_accept(J, TK_DEFAULT)) { + jsP_expect(J, ':'); + a = statementlist(J); + return STM1(DEFAULT, a); + } + + jsP_error(J, "unexpected token in switch: %s (expected 'case' or 'default')", jsY_tokenstring(J->lookahead)); +} + +static js_Ast *caselist(js_State *J) +{ + js_Ast *head, *tail; + if (J->lookahead == '}') + return NULL; + head = tail = LIST(caseclause(J)); + while (J->lookahead != '}') + tail = tail->b = LIST(caseclause(J)); + return jsP_list(head); +} + +static js_Ast *block(js_State *J) +{ + js_Ast *a; + int line = J->lexline; + jsP_expect(J, '{'); + a = statementlist(J); + jsP_expect(J, '}'); + return STM1(BLOCK, a); +} + +static js_Ast *forexpression(js_State *J, int end) +{ + js_Ast *a = NULL; + if (J->lookahead != end) + a = expression(J, 0); + jsP_expect(J, end); + return a; +} + +static js_Ast *forstatement(js_State *J, int line) +{ + js_Ast *a, *b, *c, *d; + jsP_expect(J, '('); + if (jsP_accept(J, TK_VAR)) { + a = vardeclist(J, 1); + if (jsP_accept(J, ';')) { + b = forexpression(J, ';'); + c = forexpression(J, ')'); + d = statement(J); + return STM4(FOR_VAR, a, b, c, d); + } + if (jsP_accept(J, TK_IN)) { + b = expression(J, 0); + jsP_expect(J, ')'); + c = statement(J); + return STM3(FOR_IN_VAR, a, b, c); + } + jsP_error(J, "unexpected token in for-var-statement: %s", jsY_tokenstring(J->lookahead)); + } + + if (J->lookahead != ';') + a = expression(J, 1); + else + a = NULL; + if (jsP_accept(J, ';')) { + b = forexpression(J, ';'); + c = forexpression(J, ')'); + d = statement(J); + return STM4(FOR, a, b, c, d); + } + if (jsP_accept(J, TK_IN)) { + b = expression(J, 0); + jsP_expect(J, ')'); + c = statement(J); + return STM3(FOR_IN, a, b, c); + } + jsP_error(J, "unexpected token in for-statement: %s", jsY_tokenstring(J->lookahead)); +} + +static js_Ast *statement(js_State *J) +{ + js_Ast *a, *b, *c, *d; + js_Ast *stm; + int line = J->lexline; + + INCREC(); + + if (J->lookahead == '{') { + stm = block(J); + } + + else if (jsP_accept(J, TK_VAR)) { + a = vardeclist(J, 0); + semicolon(J); + stm = STM1(VAR, a); + } + + /* empty statement */ + else if (jsP_accept(J, ';')) { + stm = STM0(EMPTY); + } + + else if (jsP_accept(J, TK_IF)) { + jsP_expect(J, '('); + a = expression(J, 0); + jsP_expect(J, ')'); + b = statement(J); + if (jsP_accept(J, TK_ELSE)) + c = statement(J); + else + c = NULL; + stm = STM3(IF, a, b, c); + } + + else if (jsP_accept(J, TK_DO)) { + a = statement(J); + jsP_expect(J, TK_WHILE); + jsP_expect(J, '('); + b = expression(J, 0); + jsP_expect(J, ')'); + semicolon(J); + stm = STM2(DO, a, b); + } + + else if (jsP_accept(J, TK_WHILE)) { + jsP_expect(J, '('); + a = expression(J, 0); + jsP_expect(J, ')'); + b = statement(J); + stm = STM2(WHILE, a, b); + } + + else if (jsP_accept(J, TK_FOR)) { + stm = forstatement(J, line); + } + + else if (jsP_accept(J, TK_CONTINUE)) { + a = identifieropt(J); + semicolon(J); + stm = STM1(CONTINUE, a); + } + + else if (jsP_accept(J, TK_BREAK)) { + a = identifieropt(J); + semicolon(J); + stm = STM1(BREAK, a); + } + + else if (jsP_accept(J, TK_RETURN)) { + if (J->lookahead != ';' && J->lookahead != '}' && J->lookahead != 0) + a = expression(J, 0); + else + a = NULL; + semicolon(J); + stm = STM1(RETURN, a); + } + + else if (jsP_accept(J, TK_WITH)) { + jsP_expect(J, '('); + a = expression(J, 0); + jsP_expect(J, ')'); + b = statement(J); + stm = STM2(WITH, a, b); + } + + else if (jsP_accept(J, TK_SWITCH)) { + jsP_expect(J, '('); + a = expression(J, 0); + jsP_expect(J, ')'); + jsP_expect(J, '{'); + b = caselist(J); + jsP_expect(J, '}'); + stm = STM2(SWITCH, a, b); + } + + else if (jsP_accept(J, TK_THROW)) { + a = expression(J, 0); + semicolon(J); + stm = STM1(THROW, a); + } + + else if (jsP_accept(J, TK_TRY)) { + a = block(J); + b = c = d = NULL; + if (jsP_accept(J, TK_CATCH)) { + jsP_expect(J, '('); + b = identifier(J); + jsP_expect(J, ')'); + c = block(J); + } + if (jsP_accept(J, TK_FINALLY)) { + d = block(J); + } + if (!b && !d) + jsP_error(J, "unexpected token in try: %s (expected 'catch' or 'finally')", jsY_tokenstring(J->lookahead)); + stm = STM4(TRY, a, b, c, d); + } + + else if (jsP_accept(J, TK_DEBUGGER)) { + semicolon(J); + stm = STM0(DEBUGGER); + } + + else if (jsP_accept(J, TK_FUNCTION)) { + jsP_warning(J, "function statements are not standard"); + stm = funstm(J, line); + } + + /* labelled statement or expression statement */ + else if (J->lookahead == TK_IDENTIFIER) { + a = expression(J, 0); + if (a->type == EXP_IDENTIFIER && jsP_accept(J, ':')) { + a->type = AST_IDENTIFIER; + b = statement(J); + stm = STM2(LABEL, a, b); + } else { + semicolon(J); + stm = a; + } + } + + /* expression statement */ + else { + stm = expression(J, 0); + semicolon(J); + } + + DECREC(); + return stm; +} + +/* Program */ + +static js_Ast *scriptelement(js_State *J) +{ + int line = J->lexline; + if (jsP_accept(J, TK_FUNCTION)) + return fundec(J, line); + return statement(J); +} + +static js_Ast *script(js_State *J, int terminator) +{ + js_Ast *head, *tail; + if (J->lookahead == terminator) + return NULL; + head = tail = LIST(scriptelement(J)); + while (J->lookahead != terminator) + tail = tail->b = LIST(scriptelement(J)); + return jsP_list(head); +} + +static js_Ast *funbody(js_State *J) +{ + js_Ast *a; + jsP_expect(J, '{'); + a = script(J, '}'); + jsP_expect(J, '}'); + return a; +} + +/* Constant folding */ + +static int toint32(double d) +{ + double two32 = 4294967296.0; + double two31 = 2147483648.0; + + if (!isfinite(d) || d == 0) + return 0; + + d = fmod(d, two32); + d = d >= 0 ? floor(d) : ceil(d) + two32; + if (d >= two31) + return d - two32; + else + return d; +} + +static unsigned int touint32(double d) +{ + return (unsigned int)toint32(d); +} + +static int jsP_setnumnode(js_Ast *node, double x) +{ + node->type = EXP_NUMBER; + node->number = x; + node->a = node->b = node->c = node->d = NULL; + return 1; +} + +static int jsP_foldconst(js_Ast *node) +{ + double x, y; + int a, b; + + if (node->type == AST_LIST) { + while (node) { + jsP_foldconst(node->a); + node = node->b; + } + return 0; + } + + if (node->type == EXP_NUMBER) + return 1; + + a = node->a ? jsP_foldconst(node->a) : 0; + b = node->b ? jsP_foldconst(node->b) : 0; + if (node->c) jsP_foldconst(node->c); + if (node->d) jsP_foldconst(node->d); + + if (a) { + x = node->a->number; + switch (node->type) { + default: break; + case EXP_NEG: return jsP_setnumnode(node, -x); + case EXP_POS: return jsP_setnumnode(node, x); + case EXP_BITNOT: return jsP_setnumnode(node, ~toint32(x)); + } + + if (b) { + y = node->b->number; + switch (node->type) { + default: break; + case EXP_MUL: return jsP_setnumnode(node, x * y); + case EXP_DIV: return jsP_setnumnode(node, x / y); + case EXP_MOD: return jsP_setnumnode(node, fmod(x, y)); + case EXP_ADD: return jsP_setnumnode(node, x + y); + case EXP_SUB: return jsP_setnumnode(node, x - y); + case EXP_SHL: return jsP_setnumnode(node, toint32(x) << (touint32(y) & 0x1F)); + case EXP_SHR: return jsP_setnumnode(node, toint32(x) >> (touint32(y) & 0x1F)); + case EXP_USHR: return jsP_setnumnode(node, touint32(x) >> (touint32(y) & 0x1F)); + case EXP_BITAND: return jsP_setnumnode(node, toint32(x) & toint32(y)); + case EXP_BITXOR: return jsP_setnumnode(node, toint32(x) ^ toint32(y)); + case EXP_BITOR: return jsP_setnumnode(node, toint32(x) | toint32(y)); + } + } + } + + return 0; +} + +/* Main entry point */ + +js_Ast *jsP_parse(js_State *J, const char *filename, const char *source) +{ + js_Ast *p; + + jsY_initlex(J, filename, source); + jsP_next(J); + J->astdepth = 0; + p = script(J, 0); + if (p) + jsP_foldconst(p); + + return p; +} + +js_Ast *jsP_parsefunction(js_State *J, const char *filename, const char *params, const char *body) +{ + js_Ast *p = NULL; + int line = 0; + if (params) { + jsY_initlex(J, filename, params); + jsP_next(J); + J->astdepth = 0; + p = parameters(J); + } + return EXP3(FUN, NULL, p, jsP_parse(J, filename, body)); +} diff --git a/mujs/jsproperty.c b/mujs/jsproperty.c new file mode 100644 index 0000000..7d6e271 --- /dev/null +++ b/mujs/jsproperty.c @@ -0,0 +1,341 @@ +#include "jsi.h" + +#include + +/* + Use an AA-tree to quickly look up properties in objects: + + The level of every leaf node is one. + The level of every left child is one less than its parent. + The level of every right child is equal or one less than its parent. + The level of every right grandchild is less than its grandparent. + Every node of level greater than one has two children. + + A link where the child's level is equal to that of its parent is called a horizontal link. + Individual right horizontal links are allowed, but consecutive ones are forbidden. + Left horizontal links are forbidden. + + skew() fixes left horizontal links. + split() fixes consecutive right horizontal links. +*/ + +static js_Property sentinel = { + &sentinel, &sentinel, + 0, 0, + { { {0}, JS_TUNDEFINED } }, + NULL, NULL, "" +}; + +static js_Property *newproperty(js_State *J, js_Object *obj, const char *name) +{ + int n = strlen(name) + 1; + js_Property *node = js_malloc(J, offsetof(js_Property, name) + n); + node->left = node->right = &sentinel; + node->level = 1; + node->atts = 0; + node->value.t.type = JS_TUNDEFINED; + node->value.u.number = 0; + node->getter = NULL; + node->setter = NULL; + memcpy(node->name, name, n); + ++obj->count; + ++J->gccounter; + return node; +} + +static js_Property *lookup(js_Property *node, const char *name) +{ + while (node != &sentinel) { + int c = strcmp(name, node->name); + if (c == 0) + return node; + else if (c < 0) + node = node->left; + else + node = node->right; + } + return NULL; +} + +static js_Property *skew(js_Property *node) +{ + if (node->left->level == node->level) { + js_Property *temp = node; + node = node->left; + temp->left = node->right; + node->right = temp; + } + return node; +} + +static js_Property *split(js_Property *node) +{ + if (node->right->right->level == node->level) { + js_Property *temp = node; + node = node->right; + temp->right = node->left; + node->left = temp; + ++node->level; + } + return node; +} + +static js_Property *insert(js_State *J, js_Object *obj, js_Property *node, const char *name, js_Property **result) +{ + if (node != &sentinel) { + int c = strcmp(name, node->name); + if (c < 0) + node->left = insert(J, obj, node->left, name, result); + else if (c > 0) + node->right = insert(J, obj, node->right, name, result); + else + return *result = node; + node = skew(node); + node = split(node); + return node; + } + return *result = newproperty(J, obj, name); +} + +static void freeproperty(js_State *J, js_Object *obj, js_Property *node) +{ + js_free(J, node); + --obj->count; +} + +static js_Property *unlinkproperty(js_Property *node, const char *name, js_Property **garbage) +{ + js_Property *temp, *a, *b; + if (node != &sentinel) { + int c = strcmp(name, node->name); + if (c < 0) { + node->left = unlinkproperty(node->left, name, garbage); + } else if (c > 0) { + node->right = unlinkproperty(node->right, name, garbage); + } else { + *garbage = node; + if (node->left == &sentinel && node->right == &sentinel) { + return &sentinel; + } + else if (node->left == &sentinel) { + a = node->right; + while (a->left != &sentinel) + a = a->left; + b = unlinkproperty(node->right, a->name, &temp); + temp->level = node->level; + temp->left = node->left; + temp->right = b; + node = temp; + } + else { + a = node->left; + while (a->right != &sentinel) + a = a->right; + b = unlinkproperty(node->left, a->name, &temp); + temp->level = node->level; + temp->left = b; + temp->right = node->right; + node = temp; + } + } + + if (node->left->level < node->level - 1 || node->right->level < node->level - 1) + { + if (node->right->level > --node->level) + node->right->level = node->level; + node = skew(node); + node->right = skew(node->right); + node->right->right = skew(node->right->right); + node = split(node); + node->right = split(node->right); + } + } + return node; +} + +static js_Property *deleteproperty(js_State *J, js_Object *obj, js_Property *tree, const char *name) +{ + js_Property *garbage = &sentinel; + tree = unlinkproperty(tree, name, &garbage); + if (garbage != &sentinel) + freeproperty(J, obj, garbage); + return tree; +} + +js_Object *jsV_newobject(js_State *J, enum js_Class type, js_Object *prototype) +{ + js_Object *obj = js_malloc(J, sizeof *obj); + memset(obj, 0, sizeof *obj); + obj->gcmark = 0; + obj->gcnext = J->gcobj; + J->gcobj = obj; + ++J->gccounter; + + obj->type = type; + obj->properties = &sentinel; + obj->prototype = prototype; + obj->extensible = 1; + return obj; +} + +js_Property *jsV_getownproperty(js_State *J, js_Object *obj, const char *name) +{ + return lookup(obj->properties, name); +} + +js_Property *jsV_getpropertyx(js_State *J, js_Object *obj, const char *name, int *own) +{ + *own = 1; + do { + js_Property *ref = lookup(obj->properties, name); + if (ref) + return ref; + obj = obj->prototype; + *own = 0; + } while (obj); + return NULL; +} + +js_Property *jsV_getproperty(js_State *J, js_Object *obj, const char *name) +{ + do { + js_Property *ref = lookup(obj->properties, name); + if (ref) + return ref; + obj = obj->prototype; + } while (obj); + return NULL; +} + +static js_Property *jsV_getenumproperty(js_State *J, js_Object *obj, const char *name) +{ + do { + js_Property *ref = lookup(obj->properties, name); + if (ref && !(ref->atts & JS_DONTENUM)) + return ref; + obj = obj->prototype; + } while (obj); + return NULL; +} + +js_Property *jsV_setproperty(js_State *J, js_Object *obj, const char *name) +{ + js_Property *result; + + if (!obj->extensible) { + result = lookup(obj->properties, name); + if (J->strict && !result) + js_typeerror(J, "object is non-extensible"); + return result; + } + + obj->properties = insert(J, obj, obj->properties, name, &result); + + return result; +} + +void jsV_delproperty(js_State *J, js_Object *obj, const char *name) +{ + obj->properties = deleteproperty(J, obj, obj->properties, name); +} + +/* Flatten hierarchy of enumerable properties into an iterator object */ + +static js_Iterator *itnewnode(js_State *J, const char *name, js_Iterator *next) { + int n = strlen(name) + 1; + js_Iterator *node = js_malloc(J, offsetof(js_Iterator, name) + n); + node->next = next; + memcpy(node->name, name, n); + return node; +} + +static js_Iterator *itwalk(js_State *J, js_Iterator *iter, js_Property *prop, js_Object *seen) +{ + if (prop->right != &sentinel) + iter = itwalk(J, iter, prop->right, seen); + if (!(prop->atts & JS_DONTENUM)) { + if (!seen || !jsV_getenumproperty(J, seen, prop->name)) { + iter = itnewnode(J, prop->name, iter); + } + } + if (prop->left != &sentinel) + iter = itwalk(J, iter, prop->left, seen); + return iter; +} + +static js_Iterator *itflatten(js_State *J, js_Object *obj) +{ + js_Iterator *iter = NULL; + if (obj->prototype) + iter = itflatten(J, obj->prototype); + if (obj->properties != &sentinel) + iter = itwalk(J, iter, obj->properties, obj->prototype); + return iter; +} + +js_Object *jsV_newiterator(js_State *J, js_Object *obj, int own) +{ + js_Object *io = jsV_newobject(J, JS_CITERATOR, NULL); + io->u.iter.target = obj; + io->u.iter.i = 0; + io->u.iter.n = 0; + if (own) { + io->u.iter.head = NULL; + if (obj->properties != &sentinel) + io->u.iter.head = itwalk(J, io->u.iter.head, obj->properties, NULL); + } else { + io->u.iter.head = itflatten(J, obj); + } + io->u.iter.current = io->u.iter.head; + + if (obj->type == JS_CSTRING) + io->u.iter.n = obj->u.s.length; + + if (obj->type == JS_CARRAY && obj->u.a.simple) + io->u.iter.n = obj->u.a.flat_length; + + return io; +} + +const char *jsV_nextiterator(js_State *J, js_Object *io) +{ + if (io->type != JS_CITERATOR) + js_typeerror(J, "not an iterator"); + if (io->u.iter.i < io->u.iter.n) { + js_itoa(J->scratch, io->u.iter.i); + io->u.iter.i++; + return J->scratch; + } + while (io->u.iter.current) { + const char *name = io->u.iter.current->name; + io->u.iter.current = io->u.iter.current->next; + if (jsV_getproperty(J, io->u.iter.target, name)) + return name; + } + return NULL; +} + +/* Walk all the properties and delete them one by one for arrays */ + +void jsV_resizearray(js_State *J, js_Object *obj, int newlen) +{ + char buf[32]; + const char *s; + int k; + assert(!obj->u.a.simple); + if (newlen < obj->u.a.length) { + if (obj->u.a.length > obj->count * 2) { + js_Object *it = jsV_newiterator(J, obj, 1); + while ((s = jsV_nextiterator(J, it))) { + k = jsV_numbertointeger(jsV_stringtonumber(J, s)); + if (k >= newlen && !strcmp(s, jsV_numbertostring(J, buf, k))) + jsV_delproperty(J, obj, s); + } + } else { + for (k = newlen; k < obj->u.a.length; ++k) { + jsV_delproperty(J, obj, js_itoa(buf, k)); + } + } + } + obj->u.a.length = newlen; +} diff --git a/mujs/jsregexp.c b/mujs/jsregexp.c new file mode 100644 index 0000000..85c4a0d --- /dev/null +++ b/mujs/jsregexp.c @@ -0,0 +1,232 @@ +#include "jsi.h" +#include "regexp.h" + +static char *escaperegexp(js_State *J, const char *pattern) { + char *copy, *p; + const char *s; + int n = 0; + for (s = pattern; *s; ++s) { + if (*s == '/') + ++n; + ++n; + } + copy = p = js_malloc(J, n+1); + for (s = pattern; *s; ++s) { + if (*s == '/') + *p++ = '\\'; + *p++ = *s; + } + *p = 0; + return copy; +} + +static void js_newregexpx(js_State *J, const char *pattern, int flags, int is_clone) +{ + const char *error; + js_Object *obj; + Reprog *prog; + int opts; + + obj = jsV_newobject(J, JS_CREGEXP, J->RegExp_prototype); + + opts = 0; + if (flags & JS_REGEXP_I) opts |= REG_ICASE; + if (flags & JS_REGEXP_M) opts |= REG_NEWLINE; + + prog = js_regcompx(J->alloc, J->actx, pattern, opts, &error); + if (!prog) + js_syntaxerror(J, "regular expression: %s", error); + + obj->u.r.prog = prog; + obj->u.r.source = is_clone ? js_strdup(J, pattern) : escaperegexp(J, pattern); + obj->u.r.flags = flags; + obj->u.r.last = 0; + js_pushobject(J, obj); +} + +void js_newregexp(js_State *J, const char *pattern, int flags) +{ + js_newregexpx(J, pattern, flags, 0); +} + +void js_RegExp_prototype_exec(js_State *J, js_Regexp *re, const char *text) +{ + const char *haystack; + int result; + int i; + int opts; + Resub m; + + haystack = text; + opts = 0; + if (re->flags & JS_REGEXP_G) { + if (re->last > strlen(haystack)) { + re->last = 0; + js_pushnull(J); + return; + } + if (re->last > 0) { + haystack = text + re->last; + opts |= REG_NOTBOL; + } + } + + result = js_regexec(re->prog, haystack, &m, opts); + if (result < 0) + js_error(J, "regexec failed"); + if (result == 0) { + js_newarray(J); + js_pushstring(J, text); + js_setproperty(J, -2, "input"); + js_pushnumber(J, js_utfptrtoidx(text, m.sub[0].sp)); + js_setproperty(J, -2, "index"); + for (i = 0; i < m.nsub; ++i) { + js_pushlstring(J, m.sub[i].sp, m.sub[i].ep - m.sub[i].sp); + js_setindex(J, -2, i); + } + if (re->flags & JS_REGEXP_G) + re->last = m.sub[0].ep - text; + return; + } + + if (re->flags & JS_REGEXP_G) + re->last = 0; + + js_pushnull(J); +} + +static void Rp_test(js_State *J) +{ + js_Regexp *re; + const char *text; + int result; + int opts; + Resub m; + + re = js_toregexp(J, 0); + text = js_tostring(J, 1); + + opts = 0; + if (re->flags & JS_REGEXP_G) { + if (re->last > strlen(text)) { + re->last = 0; + js_pushboolean(J, 0); + return; + } + if (re->last > 0) { + text += re->last; + opts |= REG_NOTBOL; + } + } + + result = js_regexec(re->prog, text, &m, opts); + if (result < 0) + js_error(J, "regexec failed"); + if (result == 0) { + if (re->flags & JS_REGEXP_G) + re->last = re->last + (m.sub[0].ep - text); + js_pushboolean(J, 1); + return; + } + + if (re->flags & JS_REGEXP_G) + re->last = 0; + + js_pushboolean(J, 0); +} + +static void jsB_new_RegExp(js_State *J) +{ + js_Regexp *old; + const char *pattern; + int flags; + int is_clone = 0; + + if (js_isregexp(J, 1)) { + if (js_isdefined(J, 2)) + js_typeerror(J, "cannot supply flags when creating one RegExp from another"); + old = js_toregexp(J, 1); + pattern = old->source; + flags = old->flags; + is_clone = 1; + } else if (js_isundefined(J, 1)) { + pattern = "(?:)"; + flags = 0; + } else { + pattern = js_tostring(J, 1); + flags = 0; + } + + if (strlen(pattern) == 0) + pattern = "(?:)"; + + if (js_isdefined(J, 2)) { + const char *s = js_tostring(J, 2); + int g = 0, i = 0, m = 0; + while (*s) { + if (*s == 'g') ++g; + else if (*s == 'i') ++i; + else if (*s == 'm') ++m; + else js_syntaxerror(J, "invalid regular expression flag: '%c'", *s); + ++s; + } + if (g > 1) js_syntaxerror(J, "invalid regular expression flag: 'g'"); + if (i > 1) js_syntaxerror(J, "invalid regular expression flag: 'i'"); + if (m > 1) js_syntaxerror(J, "invalid regular expression flag: 'm'"); + if (g) flags |= JS_REGEXP_G; + if (i) flags |= JS_REGEXP_I; + if (m) flags |= JS_REGEXP_M; + } + + js_newregexpx(J, pattern, flags, is_clone); +} + +static void jsB_RegExp(js_State *J) +{ + if (js_isregexp(J, 1)) + return; + jsB_new_RegExp(J); +} + +static void Rp_toString(js_State *J) +{ + js_Regexp *re; + char * volatile out = NULL; + + re = js_toregexp(J, 0); + + if (js_try(J)) { + js_free(J, out); + js_throw(J); + } + + out = js_malloc(J, strlen(re->source) + 6); /* extra space for //gim */ + strcpy(out, "/"); + strcat(out, re->source); + strcat(out, "/"); + if (re->flags & JS_REGEXP_G) strcat(out, "g"); + if (re->flags & JS_REGEXP_I) strcat(out, "i"); + if (re->flags & JS_REGEXP_M) strcat(out, "m"); + + js_pop(J, 0); + js_pushstring(J, out); + js_endtry(J); + js_free(J, out); +} + +static void Rp_exec(js_State *J) +{ + js_RegExp_prototype_exec(J, js_toregexp(J, 0), js_tostring(J, 1)); +} + +void jsB_initregexp(js_State *J) +{ + js_pushobject(J, J->RegExp_prototype); + { + jsB_propf(J, "RegExp.prototype.toString", Rp_toString, 0); + jsB_propf(J, "RegExp.prototype.test", Rp_test, 0); + jsB_propf(J, "RegExp.prototype.exec", Rp_exec, 0); + } + js_newcconstructor(J, jsB_RegExp, jsB_new_RegExp, "RegExp", 1); + js_defglobal(J, "RegExp", JS_DONTENUM); +} diff --git a/mujs/jsrepr.c b/mujs/jsrepr.c new file mode 100644 index 0000000..6d7e4d3 --- /dev/null +++ b/mujs/jsrepr.c @@ -0,0 +1,285 @@ +#include "jsi.h" +#include "utf.h" + +static void reprvalue(js_State *J, js_Buffer **sb); + +static void reprnum(js_State *J, js_Buffer **sb, double n) +{ + char buf[40]; + if (n == 0 && signbit(n)) + js_puts(J, sb, "-0"); + else + js_puts(J, sb, jsV_numbertostring(J, buf, n)); +} + +static void reprstr(js_State *J, js_Buffer **sb, const char *s) +{ + static const char *HEX = "0123456789ABCDEF"; + int i, n; + Rune c; + js_putc(J, sb, '"'); + while (*s) { + n = chartorune(&c, s); + switch (c) { + case '"': js_puts(J, sb, "\\\""); break; + case '\\': js_puts(J, sb, "\\\\"); break; + case '\b': js_puts(J, sb, "\\b"); break; + case '\f': js_puts(J, sb, "\\f"); break; + case '\n': js_puts(J, sb, "\\n"); break; + case '\r': js_puts(J, sb, "\\r"); break; + case '\t': js_puts(J, sb, "\\t"); break; + default: + if (c < ' ') { + js_putc(J, sb, '\\'); + js_putc(J, sb, 'x'); + js_putc(J, sb, HEX[(c>>4)&15]); + js_putc(J, sb, HEX[c&15]); + } else if (c < 128) { + js_putc(J, sb, c); + } else if (c < 0x10000) { + js_putc(J, sb, '\\'); + js_putc(J, sb, 'u'); + js_putc(J, sb, HEX[(c>>12)&15]); + js_putc(J, sb, HEX[(c>>8)&15]); + js_putc(J, sb, HEX[(c>>4)&15]); + js_putc(J, sb, HEX[c&15]); + } else { + for (i = 0; i < n; ++i) + js_putc(J, sb, s[i]); + } + break; + } + s += n; + } + js_putc(J, sb, '"'); +} + +#ifndef isalpha +#define isalpha(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) +#endif +#ifndef isdigit +#define isdigit(c) (c >= '0' && c <= '9') +#endif + +static void reprident(js_State *J, js_Buffer **sb, const char *name) +{ + const char *p = name; + if (isdigit(*p)) + while (isdigit(*p)) + ++p; + else if (isalpha(*p) || *p == '_') + while (isdigit(*p) || isalpha(*p) || *p == '_') + ++p; + if (p > name && *p == 0) + js_puts(J, sb, name); + else + reprstr(J, sb, name); +} + +static void reprobject(js_State *J, js_Buffer **sb) +{ + const char *key; + int i, n; + + n = js_gettop(J) - 1; + for (i = 0; i < n; ++i) { + if (js_isobject(J, i)) { + if (js_toobject(J, i) == js_toobject(J, -1)) { + js_puts(J, sb, "{}"); + return; + } + } + } + + n = 0; + js_putc(J, sb, '{'); + js_pushiterator(J, -1, 1); + while ((key = js_nextiterator(J, -1))) { + if (n++ > 0) + js_puts(J, sb, ", "); + reprident(J, sb, key); + js_puts(J, sb, ": "); + js_getproperty(J, -2, key); + reprvalue(J, sb); + js_pop(J, 1); + } + js_pop(J, 1); + js_putc(J, sb, '}'); +} + +static void reprarray(js_State *J, js_Buffer **sb) +{ + int n, i; + + n = js_gettop(J) - 1; + for (i = 0; i < n; ++i) { + if (js_isobject(J, i)) { + if (js_toobject(J, i) == js_toobject(J, -1)) { + js_puts(J, sb, "[]"); + return; + } + } + } + + js_putc(J, sb, '['); + n = js_getlength(J, -1); + for (i = 0; i < n; ++i) { + if (i > 0) + js_puts(J, sb, ", "); + if (js_hasindex(J, -1, i)) { + reprvalue(J, sb); + js_pop(J, 1); + } + } + js_putc(J, sb, ']'); +} + +static void reprfun(js_State *J, js_Buffer **sb, js_Function *fun) +{ + int i; + js_puts(J, sb, "function "); + js_puts(J, sb, fun->name); + js_putc(J, sb, '('); + for (i = 0; i < fun->numparams; ++i) { + if (i > 0) + js_puts(J, sb, ", "); + js_puts(J, sb, fun->vartab[i]); + } + js_puts(J, sb, ") { [byte code] }"); +} + +static void reprvalue(js_State *J, js_Buffer **sb) +{ + if (js_isundefined(J, -1)) + js_puts(J, sb, "undefined"); + else if (js_isnull(J, -1)) + js_puts(J, sb, "null"); + else if (js_isboolean(J, -1)) + js_puts(J, sb, js_toboolean(J, -1) ? "true" : "false"); + else if (js_isnumber(J, -1)) + reprnum(J, sb, js_tonumber(J, -1)); + else if (js_isstring(J, -1)) + reprstr(J, sb, js_tostring(J, -1)); + else if (js_isobject(J, -1)) { + js_Object *obj = js_toobject(J, -1); + switch (obj->type) { + default: + reprobject(J, sb); + break; + case JS_CARRAY: + reprarray(J, sb); + break; + case JS_CFUNCTION: + case JS_CSCRIPT: + reprfun(J, sb, obj->u.f.function); + break; + case JS_CCFUNCTION: + js_puts(J, sb, "function "); + js_puts(J, sb, obj->u.c.name); + js_puts(J, sb, "() { [native code] }"); + break; + case JS_CBOOLEAN: + js_puts(J, sb, "(new Boolean("); + js_puts(J, sb, obj->u.boolean ? "true" : "false"); + js_puts(J, sb, "))"); + break; + case JS_CNUMBER: + js_puts(J, sb, "(new Number("); + reprnum(J, sb, obj->u.number); + js_puts(J, sb, "))"); + break; + case JS_CSTRING: + js_puts(J, sb, "(new String("); + reprstr(J, sb, obj->u.s.string); + js_puts(J, sb, "))"); + break; + case JS_CREGEXP: + js_putc(J, sb, '/'); + js_puts(J, sb, obj->u.r.source); + js_putc(J, sb, '/'); + if (obj->u.r.flags & JS_REGEXP_G) js_putc(J, sb, 'g'); + if (obj->u.r.flags & JS_REGEXP_I) js_putc(J, sb, 'i'); + if (obj->u.r.flags & JS_REGEXP_M) js_putc(J, sb, 'm'); + break; + case JS_CDATE: + { + char buf[40]; + js_puts(J, sb, "(new Date("); + js_puts(J, sb, jsV_numbertostring(J, buf, obj->u.number)); + js_puts(J, sb, "))"); + } + break; + case JS_CERROR: + js_puts(J, sb, "(new "); + js_getproperty(J, -1, "name"); + js_puts(J, sb, js_tostring(J, -1)); + js_pop(J, 1); + js_putc(J, sb, '('); + if (js_hasproperty(J, -1, "message")) { + reprvalue(J, sb); + js_pop(J, 1); + } + js_puts(J, sb, "))"); + break; + case JS_CMATH: + js_puts(J, sb, "Math"); + break; + case JS_CJSON: + js_puts(J, sb, "JSON"); + break; + case JS_CITERATOR: + js_puts(J, sb, "[iterator "); + break; + case JS_CUSERDATA: + js_puts(J, sb, "[userdata "); + js_puts(J, sb, obj->u.user.tag); + js_putc(J, sb, ']'); + break; + } + } +} + +void js_repr(js_State *J, int idx) +{ + js_Buffer *sb = NULL; + int savebot; + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + + js_copy(J, idx); + + savebot = J->bot; + J->bot = J->top - 1; + reprvalue(J, &sb); + J->bot = savebot; + + js_pop(J, 1); + + js_putc(J, &sb, 0); + js_pushstring(J, sb ? sb->s : "undefined"); + + js_endtry(J); + js_free(J, sb); +} + +const char *js_torepr(js_State *J, int idx) +{ + js_repr(J, idx); + js_replace(J, idx < 0 ? idx-1 : idx); + return js_tostring(J, idx); +} + +const char *js_tryrepr(js_State *J, int idx, const char *error) +{ + const char *s; + if (js_try(J)) { + js_pop(J, 1); + return error; + } + s = js_torepr(J, idx); + js_endtry(J); + return s; +} diff --git a/mujs/jsrun.c b/mujs/jsrun.c new file mode 100644 index 0000000..b956208 --- /dev/null +++ b/mujs/jsrun.c @@ -0,0 +1,2063 @@ +#include "jsi.h" +#include "utf.h" + +#include + +static void jsR_run(js_State *J, js_Function *F); + +/* Push values on stack */ + +#define STACK (J->stack) +#define TOP (J->top) +#define BOT (J->bot) + +static void js_trystackoverflow(js_State *J) +{ + STACK[TOP].t.type = JS_TLITSTR; + STACK[TOP].u.litstr = "exception stack overflow"; + ++TOP; + js_throw(J); +} + +static void js_stackoverflow(js_State *J) +{ + STACK[TOP].t.type = JS_TLITSTR; + STACK[TOP].u.litstr = "stack overflow"; + ++TOP; + js_throw(J); +} + +static void js_outofmemory(js_State *J) +{ + STACK[TOP].t.type = JS_TLITSTR; + STACK[TOP].u.litstr = "out of memory"; + ++TOP; + js_throw(J); +} + +void *js_malloc(js_State *J, int size) +{ + void *ptr = J->alloc(J->actx, NULL, size); + if (!ptr) + js_outofmemory(J); + return ptr; +} + +void *js_realloc(js_State *J, void *ptr, int size) +{ + ptr = J->alloc(J->actx, ptr, size); + if (!ptr) + js_outofmemory(J); + return ptr; +} + +char *js_strdup(js_State *J, const char *s) +{ + int n = strlen(s) + 1; + char *p = js_malloc(J, n); + memcpy(p, s, n); + return p; +} + +void js_free(js_State *J, void *ptr) +{ + J->alloc(J->actx, ptr, 0); +} + +js_String *jsV_newmemstring(js_State *J, const char *s, int n) +{ + js_String *v = js_malloc(J, soffsetof(js_String, p) + n + 1); + memcpy(v->p, s, n); + v->p[n] = 0; + v->gcmark = 0; + v->gcnext = J->gcstr; + J->gcstr = v; + ++J->gccounter; + return v; +} + +#define CHECKSTACK(n) if (TOP + n >= JS_STACKSIZE) js_stackoverflow(J) + +void js_pushvalue(js_State *J, js_Value v) +{ + CHECKSTACK(1); + STACK[TOP] = v; + ++TOP; +} + +void js_pushundefined(js_State *J) +{ + CHECKSTACK(1); + STACK[TOP].t.type = JS_TUNDEFINED; + ++TOP; +} + +void js_pushnull(js_State *J) +{ + CHECKSTACK(1); + STACK[TOP].t.type = JS_TNULL; + ++TOP; +} + +void js_pushboolean(js_State *J, int v) +{ + CHECKSTACK(1); + STACK[TOP].t.type = JS_TBOOLEAN; + STACK[TOP].u.boolean = !!v; + ++TOP; +} + +void js_pushnumber(js_State *J, double v) +{ + CHECKSTACK(1); + STACK[TOP].t.type = JS_TNUMBER; + STACK[TOP].u.number = v; + ++TOP; +} + +void js_pushstring(js_State *J, const char *v) +{ + size_t n = strlen(v); + if (n > JS_STRLIMIT) + js_rangeerror(J, "invalid string length"); + CHECKSTACK(1); + if (n <= soffsetof(js_Value, t.type)) { + char *s = STACK[TOP].u.shrstr; + while (n--) *s++ = *v++; + *s = 0; + STACK[TOP].t.type = JS_TSHRSTR; + } else { + STACK[TOP].t.type = JS_TMEMSTR; + STACK[TOP].u.memstr = jsV_newmemstring(J, v, n); + } + ++TOP; +} + +void js_pushlstring(js_State *J, const char *v, int n) +{ + if (n > JS_STRLIMIT) + js_rangeerror(J, "invalid string length"); + CHECKSTACK(1); + if (n <= soffsetof(js_Value, t.type)) { + char *s = STACK[TOP].u.shrstr; + while (n--) *s++ = *v++; + *s = 0; + STACK[TOP].t.type = JS_TSHRSTR; + } else { + STACK[TOP].t.type = JS_TMEMSTR; + STACK[TOP].u.memstr = jsV_newmemstring(J, v, n); + } + ++TOP; +} + +void js_pushliteral(js_State *J, const char *v) +{ + CHECKSTACK(1); + STACK[TOP].t.type = JS_TLITSTR; + STACK[TOP].u.litstr = v; + ++TOP; +} + +void js_pushobject(js_State *J, js_Object *v) +{ + CHECKSTACK(1); + STACK[TOP].t.type = JS_TOBJECT; + STACK[TOP].u.object = v; + ++TOP; +} + +void js_pushglobal(js_State *J) +{ + js_pushobject(J, J->G); +} + +void js_currentfunction(js_State *J) +{ + CHECKSTACK(1); + if (BOT > 0) + STACK[TOP] = STACK[BOT-1]; + else + STACK[TOP].t.type = JS_TUNDEFINED; + ++TOP; +} + +void *js_currentfunctiondata(js_State *J) +{ + if (BOT > 0) + return STACK[BOT-1].u.object->u.c.data; + return NULL; +} + +/* Read values from stack */ + +static js_Value *stackidx(js_State *J, int idx) +{ + static js_Value undefined = { { {0}, JS_TUNDEFINED } }; + idx = idx < 0 ? TOP + idx : BOT + idx; + if (idx < 0 || idx >= TOP) + return &undefined; + return STACK + idx; +} + +js_Value *js_tovalue(js_State *J, int idx) +{ + return stackidx(J, idx); +} + +int js_isdefined(js_State *J, int idx) { return stackidx(J, idx)->t.type != JS_TUNDEFINED; } +int js_isundefined(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TUNDEFINED; } +int js_isnull(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TNULL; } +int js_isboolean(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TBOOLEAN; } +int js_isnumber(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TNUMBER; } +int js_isstring(js_State *J, int idx) { enum js_Type t = stackidx(J, idx)->t.type; return t == JS_TSHRSTR || t == JS_TLITSTR || t == JS_TMEMSTR; } +int js_isprimitive(js_State *J, int idx) { return stackidx(J, idx)->t.type != JS_TOBJECT; } +int js_isobject(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TOBJECT; } +int js_iscoercible(js_State *J, int idx) { js_Value *v = stackidx(J, idx); return v->t.type != JS_TUNDEFINED && v->t.type != JS_TNULL; } + +int js_iscallable(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + if (v->t.type == JS_TOBJECT) + return v->u.object->type == JS_CFUNCTION || + v->u.object->type == JS_CSCRIPT || + v->u.object->type == JS_CCFUNCTION; + return 0; +} + +int js_isarray(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + return v->t.type == JS_TOBJECT && v->u.object->type == JS_CARRAY; +} + +int js_isregexp(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + return v->t.type == JS_TOBJECT && v->u.object->type == JS_CREGEXP; +} + +int js_isuserdata(js_State *J, int idx, const char *tag) +{ + js_Value *v = stackidx(J, idx); + if (v->t.type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA) + return !strcmp(tag, v->u.object->u.user.tag); + return 0; +} + +int js_iserror(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + return v->t.type == JS_TOBJECT && v->u.object->type == JS_CERROR; +} + +const char *js_typeof(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + switch (v->t.type) { + default: + case JS_TSHRSTR: return "string"; + case JS_TUNDEFINED: return "undefined"; + case JS_TNULL: return "object"; + case JS_TBOOLEAN: return "boolean"; + case JS_TNUMBER: return "number"; + case JS_TLITSTR: return "string"; + case JS_TMEMSTR: return "string"; + case JS_TOBJECT: + if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION) + return "function"; + return "object"; + } +} + +int js_type(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + switch (v->t.type) { + default: + case JS_TSHRSTR: return JS_ISSTRING; + case JS_TUNDEFINED: return JS_ISUNDEFINED; + case JS_TNULL: return JS_ISNULL; + case JS_TBOOLEAN: return JS_ISBOOLEAN; + case JS_TNUMBER: return JS_ISNUMBER; + case JS_TLITSTR: return JS_ISSTRING; + case JS_TMEMSTR: return JS_ISSTRING; + case JS_TOBJECT: + if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION) + return JS_ISFUNCTION; + return JS_ISOBJECT; + } +} + +int js_toboolean(js_State *J, int idx) +{ + return jsV_toboolean(J, stackidx(J, idx)); +} + +double js_tonumber(js_State *J, int idx) +{ + return jsV_tonumber(J, stackidx(J, idx)); +} + +int js_tointeger(js_State *J, int idx) +{ + return jsV_numbertointeger(jsV_tonumber(J, stackidx(J, idx))); +} + +int js_toint32(js_State *J, int idx) +{ + return jsV_numbertoint32(jsV_tonumber(J, stackidx(J, idx))); +} + +unsigned int js_touint32(js_State *J, int idx) +{ + return jsV_numbertouint32(jsV_tonumber(J, stackidx(J, idx))); +} + +short js_toint16(js_State *J, int idx) +{ + return jsV_numbertoint16(jsV_tonumber(J, stackidx(J, idx))); +} + +unsigned short js_touint16(js_State *J, int idx) +{ + return jsV_numbertouint16(jsV_tonumber(J, stackidx(J, idx))); +} + +const char *js_tostring(js_State *J, int idx) +{ + return jsV_tostring(J, stackidx(J, idx)); +} + +js_Object *js_toobject(js_State *J, int idx) +{ + return jsV_toobject(J, stackidx(J, idx)); +} + +void js_toprimitive(js_State *J, int idx, int hint) +{ + jsV_toprimitive(J, stackidx(J, idx), hint); +} + +js_Regexp *js_toregexp(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + if (v->t.type == JS_TOBJECT && v->u.object->type == JS_CREGEXP) + return &v->u.object->u.r; + js_typeerror(J, "not a regexp"); +} + +void *js_touserdata(js_State *J, int idx, const char *tag) +{ + js_Value *v = stackidx(J, idx); + if (v->t.type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA) + if (!strcmp(tag, v->u.object->u.user.tag)) + return v->u.object->u.user.data; + js_typeerror(J, "not a %s", tag); +} + +static js_Object *jsR_tofunction(js_State *J, int idx) +{ + js_Value *v = stackidx(J, idx); + if (v->t.type == JS_TUNDEFINED || v->t.type == JS_TNULL) + return NULL; + if (v->t.type == JS_TOBJECT) + if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION) + return v->u.object; + js_typeerror(J, "not a function"); +} + +/* Stack manipulation */ + +int js_gettop(js_State *J) +{ + return TOP - BOT; +} + +void js_pop(js_State *J, int n) +{ + TOP -= n; + if (TOP < BOT) { + TOP = BOT; + js_error(J, "stack underflow!"); + } +} + +void js_remove(js_State *J, int idx) +{ + idx = idx < 0 ? TOP + idx : BOT + idx; + if (idx < BOT || idx >= TOP) + js_error(J, "stack error!"); + for (;idx < TOP - 1; ++idx) + STACK[idx] = STACK[idx+1]; + --TOP; +} + +void js_insert(js_State *J, int idx) +{ + js_error(J, "not implemented yet"); +} + +void js_replace(js_State* J, int idx) +{ + idx = idx < 0 ? TOP + idx : BOT + idx; + if (idx < BOT || idx >= TOP) + js_error(J, "stack error!"); + STACK[idx] = STACK[--TOP]; +} + +void js_copy(js_State *J, int idx) +{ + CHECKSTACK(1); + STACK[TOP] = *stackidx(J, idx); + ++TOP; +} + +void js_dup(js_State *J) +{ + CHECKSTACK(1); + STACK[TOP] = STACK[TOP-1]; + ++TOP; +} + +void js_dup2(js_State *J) +{ + CHECKSTACK(2); + STACK[TOP] = STACK[TOP-2]; + STACK[TOP+1] = STACK[TOP-1]; + TOP += 2; +} + +void js_rot2(js_State *J) +{ + /* A B -> B A */ + js_Value tmp = STACK[TOP-1]; /* A B (B) */ + STACK[TOP-1] = STACK[TOP-2]; /* A A */ + STACK[TOP-2] = tmp; /* B A */ +} + +void js_rot3(js_State *J) +{ + /* A B C -> C A B */ + js_Value tmp = STACK[TOP-1]; /* A B C (C) */ + STACK[TOP-1] = STACK[TOP-2]; /* A B B */ + STACK[TOP-2] = STACK[TOP-3]; /* A A B */ + STACK[TOP-3] = tmp; /* C A B */ +} + +void js_rot4(js_State *J) +{ + /* A B C D -> D A B C */ + js_Value tmp = STACK[TOP-1]; /* A B C D (D) */ + STACK[TOP-1] = STACK[TOP-2]; /* A B C C */ + STACK[TOP-2] = STACK[TOP-3]; /* A B B C */ + STACK[TOP-3] = STACK[TOP-4]; /* A A B C */ + STACK[TOP-4] = tmp; /* D A B C */ +} + +void js_rot2pop1(js_State *J) +{ + /* A B -> B */ + STACK[TOP-2] = STACK[TOP-1]; + --TOP; +} + +void js_rot3pop2(js_State *J) +{ + /* A B C -> C */ + STACK[TOP-3] = STACK[TOP-1]; + TOP -= 2; +} + +void js_rot(js_State *J, int n) +{ + int i; + js_Value tmp = STACK[TOP-1]; + for (i = 1; i < n; ++i) + STACK[TOP-i] = STACK[TOP-i-1]; + STACK[TOP-i] = tmp; +} + +/* Property access that takes care of attributes and getters/setters */ + +int js_isarrayindex(js_State *J, const char *p, int *idx) +{ + int n = 0; + + /* check for empty string */ + if (p[0] == 0) + return 0; + + /* check for '0' and integers with leading zero */ + if (p[0] == '0') + return (p[1] == 0) ? *idx = 0, 1 : 0; + + while (*p) { + int c = *p++; + if (c >= '0' && c <= '9') { + if (n >= INT_MAX / 10) + return 0; + n = n * 10 + (c - '0'); + } else { + return 0; + } + } + return *idx = n, 1; +} + +static void js_pushrune(js_State *J, Rune rune) +{ + char buf[UTFmax + 1]; + if (rune >= 0) { + buf[runetochar(buf, &rune)] = 0; + js_pushstring(J, buf); + } else { + js_pushundefined(J); + } +} + +void jsR_unflattenarray(js_State *J, js_Object *obj) { + if (obj->type == JS_CARRAY && obj->u.a.simple) { + js_Property *ref; + int i; + char name[32]; + if (js_try(J)) { + obj->properties = NULL; + js_throw(J); + } + for (i = 0; i < obj->u.a.flat_length; ++i) { + js_itoa(name, i); + ref = jsV_setproperty(J, obj, name); + ref->value = obj->u.a.array[i]; + } + js_free(J, obj->u.a.array); + obj->u.a.simple = 0; + obj->u.a.flat_length = 0; + obj->u.a.flat_capacity = 0; + obj->u.a.array = NULL; + js_endtry(J); + } +} + +static int jsR_hasproperty(js_State *J, js_Object *obj, const char *name) +{ + js_Property *ref; + int k; + + if (obj->type == JS_CARRAY) { + if (!strcmp(name, "length")) { + js_pushnumber(J, obj->u.a.length); + return 1; + } + if (obj->u.a.simple) { + if (js_isarrayindex(J, name, &k)) { + if (k >= 0 && k < obj->u.a.flat_length) { + js_pushvalue(J, obj->u.a.array[k]); + return 1; + } + return 0; + } + } + } + + else if (obj->type == JS_CSTRING) { + if (!strcmp(name, "length")) { + js_pushnumber(J, obj->u.s.length); + return 1; + } + if (js_isarrayindex(J, name, &k)) { + if (k >= 0 && k < obj->u.s.length) { + js_pushrune(J, js_runeat(J, obj->u.s.string, k)); + return 1; + } + } + } + + else if (obj->type == JS_CREGEXP) { + if (!strcmp(name, "source")) { + js_pushstring(J, obj->u.r.source); + return 1; + } + if (!strcmp(name, "global")) { + js_pushboolean(J, obj->u.r.flags & JS_REGEXP_G); + return 1; + } + if (!strcmp(name, "ignoreCase")) { + js_pushboolean(J, obj->u.r.flags & JS_REGEXP_I); + return 1; + } + if (!strcmp(name, "multiline")) { + js_pushboolean(J, obj->u.r.flags & JS_REGEXP_M); + return 1; + } + if (!strcmp(name, "lastIndex")) { + js_pushnumber(J, obj->u.r.last); + return 1; + } + } + + else if (obj->type == JS_CUSERDATA) { + if (obj->u.user.has && obj->u.user.has(J, obj->u.user.data, name)) + return 1; + } + + ref = jsV_getproperty(J, obj, name); + if (ref) { + if (ref->getter) { + js_pushobject(J, ref->getter); + js_pushobject(J, obj); + js_call(J, 0); + } else { + js_pushvalue(J, ref->value); + } + return 1; + } + + return 0; +} + +static void jsR_getproperty(js_State *J, js_Object *obj, const char *name) +{ + if (!jsR_hasproperty(J, obj, name)) + js_pushundefined(J); +} + +static int jsR_hasindex(js_State *J, js_Object *obj, int k) +{ + char buf[32]; + if (obj->type == JS_CARRAY && obj->u.a.simple) { + if (k >= 0 && k < obj->u.a.flat_length) { + js_pushvalue(J, obj->u.a.array[k]); + return 1; + } + return 0; + } + return jsR_hasproperty(J, obj, js_itoa(buf, k)); +} + +static void jsR_getindex(js_State *J, js_Object *obj, int k) +{ + if (!jsR_hasindex(J, obj, k)) + js_pushundefined(J); +} + +static void jsR_setarrayindex(js_State *J, js_Object *obj, int k, js_Value *value) +{ + int newlen = k + 1; + assert(obj->u.a.simple); + assert(k >= 0); + if (newlen > JS_ARRAYLIMIT) + js_rangeerror(J, "array too large"); + if (newlen > obj->u.a.flat_length) { + assert(newlen == obj->u.a.flat_length + 1); + if (newlen > obj->u.a.flat_capacity) { + int newcap = obj->u.a.flat_capacity; + if (newcap == 0) + newcap = 8; + while (newcap < newlen) + newcap <<= 1; + obj->u.a.array = js_realloc(J, obj->u.a.array, newcap * sizeof(js_Value)); + obj->u.a.flat_capacity = newcap; + } + obj->u.a.flat_length = newlen; + } + if (newlen > obj->u.a.length) + obj->u.a.length = newlen; + obj->u.a.array[k] = *value; +} + +static void jsR_setproperty(js_State *J, js_Object *obj, const char *name, int transient) +{ + js_Value *value = stackidx(J, -1); + js_Property *ref; + int k; + int own; + + if (obj->type == JS_CARRAY) { + if (!strcmp(name, "length")) { + double rawlen = jsV_tonumber(J, value); + int newlen = jsV_numbertointeger(rawlen); + if (newlen != rawlen || newlen < 0) + js_rangeerror(J, "invalid array length"); + if (newlen > JS_ARRAYLIMIT) + js_rangeerror(J, "array too large"); + if (obj->u.a.simple) { + obj->u.a.length = newlen; + if (newlen <= obj->u.a.flat_length) + obj->u.a.flat_length = newlen; + } else { + jsV_resizearray(J, obj, newlen); + } + return; + } + + if (js_isarrayindex(J, name, &k)) { + if (obj->u.a.simple) { + if (k >= 0 && k <= obj->u.a.flat_length) { + jsR_setarrayindex(J, obj, k, value); + } else { + jsR_unflattenarray(J, obj); + if (obj->u.a.length < k + 1) + obj->u.a.length = k + 1; + } + } else { + if (obj->u.a.length < k + 1) + obj->u.a.length = k + 1; + } + } + } + + else if (obj->type == JS_CSTRING) { + if (!strcmp(name, "length")) + goto readonly; + if (js_isarrayindex(J, name, &k)) + if (k >= 0 && k < obj->u.s.length) + goto readonly; + } + + else if (obj->type == JS_CREGEXP) { + if (!strcmp(name, "source")) goto readonly; + if (!strcmp(name, "global")) goto readonly; + if (!strcmp(name, "ignoreCase")) goto readonly; + if (!strcmp(name, "multiline")) goto readonly; + if (!strcmp(name, "lastIndex")) { + obj->u.r.last = jsV_tointeger(J, value); + return; + } + } + + else if (obj->type == JS_CUSERDATA) { + if (obj->u.user.put && obj->u.user.put(J, obj->u.user.data, name)) + return; + } + + /* First try to find a setter in prototype chain */ + ref = jsV_getpropertyx(J, obj, name, &own); + if (ref) { + if (ref->setter) { + js_pushobject(J, ref->setter); + js_pushobject(J, obj); + js_pushvalue(J, *value); + js_call(J, 1); + js_pop(J, 1); + return; + } else { + if (J->strict) + if (ref->getter) + js_typeerror(J, "setting property '%s' that only has a getter", name); + if (ref->atts & JS_READONLY) + goto readonly; + } + } + + /* Property not found on this object, so create one */ + if (!ref || !own) { + if (transient) { + if (J->strict) + js_typeerror(J, "cannot create property '%s' on transient object", name); + return; + } + ref = jsV_setproperty(J, obj, name); + } + + if (ref) { + if (!(ref->atts & JS_READONLY)) + ref->value = *value; + else + goto readonly; + } + + return; + +readonly: + if (J->strict) + js_typeerror(J, "'%s' is read-only", name); +} + +static void jsR_setindex(js_State *J, js_Object *obj, int k, int transient) +{ + char buf[32]; + if (obj->type == JS_CARRAY && obj->u.a.simple && k >= 0 && k <= obj->u.a.flat_length) { + jsR_setarrayindex(J, obj, k, stackidx(J, -1)); + } else { + jsR_setproperty(J, obj, js_itoa(buf, k), transient); + } +} + +static void jsR_defproperty(js_State *J, js_Object *obj, const char *name, + int atts, js_Value *value, js_Object *getter, js_Object *setter, + int throw) +{ + js_Property *ref; + int k; + + if (obj->type == JS_CARRAY) { + if (!strcmp(name, "length")) + goto readonly; + if (obj->u.a.simple) + jsR_unflattenarray(J, obj); + } + + else if (obj->type == JS_CSTRING) { + if (!strcmp(name, "length")) + goto readonly; + if (js_isarrayindex(J, name, &k)) + if (k >= 0 && k < obj->u.s.length) + goto readonly; + } + + else if (obj->type == JS_CREGEXP) { + if (!strcmp(name, "source")) goto readonly; + if (!strcmp(name, "global")) goto readonly; + if (!strcmp(name, "ignoreCase")) goto readonly; + if (!strcmp(name, "multiline")) goto readonly; + if (!strcmp(name, "lastIndex")) goto readonly; + } + + else if (obj->type == JS_CUSERDATA) { + if (obj->u.user.put && obj->u.user.put(J, obj->u.user.data, name)) + return; + } + + ref = jsV_setproperty(J, obj, name); + if (ref) { + if (value) { + if (!(ref->atts & JS_READONLY)) + ref->value = *value; + else if (J->strict) + js_typeerror(J, "'%s' is read-only", name); + } + if (getter) { + if (!(ref->atts & JS_DONTCONF)) + ref->getter = getter; + else if (J->strict) + js_typeerror(J, "'%s' is non-configurable", name); + } + if (setter) { + if (!(ref->atts & JS_DONTCONF)) + ref->setter = setter; + else if (J->strict) + js_typeerror(J, "'%s' is non-configurable", name); + } + ref->atts |= atts; + } + + return; + +readonly: + if (J->strict || throw) + js_typeerror(J, "'%s' is read-only or non-configurable", name); +} + +static int jsR_delproperty(js_State *J, js_Object *obj, const char *name) +{ + js_Property *ref; + int k; + + if (obj->type == JS_CARRAY) { + if (!strcmp(name, "length")) + goto dontconf; + if (obj->u.a.simple) + jsR_unflattenarray(J, obj); + } + + else if (obj->type == JS_CSTRING) { + if (!strcmp(name, "length")) + goto dontconf; + if (js_isarrayindex(J, name, &k)) + if (k >= 0 && k < obj->u.s.length) + goto dontconf; + } + + else if (obj->type == JS_CREGEXP) { + if (!strcmp(name, "source")) goto dontconf; + if (!strcmp(name, "global")) goto dontconf; + if (!strcmp(name, "ignoreCase")) goto dontconf; + if (!strcmp(name, "multiline")) goto dontconf; + if (!strcmp(name, "lastIndex")) goto dontconf; + } + + else if (obj->type == JS_CUSERDATA) { + if (obj->u.user.delete && obj->u.user.delete(J, obj->u.user.data, name)) + return 1; + } + + ref = jsV_getownproperty(J, obj, name); + if (ref) { + if (ref->atts & JS_DONTCONF) + goto dontconf; + jsV_delproperty(J, obj, name); + } + return 1; + +dontconf: + if (J->strict) + js_typeerror(J, "'%s' is non-configurable", name); + return 0; +} + +static void jsR_delindex(js_State *J, js_Object *obj, int k) +{ + char buf[32]; + /* Allow deleting last element of a simple array without unflattening */ + if (obj->type == JS_CARRAY && obj->u.a.simple && k == obj->u.a.flat_length - 1) + obj->u.a.flat_length = k; + else + jsR_delproperty(J, obj, js_itoa(buf, k)); +} + +/* Registry, global and object property accessors */ + +const char *js_ref(js_State *J) +{ + js_Value *v = stackidx(J, -1); + const char *s; + char buf[32]; + switch (v->t.type) { + case JS_TUNDEFINED: s = "_Undefined"; break; + case JS_TNULL: s = "_Null"; break; + case JS_TBOOLEAN: + s = v->u.boolean ? "_True" : "_False"; + break; + case JS_TOBJECT: + sprintf(buf, "%p", (void*)v->u.object); + s = js_intern(J, buf); + break; + default: + sprintf(buf, "%d", J->nextref++); + s = js_intern(J, buf); + break; + } + js_setregistry(J, s); + return s; +} + +void js_unref(js_State *J, const char *ref) +{ + js_delregistry(J, ref); +} + +void js_getregistry(js_State *J, const char *name) +{ + jsR_getproperty(J, J->R, name); +} + +void js_setregistry(js_State *J, const char *name) +{ + jsR_setproperty(J, J->R, name, 0); + js_pop(J, 1); +} + +void js_delregistry(js_State *J, const char *name) +{ + jsR_delproperty(J, J->R, name); +} + +void js_getglobal(js_State *J, const char *name) +{ + jsR_getproperty(J, J->G, name); +} + +void js_setglobal(js_State *J, const char *name) +{ + jsR_setproperty(J, J->G, name, 0); + js_pop(J, 1); +} + +void js_defglobal(js_State *J, const char *name, int atts) +{ + jsR_defproperty(J, J->G, name, atts, stackidx(J, -1), NULL, NULL, 0); + js_pop(J, 1); +} + +void js_delglobal(js_State *J, const char *name) +{ + jsR_delproperty(J, J->G, name); +} + +void js_getproperty(js_State *J, int idx, const char *name) +{ + jsR_getproperty(J, js_toobject(J, idx), name); +} + +void js_setproperty(js_State *J, int idx, const char *name) +{ + jsR_setproperty(J, js_toobject(J, idx), name, !js_isobject(J, idx)); + js_pop(J, 1); +} + +void js_defproperty(js_State *J, int idx, const char *name, int atts) +{ + jsR_defproperty(J, js_toobject(J, idx), name, atts, stackidx(J, -1), NULL, NULL, 1); + js_pop(J, 1); +} + +void js_delproperty(js_State *J, int idx, const char *name) +{ + jsR_delproperty(J, js_toobject(J, idx), name); +} + +void js_defaccessor(js_State *J, int idx, const char *name, int atts) +{ + jsR_defproperty(J, js_toobject(J, idx), name, atts, NULL, jsR_tofunction(J, -2), jsR_tofunction(J, -1), 1); + js_pop(J, 2); +} + +int js_hasproperty(js_State *J, int idx, const char *name) +{ + return jsR_hasproperty(J, js_toobject(J, idx), name); +} + +void js_getindex(js_State *J, int idx, int i) +{ + jsR_getindex(J, js_toobject(J, idx), i); +} + +int js_hasindex(js_State *J, int idx, int i) +{ + return jsR_hasindex(J, js_toobject(J, idx), i); +} + +void js_setindex(js_State *J, int idx, int i) +{ + jsR_setindex(J, js_toobject(J, idx), i, !js_isobject(J, idx)); + js_pop(J, 1); +} + +void js_delindex(js_State *J, int idx, int i) +{ + jsR_delindex(J, js_toobject(J, idx), i); +} + +/* Iterator */ + +void js_pushiterator(js_State *J, int idx, int own) +{ + js_pushobject(J, jsV_newiterator(J, js_toobject(J, idx), own)); +} + +const char *js_nextiterator(js_State *J, int idx) +{ + return jsV_nextiterator(J, js_toobject(J, idx)); +} + +/* Environment records */ + +js_Environment *jsR_newenvironment(js_State *J, js_Object *vars, js_Environment *outer) +{ + js_Environment *E = js_malloc(J, sizeof *E); + E->gcmark = 0; + E->gcnext = J->gcenv; + J->gcenv = E; + ++J->gccounter; + + E->outer = outer; + E->variables = vars; + return E; +} + +static void js_initvar(js_State *J, const char *name, int idx) +{ + jsR_defproperty(J, J->E->variables, name, JS_DONTENUM | JS_DONTCONF, stackidx(J, idx), NULL, NULL, 0); +} + +static int js_hasvar(js_State *J, const char *name) +{ + js_Environment *E = J->E; + do { + js_Property *ref = jsV_getproperty(J, E->variables, name); + if (ref) { + if (ref->getter) { + js_pushobject(J, ref->getter); + js_pushobject(J, E->variables); + js_call(J, 0); + } else { + js_pushvalue(J, ref->value); + } + return 1; + } + E = E->outer; + } while (E); + return 0; +} + +static void js_setvar(js_State *J, const char *name) +{ + js_Environment *E = J->E; + do { + js_Property *ref = jsV_getproperty(J, E->variables, name); + if (ref) { + if (ref->setter) { + js_pushobject(J, ref->setter); + js_pushobject(J, E->variables); + js_copy(J, -3); + js_call(J, 1); + js_pop(J, 1); + return; + } + if (!(ref->atts & JS_READONLY)) + ref->value = *stackidx(J, -1); + else if (J->strict) + js_typeerror(J, "'%s' is read-only", name); + return; + } + E = E->outer; + } while (E); + if (J->strict) + js_referenceerror(J, "assignment to undeclared variable '%s'", name); + jsR_setproperty(J, J->G, name, 0); +} + +static int js_delvar(js_State *J, const char *name) +{ + js_Environment *E = J->E; + do { + js_Property *ref = jsV_getownproperty(J, E->variables, name); + if (ref) { + if (ref->atts & JS_DONTCONF) { + if (J->strict) + js_typeerror(J, "'%s' is non-configurable", name); + return 0; + } + jsV_delproperty(J, E->variables, name); + return 1; + } + E = E->outer; + } while (E); + return jsR_delproperty(J, J->G, name); +} + +/* Function calls */ + +static void jsR_savescope(js_State *J, js_Environment *newE) +{ + if (J->envtop + 1 >= JS_ENVLIMIT) + js_stackoverflow(J); + J->envstack[J->envtop++] = J->E; + J->E = newE; +} + +static void jsR_restorescope(js_State *J) +{ + J->E = J->envstack[--J->envtop]; +} + +static void jsR_calllwfunction(js_State *J, int n, js_Function *F, js_Environment *scope) +{ + js_Value v; + int i; + + jsR_savescope(J, scope); + + if (n > F->numparams) { + js_pop(J, n - F->numparams); + n = F->numparams; + } + + for (i = n; i < F->varlen; ++i) + js_pushundefined(J); + + jsR_run(J, F); + v = *stackidx(J, -1); + TOP = --BOT; /* clear stack */ + js_pushvalue(J, v); + + jsR_restorescope(J); +} + +static void jsR_callfunction(js_State *J, int n, js_Function *F, js_Environment *scope) +{ + js_Value v; + int i; + + scope = jsR_newenvironment(J, jsV_newobject(J, JS_COBJECT, NULL), scope); + + jsR_savescope(J, scope); + + if (F->arguments) { + js_newarguments(J); + if (!J->strict) { + js_currentfunction(J); + js_defproperty(J, -2, "callee", JS_DONTENUM); + } + js_pushnumber(J, n); + js_defproperty(J, -2, "length", JS_DONTENUM); + for (i = 0; i < n; ++i) { + js_copy(J, i + 1); + js_setindex(J, -2, i); + } + js_initvar(J, "arguments", -1); + js_pop(J, 1); + } + + for (i = 0; i < n && i < F->numparams; ++i) + js_initvar(J, F->vartab[i], i + 1); + js_pop(J, n); + + for (; i < F->varlen; ++i) { + js_pushundefined(J); + js_initvar(J, F->vartab[i], -1); + js_pop(J, 1); + } + + jsR_run(J, F); + v = *stackidx(J, -1); + TOP = --BOT; /* clear stack */ + js_pushvalue(J, v); + + jsR_restorescope(J); +} + +static void jsR_callscript(js_State *J, int n, js_Function *F, js_Environment *scope) +{ + js_Value v; + int i; + + if (scope) + jsR_savescope(J, scope); + + /* scripts take no arguments */ + js_pop(J, n); + + for (i = 0; i < F->varlen; ++i) { + /* Bug 701886: don't redefine existing vars in eval/scripts */ + if (!js_hasvar(J, F->vartab[i])) { + js_pushundefined(J); + js_initvar(J, F->vartab[i], -1); + js_pop(J, 1); + } + } + + jsR_run(J, F); + v = *stackidx(J, -1); + TOP = --BOT; /* clear stack */ + js_pushvalue(J, v); + + if (scope) + jsR_restorescope(J); +} + +static void jsR_callcfunction(js_State *J, int n, int min, js_CFunction F) +{ + int save_top; + int i; + js_Value v; + + for (i = n; i < min; ++i) + js_pushundefined(J); + + save_top = TOP; + F(J); + if (TOP > save_top) { + v = *stackidx(J, -1); + TOP = --BOT; /* clear stack */ + js_pushvalue(J, v); + } else { + TOP = --BOT; /* clear stack */ + js_pushundefined(J); + } +} + +static void jsR_pushtrace(js_State *J, const char *name, const char *file, int line) +{ + if (J->tracetop + 1 == JS_ENVLIMIT) + js_error(J, "call stack overflow"); + ++J->tracetop; + J->trace[J->tracetop].name = name; + J->trace[J->tracetop].file = file; + J->trace[J->tracetop].line = line; +} + +void js_call(js_State *J, int n) +{ + js_Object *obj; + int savebot; + + if (n < 0) + js_rangeerror(J, "number of arguments cannot be negative"); + + if (!js_iscallable(J, -n-2)) + js_typeerror(J, "%s is not callable", js_typeof(J, -n-2)); + + obj = js_toobject(J, -n-2); + + savebot = BOT; + BOT = TOP - n - 1; + + if (obj->type == JS_CFUNCTION) { + jsR_pushtrace(J, obj->u.f.function->name, obj->u.f.function->filename, obj->u.f.function->line); + if (obj->u.f.function->lightweight) + jsR_calllwfunction(J, n, obj->u.f.function, obj->u.f.scope); + else + jsR_callfunction(J, n, obj->u.f.function, obj->u.f.scope); + --J->tracetop; + } else if (obj->type == JS_CSCRIPT) { + jsR_pushtrace(J, obj->u.f.function->name, obj->u.f.function->filename, obj->u.f.function->line); + jsR_callscript(J, n, obj->u.f.function, obj->u.f.scope); + --J->tracetop; + } else if (obj->type == JS_CCFUNCTION) { + jsR_pushtrace(J, obj->u.c.name, "native", 0); + jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.function); + --J->tracetop; + } + + BOT = savebot; +} + +void js_construct(js_State *J, int n) +{ + js_Object *obj; + js_Object *prototype; + js_Object *newobj; + + if (!js_iscallable(J, -n-1)) + js_typeerror(J, "%s is not callable", js_typeof(J, -n-1)); + + obj = js_toobject(J, -n-1); + + /* built-in constructors create their own objects, give them a 'null' this */ + if (obj->type == JS_CCFUNCTION && obj->u.c.constructor) { + int savebot = BOT; + js_pushnull(J); + if (n > 0) + js_rot(J, n + 1); + BOT = TOP - n - 1; + + jsR_pushtrace(J, obj->u.c.name, "native", 0); + jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.constructor); + --J->tracetop; + + BOT = savebot; + return; + } + + /* extract the function object's prototype property */ + js_getproperty(J, -n - 1, "prototype"); + if (js_isobject(J, -1)) + prototype = js_toobject(J, -1); + else + prototype = J->Object_prototype; + js_pop(J, 1); + + /* create a new object with above prototype, and shift it into the 'this' slot */ + newobj = jsV_newobject(J, JS_COBJECT, prototype); + js_pushobject(J, newobj); + if (n > 0) + js_rot(J, n + 1); + + /* and save a copy to return */ + js_pushobject(J, newobj); + js_rot(J, n + 3); + + /* call the function */ + js_call(J, n); + + /* if result is not an object, return the original object we created */ + if (!js_isobject(J, -1)) { + js_pop(J, 1); + } else { + js_rot2pop1(J); + } +} + +void js_eval(js_State *J) +{ + if (!js_isstring(J, -1)) + return; + js_loadeval(J, "(eval)", js_tostring(J, -1)); + js_rot2pop1(J); + js_copy(J, 0); /* copy 'this' */ + js_call(J, 0); +} + +int js_pconstruct(js_State *J, int n) +{ + int savetop = TOP - n - 2; + if (js_try(J)) { + /* clean up the stack to only hold the error object */ + STACK[savetop] = STACK[TOP-1]; + TOP = savetop + 1; + return 1; + } + js_construct(J, n); + js_endtry(J); + return 0; +} + +int js_pcall(js_State *J, int n) +{ + int savetop = TOP - n - 2; + if (js_try(J)) { + /* clean up the stack to only hold the error object */ + STACK[savetop] = STACK[TOP-1]; + TOP = savetop + 1; + return 1; + } + js_call(J, n); + js_endtry(J); + return 0; +} + +/* Exceptions */ + +void *js_savetrypc(js_State *J, js_Instruction *pc) +{ + if (J->trytop == JS_TRYLIMIT) + js_trystackoverflow(J); + J->trybuf[J->trytop].E = J->E; + J->trybuf[J->trytop].envtop = J->envtop; + J->trybuf[J->trytop].tracetop = J->tracetop; + J->trybuf[J->trytop].top = J->top; + J->trybuf[J->trytop].bot = J->bot; + J->trybuf[J->trytop].strict = J->strict; + J->trybuf[J->trytop].pc = pc; + return J->trybuf[J->trytop++].buf; +} + +void *js_savetry(js_State *J) +{ + if (J->trytop == JS_TRYLIMIT) + js_trystackoverflow(J); + J->trybuf[J->trytop].E = J->E; + J->trybuf[J->trytop].envtop = J->envtop; + J->trybuf[J->trytop].tracetop = J->tracetop; + J->trybuf[J->trytop].top = J->top; + J->trybuf[J->trytop].bot = J->bot; + J->trybuf[J->trytop].strict = J->strict; + J->trybuf[J->trytop].pc = NULL; + return J->trybuf[J->trytop++].buf; +} + +void js_endtry(js_State *J) +{ + if (J->trytop == 0) + js_error(J, "endtry: exception stack underflow"); + --J->trytop; +} + +void js_throw(js_State *J) +{ + if (J->trytop > 0) { + js_Value v = *stackidx(J, -1); + --J->trytop; + J->E = J->trybuf[J->trytop].E; + J->envtop = J->trybuf[J->trytop].envtop; + J->tracetop = J->trybuf[J->trytop].tracetop; + J->top = J->trybuf[J->trytop].top; + J->bot = J->trybuf[J->trytop].bot; + J->strict = J->trybuf[J->trytop].strict; + js_pushvalue(J, v); + longjmp(J->trybuf[J->trytop].buf, 1); + } + if (J->panic) + J->panic(J); + abort(); +} + +/* Main interpreter loop */ + +static void js_dumpvalue(js_State *J, js_Value v) +{ + switch (v.t.type) { + case JS_TUNDEFINED: printf("undefined"); break; + case JS_TNULL: printf("null"); break; + case JS_TBOOLEAN: printf(v.u.boolean ? "true" : "false"); break; + case JS_TNUMBER: printf("%.9g", v.u.number); break; + case JS_TSHRSTR: printf("'%s'", v.u.shrstr); break; + case JS_TLITSTR: printf("'%s'", v.u.litstr); break; + case JS_TMEMSTR: printf("'%s'", v.u.memstr->p); break; + case JS_TOBJECT: + if (v.u.object == J->G) { + printf("[Global]"); + break; + } + switch (v.u.object->type) { + case JS_COBJECT: printf("[Object %p]", (void*)v.u.object); break; + case JS_CARRAY: printf("[Array %p]", (void*)v.u.object); break; + case JS_CFUNCTION: + printf("[Function %p, %s, %s:%d]", + (void*)v.u.object, + v.u.object->u.f.function->name, + v.u.object->u.f.function->filename, + v.u.object->u.f.function->line); + break; + case JS_CSCRIPT: printf("[Script %s]", v.u.object->u.f.function->filename); break; + case JS_CCFUNCTION: printf("[CFunction %s]", v.u.object->u.c.name); break; + case JS_CBOOLEAN: printf("[Boolean %d]", v.u.object->u.boolean); break; + case JS_CNUMBER: printf("[Number %g]", v.u.object->u.number); break; + case JS_CSTRING: printf("[String'%s']", v.u.object->u.s.string); break; + case JS_CERROR: printf("[Error]"); break; + case JS_CARGUMENTS: printf("[Arguments %p]", (void*)v.u.object); break; + case JS_CITERATOR: printf("[Iterator %p]", (void*)v.u.object); break; + case JS_CUSERDATA: + printf("[Userdata %s %p]", v.u.object->u.user.tag, v.u.object->u.user.data); + break; + default: printf("[Object %p]", (void*)v.u.object); break; + } + break; + } +} + +static void js_stacktrace(js_State *J) +{ + int n; + printf("stack trace:\n"); + for (n = J->tracetop; n >= 0; --n) { + const char *name = J->trace[n].name; + const char *file = J->trace[n].file; + int line = J->trace[n].line; + if (line > 0) { + if (name[0]) + printf("\tat %s (%s:%d)\n", name, file, line); + else + printf("\tat %s:%d\n", file, line); + } else + printf("\tat %s (%s)\n", name, file); + } +} + +static void js_dumpstack(js_State *J) +{ + int i; + printf("stack {\n"); + for (i = 0; i < TOP; ++i) { + putchar(i == BOT ? '>' : ' '); + printf("%4d: ", i); + js_dumpvalue(J, STACK[i]); + putchar('\n'); + } + printf("}\n"); +} + +void js_trap(js_State *J, int pc) +{ + js_dumpstack(J); + js_stacktrace(J); +} + +static int jsR_isindex(js_State *J, int idx, int *k) +{ + js_Value *v = stackidx(J, idx); + if (v->t.type == JS_TNUMBER) { + *k = v->u.number; + return *k == v->u.number && *k >= 0; + } + return 0; +} + +static void jsR_run(js_State *J, js_Function *F) +{ + js_Function **FT = F->funtab; + const char **VT = F->vartab-1; + int lightweight = F->lightweight; + js_Instruction *pcstart = F->code; + js_Instruction *pc = F->code; + enum js_OpCode opcode; + int offset; + int savestrict; + + const char *str; + js_Object *obj; + double x, y; + unsigned int ux, uy; + int ix, iy, okay; + int b; + int transient; + + savestrict = J->strict; + J->strict = F->strict; + +#define READSTRING() \ + memcpy(&str, pc, sizeof(str)); \ + pc += sizeof(str) / sizeof(*pc) + + while (1) { + if (J->gccounter > J->gcthresh) + js_gc(J, 0); + + J->trace[J->tracetop].line = *pc++; + + opcode = *pc++; + + switch (opcode) { + case OP_POP: js_pop(J, 1); break; + case OP_DUP: js_dup(J); break; + case OP_DUP2: js_dup2(J); break; + case OP_ROT2: js_rot2(J); break; + case OP_ROT3: js_rot3(J); break; + case OP_ROT4: js_rot4(J); break; + + case OP_INTEGER: + js_pushnumber(J, *pc++ - 32768); + break; + + case OP_NUMBER: + memcpy(&x, pc, sizeof(x)); + pc += sizeof(x) / sizeof(*pc); + js_pushnumber(J, x); + break; + + case OP_STRING: + READSTRING(); + js_pushliteral(J, str); + break; + + case OP_CLOSURE: js_newfunction(J, FT[*pc++], J->E); break; + case OP_NEWOBJECT: js_newobject(J); break; + case OP_NEWARRAY: js_newarray(J); break; + case OP_NEWREGEXP: + READSTRING(); + js_newregexp(J, str, *pc++); + break; + + case OP_UNDEF: js_pushundefined(J); break; + case OP_NULL: js_pushnull(J); break; + case OP_TRUE: js_pushboolean(J, 1); break; + case OP_FALSE: js_pushboolean(J, 0); break; + + case OP_THIS: + if (J->strict) { + js_copy(J, 0); + } else { + if (js_iscoercible(J, 0)) + js_copy(J, 0); + else + js_pushglobal(J); + } + break; + + case OP_CURRENT: + js_currentfunction(J); + break; + + case OP_GETLOCAL: + if (lightweight) { + CHECKSTACK(1); + STACK[TOP++] = STACK[BOT + *pc++]; + } else { + str = VT[*pc++]; + if (!js_hasvar(J, str)) + js_referenceerror(J, "'%s' is not defined", str); + } + break; + + case OP_SETLOCAL: + if (lightweight) { + STACK[BOT + *pc++] = STACK[TOP-1]; + } else { + js_setvar(J, VT[*pc++]); + } + break; + + case OP_DELLOCAL: + if (lightweight) { + ++pc; + js_pushboolean(J, 0); + } else { + b = js_delvar(J, VT[*pc++]); + js_pushboolean(J, b); + } + break; + + case OP_GETVAR: + READSTRING(); + if (!js_hasvar(J, str)) + js_referenceerror(J, "'%s' is not defined", str); + break; + + case OP_HASVAR: + READSTRING(); + if (!js_hasvar(J, str)) + js_pushundefined(J); + break; + + case OP_SETVAR: + READSTRING(); + js_setvar(J, str); + break; + + case OP_DELVAR: + READSTRING(); + b = js_delvar(J, str); + js_pushboolean(J, b); + break; + + case OP_IN: + str = js_tostring(J, -2); + if (!js_isobject(J, -1)) + js_typeerror(J, "operand to 'in' is not an object"); + b = js_hasproperty(J, -1, str); + js_pop(J, 2 + b); + js_pushboolean(J, b); + break; + + case OP_SKIPARRAY: + js_setlength(J, -1, js_getlength(J, -1) + 1); + break; + case OP_INITARRAY: + js_setindex(J, -2, js_getlength(J, -2)); + break; + + case OP_INITPROP: + obj = js_toobject(J, -3); + str = js_tostring(J, -2); + jsR_setproperty(J, obj, str, 0); + js_pop(J, 2); + break; + + case OP_INITGETTER: + obj = js_toobject(J, -3); + str = js_tostring(J, -2); + jsR_defproperty(J, obj, str, 0, NULL, jsR_tofunction(J, -1), NULL, 0); + js_pop(J, 2); + break; + + case OP_INITSETTER: + obj = js_toobject(J, -3); + str = js_tostring(J, -2); + jsR_defproperty(J, obj, str, 0, NULL, NULL, jsR_tofunction(J, -1), 0); + js_pop(J, 2); + break; + + case OP_GETPROP: + if (jsR_isindex(J, -1, &ix)) { + obj = js_toobject(J, -2); + jsR_getindex(J, obj, ix); + } else { + str = js_tostring(J, -1); + obj = js_toobject(J, -2); + jsR_getproperty(J, obj, str); + } + js_rot3pop2(J); + break; + + case OP_GETPROP_S: + READSTRING(); + obj = js_toobject(J, -1); + jsR_getproperty(J, obj, str); + js_rot2pop1(J); + break; + + case OP_SETPROP: + if (jsR_isindex(J, -2, &ix)) { + obj = js_toobject(J, -3); + transient = !js_isobject(J, -3); + jsR_setindex(J, obj, ix, transient); + } else { + str = js_tostring(J, -2); + obj = js_toobject(J, -3); + transient = !js_isobject(J, -3); + jsR_setproperty(J, obj, str, transient); + } + js_rot3pop2(J); + break; + + case OP_SETPROP_S: + READSTRING(); + obj = js_toobject(J, -2); + transient = !js_isobject(J, -2); + jsR_setproperty(J, obj, str, transient); + js_rot2pop1(J); + break; + + case OP_DELPROP: + str = js_tostring(J, -1); + obj = js_toobject(J, -2); + b = jsR_delproperty(J, obj, str); + js_pop(J, 2); + js_pushboolean(J, b); + break; + + case OP_DELPROP_S: + READSTRING(); + obj = js_toobject(J, -1); + b = jsR_delproperty(J, obj, str); + js_pop(J, 1); + js_pushboolean(J, b); + break; + + case OP_ITERATOR: + if (js_iscoercible(J, -1)) { + obj = jsV_newiterator(J, js_toobject(J, -1), 0); + js_pop(J, 1); + js_pushobject(J, obj); + } + break; + + case OP_NEXTITER: + if (js_isobject(J, -1)) { + obj = js_toobject(J, -1); + str = jsV_nextiterator(J, obj); + if (str) { + js_pushstring(J, str); + js_pushboolean(J, 1); + } else { + js_pop(J, 1); + js_pushboolean(J, 0); + } + } else { + js_pop(J, 1); + js_pushboolean(J, 0); + } + break; + + /* Function calls */ + + case OP_EVAL: + js_eval(J); + break; + + case OP_CALL: + js_call(J, *pc++); + break; + + case OP_NEW: + js_construct(J, *pc++); + break; + + /* Unary operators */ + + case OP_TYPEOF: + str = js_typeof(J, -1); + js_pop(J, 1); + js_pushliteral(J, str); + break; + + case OP_POS: + x = js_tonumber(J, -1); + js_pop(J, 1); + js_pushnumber(J, x); + break; + + case OP_NEG: + x = js_tonumber(J, -1); + js_pop(J, 1); + js_pushnumber(J, -x); + break; + + case OP_BITNOT: + ix = js_toint32(J, -1); + js_pop(J, 1); + js_pushnumber(J, ~ix); + break; + + case OP_LOGNOT: + b = js_toboolean(J, -1); + js_pop(J, 1); + js_pushboolean(J, !b); + break; + + case OP_INC: + x = js_tonumber(J, -1); + js_pop(J, 1); + js_pushnumber(J, x + 1); + break; + + case OP_DEC: + x = js_tonumber(J, -1); + js_pop(J, 1); + js_pushnumber(J, x - 1); + break; + + case OP_POSTINC: + x = js_tonumber(J, -1); + js_pop(J, 1); + js_pushnumber(J, x + 1); + js_pushnumber(J, x); + break; + + case OP_POSTDEC: + x = js_tonumber(J, -1); + js_pop(J, 1); + js_pushnumber(J, x - 1); + js_pushnumber(J, x); + break; + + /* Multiplicative operators */ + + case OP_MUL: + x = js_tonumber(J, -2); + y = js_tonumber(J, -1); + js_pop(J, 2); + js_pushnumber(J, x * y); + break; + + case OP_DIV: + x = js_tonumber(J, -2); + y = js_tonumber(J, -1); + js_pop(J, 2); + js_pushnumber(J, x / y); + break; + + case OP_MOD: + x = js_tonumber(J, -2); + y = js_tonumber(J, -1); + js_pop(J, 2); + js_pushnumber(J, fmod(x, y)); + break; + + /* Additive operators */ + + case OP_ADD: + js_concat(J); + break; + + case OP_SUB: + x = js_tonumber(J, -2); + y = js_tonumber(J, -1); + js_pop(J, 2); + js_pushnumber(J, x - y); + break; + + /* Shift operators */ + + case OP_SHL: + ix = js_toint32(J, -2); + uy = js_touint32(J, -1); + js_pop(J, 2); + js_pushnumber(J, ix << (uy & 0x1F)); + break; + + case OP_SHR: + ix = js_toint32(J, -2); + uy = js_touint32(J, -1); + js_pop(J, 2); + js_pushnumber(J, ix >> (uy & 0x1F)); + break; + + case OP_USHR: + ux = js_touint32(J, -2); + uy = js_touint32(J, -1); + js_pop(J, 2); + js_pushnumber(J, ux >> (uy & 0x1F)); + break; + + /* Relational operators */ + + case OP_LT: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b < 0); break; + case OP_GT: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b > 0); break; + case OP_LE: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b <= 0); break; + case OP_GE: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b >= 0); break; + + case OP_INSTANCEOF: + b = js_instanceof(J); + js_pop(J, 2); + js_pushboolean(J, b); + break; + + /* Equality */ + + case OP_EQ: b = js_equal(J); js_pop(J, 2); js_pushboolean(J, b); break; + case OP_NE: b = js_equal(J); js_pop(J, 2); js_pushboolean(J, !b); break; + case OP_STRICTEQ: b = js_strictequal(J); js_pop(J, 2); js_pushboolean(J, b); break; + case OP_STRICTNE: b = js_strictequal(J); js_pop(J, 2); js_pushboolean(J, !b); break; + + case OP_JCASE: + offset = *pc++; + b = js_strictequal(J); + if (b) { + js_pop(J, 2); + pc = pcstart + offset; + } else { + js_pop(J, 1); + } + break; + + /* Binary bitwise operators */ + + case OP_BITAND: + ix = js_toint32(J, -2); + iy = js_toint32(J, -1); + js_pop(J, 2); + js_pushnumber(J, ix & iy); + break; + + case OP_BITXOR: + ix = js_toint32(J, -2); + iy = js_toint32(J, -1); + js_pop(J, 2); + js_pushnumber(J, ix ^ iy); + break; + + case OP_BITOR: + ix = js_toint32(J, -2); + iy = js_toint32(J, -1); + js_pop(J, 2); + js_pushnumber(J, ix | iy); + break; + + /* Try and Catch */ + + case OP_THROW: + js_throw(J); + + case OP_TRY: + offset = *pc++; + if (js_trypc(J, pc)) { + pc = J->trybuf[J->trytop].pc; + } else { + pc = pcstart + offset; + } + break; + + case OP_ENDTRY: + js_endtry(J); + break; + + case OP_CATCH: + READSTRING(); + obj = jsV_newobject(J, JS_COBJECT, NULL); + js_pushobject(J, obj); + js_rot2(J); + js_setproperty(J, -2, str); + J->E = jsR_newenvironment(J, obj, J->E); + js_pop(J, 1); + break; + + case OP_ENDCATCH: + J->E = J->E->outer; + break; + + /* With */ + + case OP_WITH: + obj = js_toobject(J, -1); + J->E = jsR_newenvironment(J, obj, J->E); + js_pop(J, 1); + break; + + case OP_ENDWITH: + J->E = J->E->outer; + break; + + /* Branching */ + + case OP_DEBUGGER: + js_trap(J, (int)(pc - pcstart) - 1); + break; + + case OP_JUMP: + pc = pcstart + *pc; + break; + + case OP_JTRUE: + offset = *pc++; + b = js_toboolean(J, -1); + js_pop(J, 1); + if (b) + pc = pcstart + offset; + break; + + case OP_JFALSE: + offset = *pc++; + b = js_toboolean(J, -1); + js_pop(J, 1); + if (!b) + pc = pcstart + offset; + break; + + case OP_RETURN: + J->strict = savestrict; + return; + } + } +} diff --git a/mujs/jsstate.c b/mujs/jsstate.c new file mode 100644 index 0000000..715b793 --- /dev/null +++ b/mujs/jsstate.c @@ -0,0 +1,334 @@ +#include "jsi.h" + +#include +#include + +static int js_ptry(js_State *J) { + if (J->trytop == JS_TRYLIMIT) { + J->stack[J->top].t.type = JS_TLITSTR; + J->stack[J->top].u.litstr = "exception stack overflow"; + ++J->top; + return 1; + } + return 0; +} + +static void *js_defaultalloc(void *actx, void *ptr, int size) +{ + if (size == 0) { + free(ptr); + return NULL; + } + return realloc(ptr, (size_t)size); +} + +static void js_defaultreport(js_State *J, const char *message) +{ + fputs(message, stderr); + fputc('\n', stderr); +} + +static void js_defaultpanic(js_State *J) +{ + js_report(J, "uncaught exception"); + /* return to javascript to abort */ +} + +int js_ploadstring(js_State *J, const char *filename, const char *source) +{ + if (js_ptry(J)) + return 1; + if (js_try(J)) + return 1; + js_loadstring(J, filename, source); + js_endtry(J); + return 0; +} + +int js_ploadfile(js_State *J, const char *filename) +{ + if (js_ptry(J)) + return 1; + if (js_try(J)) + return 1; + js_loadfile(J, filename); + js_endtry(J); + return 0; +} + +const char *js_trystring(js_State *J, int idx, const char *error) +{ + const char *s; + if (js_ptry(J)) { + js_pop(J, 1); + return error; + } + if (js_try(J)) { + js_pop(J, 1); + return error; + } + s = js_tostring(J, idx); + js_endtry(J); + return s; +} + +double js_trynumber(js_State *J, int idx, double error) +{ + double v; + if (js_ptry(J)) { + js_pop(J, 1); + return error; + } + if (js_try(J)) { + js_pop(J, 1); + return error; + } + v = js_tonumber(J, idx); + js_endtry(J); + return v; +} + +int js_tryinteger(js_State *J, int idx, int error) +{ + int v; + if (js_ptry(J)) { + js_pop(J, 1); + return error; + } + if (js_try(J)) { + js_pop(J, 1); + return error; + } + v = js_tointeger(J, idx); + js_endtry(J); + return v; +} + +int js_tryboolean(js_State *J, int idx, int error) +{ + int v; + if (js_ptry(J)) { + js_pop(J, 1); + return error; + } + if (js_try(J)) { + js_pop(J, 1); + return error; + } + v = js_toboolean(J, idx); + js_endtry(J); + return v; +} + +static void js_loadstringx(js_State *J, const char *filename, const char *source, int iseval) +{ + js_Ast *P; + js_Function *F; + + if (js_try(J)) { + jsP_freeparse(J); + js_throw(J); + } + + P = jsP_parse(J, filename, source); + F = jsC_compilescript(J, P, iseval ? J->strict : J->default_strict); + jsP_freeparse(J); + js_newscript(J, F, iseval ? (J->strict ? J->E : NULL) : J->GE); + + js_endtry(J); +} + +void js_loadeval(js_State *J, const char *filename, const char *source) +{ + js_loadstringx(J, filename, source, 1); +} + +void js_loadstring(js_State *J, const char *filename, const char *source) +{ + js_loadstringx(J, filename, source, 0); +} + +void js_loadfile(js_State *J, const char *filename) +{ + FILE *f; + char *s, *p; + int n, t; + + f = fopen(filename, "rb"); + if (!f) { + js_error(J, "cannot open file '%s': %s", filename, strerror(errno)); + } + + if (fseek(f, 0, SEEK_END) < 0) { + fclose(f); + js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); + } + + n = ftell(f); + if (n < 0) { + fclose(f); + js_error(J, "cannot tell in file '%s': %s", filename, strerror(errno)); + } + + if (fseek(f, 0, SEEK_SET) < 0) { + fclose(f); + js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); + } + + if (js_try(J)) { + fclose(f); + js_throw(J); + } + s = js_malloc(J, n + 1); /* add space for string terminator */ + js_endtry(J); + + t = fread(s, 1, (size_t)n, f); + if (t != n) { + js_free(J, s); + fclose(f); + js_error(J, "cannot read data from file '%s': %s", filename, strerror(errno)); + } + + s[n] = 0; /* zero-terminate string containing file data */ + + if (js_try(J)) { + js_free(J, s); + fclose(f); + js_throw(J); + } + + /* skip first line if it starts with "#!" */ + p = s; + if (p[0] == '#' && p[1] == '!') { + p += 2; + while (*p && *p != '\n') + ++p; + } + + js_loadstring(J, filename, p); + + js_free(J, s); + fclose(f); + js_endtry(J); +} + +int js_dostring(js_State *J, const char *source) +{ + if (js_ptry(J)) { + js_report(J, "exception stack overflow"); + js_pop(J, 1); + return 1; + } + if (js_try(J)) { + js_report(J, js_trystring(J, -1, "Error")); + js_pop(J, 1); + return 1; + } + js_loadstring(J, "[string]", source); + js_pushundefined(J); + js_call(J, 0); + js_pop(J, 1); + js_endtry(J); + return 0; +} + +int js_dofile(js_State *J, const char *filename) +{ + if (js_ptry(J)) { + js_report(J, "exception stack overflow"); + js_pop(J, 1); + return 1; + } + if (js_try(J)) { + js_report(J, js_trystring(J, -1, "Error")); + js_pop(J, 1); + return 1; + } + js_loadfile(J, filename); + js_pushundefined(J); + js_call(J, 0); + js_pop(J, 1); + js_endtry(J); + return 0; +} + +js_Panic js_atpanic(js_State *J, js_Panic panic) +{ + js_Panic old = J->panic; + J->panic = panic; + return old; +} + +void js_report(js_State *J, const char *message) +{ + if (J->report) + J->report(J, message); +} + +void js_setreport(js_State *J, js_Report report) +{ + J->report = report; +} + +void js_setcontext(js_State *J, void *uctx) +{ + J->uctx = uctx; +} + +void *js_getcontext(js_State *J) +{ + return J->uctx; +} + +js_State *js_newstate(js_Alloc alloc, void *actx, int flags) +{ + js_State *J; + + assert(sizeof(js_Value) == 16); + assert(soffsetof(js_Value, t.type) == 15); + + if (!alloc) + alloc = js_defaultalloc; + + J = alloc(actx, NULL, sizeof *J); + if (!J) + return NULL; + memset(J, 0, sizeof(*J)); + J->actx = actx; + J->alloc = alloc; + + if (flags & JS_STRICT) + J->strict = J->default_strict = 1; + + J->trace[0].name = "-top-"; + J->trace[0].file = "native"; + J->trace[0].line = 0; + + J->report = js_defaultreport; + J->panic = js_defaultpanic; + + J->stack = alloc(actx, NULL, JS_STACKSIZE * sizeof *J->stack); + if (!J->stack) { + alloc(actx, J, 0); + return NULL; + } + + J->gcmark = 1; + J->nextref = 0; + J->gcthresh = 0; /* reaches stability within ~ 2-5 GC cycles */ + + if (js_try(J)) { + js_freestate(J); + return NULL; + } + + J->R = jsV_newobject(J, JS_COBJECT, NULL); + J->G = jsV_newobject(J, JS_COBJECT, NULL); + J->E = jsR_newenvironment(J, J->G, NULL); + J->GE = J->E; + + jsB_init(J); + + js_endtry(J); + return J; +} diff --git a/mujs/jsstring.c b/mujs/jsstring.c new file mode 100644 index 0000000..627118a --- /dev/null +++ b/mujs/jsstring.c @@ -0,0 +1,798 @@ +#include "jsi.h" +#include "utf.h" +#include "regexp.h" + +static int js_doregexec(js_State *J, Reprog *prog, const char *string, Resub *sub, int eflags) +{ + int result = js_regexec(prog, string, sub, eflags); + if (result < 0) + js_error(J, "regexec failed"); + return result; +} + +static const char *checkstring(js_State *J, int idx) +{ + if (!js_iscoercible(J, idx)) + js_typeerror(J, "string function called on null or undefined"); + return js_tostring(J, idx); +} + +int js_runeat(js_State *J, const char *s, int i) +{ + Rune rune = EOF; + while (i >= 0) { + rune = *(unsigned char*)s; + if (rune < Runeself) { + if (rune == 0) + return EOF; + ++s; + --i; + } else { + s += chartorune(&rune, s); + if (rune >= 0x10000) + i -= 2; + else + --i; + } + } + if (rune >= 0x10000) { + /* high surrogate */ + if (i == -2) + return 0xd800 + ((rune - 0x10000) >> 10); + /* low surrogate */ + else + return 0xdc00 + ((rune - 0x10000) & 0x3ff); + } + return rune; +} + +int js_utflen(const char *s) +{ + int c; + int n; + Rune rune; + + n = 0; + for(;;) { + c = *(unsigned char *)s; + if (c < Runeself) { + if (c == 0) + return n; + s++; + n++; + } else { + s += chartorune(&rune, s); + if (rune >= 0x10000) + n += 2; + else + n++; + } + } +} + +int js_utfptrtoidx(const char *s, const char *p) +{ + Rune rune; + int i = 0; + while (s < p) { + if (*(unsigned char *)s < Runeself) + ++s; + else + s += chartorune(&rune, s); + if (rune >= 0x10000) + i += 2; + else + i += 1; + } + return i; +} + +static void jsB_new_String(js_State *J) +{ + js_newstring(J, js_gettop(J) > 1 ? js_tostring(J, 1) : ""); +} + +static void jsB_String(js_State *J) +{ + js_pushstring(J, js_gettop(J) > 1 ? js_tostring(J, 1) : ""); +} + +static void Sp_toString(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + if (self->type != JS_CSTRING) js_typeerror(J, "not a string"); + js_pushstring(J, self->u.s.string); +} + +static void Sp_valueOf(js_State *J) +{ + js_Object *self = js_toobject(J, 0); + if (self->type != JS_CSTRING) js_typeerror(J, "not a string"); + js_pushstring(J, self->u.s.string); +} + +static void Sp_charAt(js_State *J) +{ + char buf[UTFmax + 1]; + const char *s = checkstring(J, 0); + int pos = js_tointeger(J, 1); + Rune rune = js_runeat(J, s, pos); + if (rune >= 0) { + buf[runetochar(buf, &rune)] = 0; + js_pushstring(J, buf); + } else { + js_pushliteral(J, ""); + } +} + +static void Sp_charCodeAt(js_State *J) +{ + const char *s = checkstring(J, 0); + int pos = js_tointeger(J, 1); + Rune rune = js_runeat(J, s, pos); + if (rune >= 0) + js_pushnumber(J, rune); + else + js_pushnumber(J, NAN); +} + +static void Sp_concat(js_State *J) +{ + int i, top = js_gettop(J); + int n; + char * volatile out = NULL; + const char *s; + + if (top == 1) + return; + + s = checkstring(J, 0); + n = 1 + strlen(s); + + if (js_try(J)) { + js_free(J, out); + js_throw(J); + } + + if (n > JS_STRLIMIT) + js_rangeerror(J, "invalid string length"); + out = js_malloc(J, n); + strcpy(out, s); + + for (i = 1; i < top; ++i) { + s = js_tostring(J, i); + n += strlen(s); + if (n > JS_STRLIMIT) + js_rangeerror(J, "invalid string length"); + out = js_realloc(J, out, n); + strcat(out, s); + } + + js_pushstring(J, out); + js_endtry(J); + js_free(J, out); +} + +static void Sp_indexOf(js_State *J) +{ + const char *haystack = checkstring(J, 0); + const char *needle = js_tostring(J, 1); + int pos = js_tointeger(J, 2); + int len = strlen(needle); + int k = 0; + Rune rune; + while (*haystack) { + if (k >= pos && !strncmp(haystack, needle, len)) { + js_pushnumber(J, k); + return; + } + haystack += chartorune(&rune, haystack); + ++k; + } + js_pushnumber(J, -1); +} + +static void Sp_lastIndexOf(js_State *J) +{ + const char *haystack = checkstring(J, 0); + const char *needle = js_tostring(J, 1); + int pos = js_isdefined(J, 2) ? js_tointeger(J, 2) : (int)strlen(haystack); + int len = strlen(needle); + int k = 0, last = -1; + Rune rune; + while (*haystack && k <= pos) { + if (!strncmp(haystack, needle, len)) + last = k; + haystack += chartorune(&rune, haystack); + ++k; + } + js_pushnumber(J, last); +} + +static void Sp_localeCompare(js_State *J) +{ + const char *a = checkstring(J, 0); + const char *b = js_tostring(J, 1); + js_pushnumber(J, strcmp(a, b)); +} + +static void Sp_substring_imp(js_State *J, const char *s, int a, int n) +{ + Rune head_rune = 0, tail_rune = 0; + const char *head, *tail; + char *p; + int i, k, head_len, tail_len; + + /* find start of substring */ + head = s; + for (i = 0; i < a; ++i) { + head += chartorune(&head_rune, head); + if (head_rune >= 0x10000) + ++i; + } + + /* find end of substring */ + tail = head; + for (k = i - a; k < n; ++k) { + tail += chartorune(&tail_rune, tail); + if (tail_rune >= 0x10000) + ++k; + } + + /* no surrogate pair splits! */ + if (i == a && k == n) { + js_pushlstring(J, head, tail - head); + return; + } + + if (js_try(J)) { + js_free(J, p); + js_throw(J); + } + + p = js_malloc(J, UTFmax + (tail - head)); + + /* substring starts with low surrogate (head is just after character) */ + if (i > a) { + head_rune = 0xdc00 + ((head_rune - 0x10000) & 0x3ff); + head_len = runetochar(p, &head_rune); + memcpy(p + head_len, head, tail - head); + js_pushlstring(J, p, head_len + (tail - head)); + } + + /* substring ends with high surrogate (tail is just after character) */ + if (k > n) { + tail -= runelen(tail_rune); + memcpy(p, head, tail - head); + tail_rune = 0xd800 + ((tail_rune - 0x10000) >> 10); + tail_len = runetochar(p + (tail - head), &tail_rune); + js_pushlstring(J, p, (tail - head) + tail_len); + } + + js_endtry(J); + js_free(J, p); +} + +static void Sp_slice(js_State *J) +{ + const char *str = checkstring(J, 0); + int len = js_utflen(str); + int s = js_tointeger(J, 1); + int e = js_isdefined(J, 2) ? js_tointeger(J, 2) : len; + + s = s < 0 ? s + len : s; + e = e < 0 ? e + len : e; + + s = s < 0 ? 0 : s > len ? len : s; + e = e < 0 ? 0 : e > len ? len : e; + + if (s < e) + Sp_substring_imp(J, str, s, e - s); + else + Sp_substring_imp(J, str, e, s - e); +} + +static void Sp_substring(js_State *J) +{ + const char *str = checkstring(J, 0); + int len = js_utflen(str); + int s = js_tointeger(J, 1); + int e = js_isdefined(J, 2) ? js_tointeger(J, 2) : len; + + s = s < 0 ? 0 : s > len ? len : s; + e = e < 0 ? 0 : e > len ? len : e; + + if (s < e) + Sp_substring_imp(J, str, s, e - s); + else + Sp_substring_imp(J, str, e, s - e); +} + +static void Sp_toLowerCase(js_State *J) +{ + const char *s = checkstring(J, 0); + char * volatile dst = NULL; + char *d; + Rune rune; + + if (js_try(J)) { + js_free(J, dst); + js_throw(J); + } + + d = dst = js_malloc(J, UTFmax * strlen(s) + 1); + while (*s) { + s += chartorune(&rune, s); + rune = tolowerrune(rune); + d += runetochar(d, &rune); + } + *d = 0; + + js_pushstring(J, dst); + js_endtry(J); + js_free(J, dst); +} + +static void Sp_toUpperCase(js_State *J) +{ + const char *s = checkstring(J, 0); + char * volatile dst = NULL; + char *d; + Rune rune; + + if (js_try(J)) { + js_free(J, dst); + js_throw(J); + } + + d = dst = js_malloc(J, UTFmax * strlen(s) + 1); + while (*s) { + s += chartorune(&rune, s); + rune = toupperrune(rune); + d += runetochar(d, &rune); + } + *d = 0; + + js_pushstring(J, dst); + js_endtry(J); + js_free(J, dst); +} + +static int istrim(int c) +{ + return c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0 || c == 0xFEFF || + c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029; +} + +static void Sp_trim(js_State *J) +{ + const char *s, *e; + s = checkstring(J, 0); + while (istrim(*s)) + ++s; + e = s + strlen(s); + while (e > s && istrim(e[-1])) + --e; + js_pushlstring(J, s, e - s); +} + +static void S_fromCharCode(js_State *J) +{ + int i, top = js_gettop(J); + char * volatile s = NULL; + char *p; + Rune c; + + if (js_try(J)) { + js_free(J, s); + js_throw(J); + } + + s = p = js_malloc(J, (top-1) * UTFmax + 1); + + for (i = 1; i < top; ++i) { + c = js_touint32(J, i); + p += runetochar(p, &c); + } + *p = 0; + + js_pushstring(J, s); + js_endtry(J); + js_free(J, s); +} + +static void Sp_match(js_State *J) +{ + js_Regexp *re; + const char *text; + int len; + const char *a, *b, *c, *e; + Resub m; + + text = checkstring(J, 0); + + if (js_isregexp(J, 1)) + js_copy(J, 1); + else if (js_isundefined(J, 1)) + js_newregexp(J, "", 0); + else + js_newregexp(J, js_tostring(J, 1), 0); + + re = js_toregexp(J, -1); + if (!(re->flags & JS_REGEXP_G)) { + js_RegExp_prototype_exec(J, re, text); + return; + } + + re->last = 0; + + js_newarray(J); + + len = 0; + a = text; + e = text + strlen(text); + while (a <= e) { + if (js_doregexec(J, re->prog, a, &m, a > text ? REG_NOTBOL : 0)) + break; + + b = m.sub[0].sp; + c = m.sub[0].ep; + + js_pushlstring(J, b, c - b); + js_setindex(J, -2, len++); + + a = c; + if (c - b == 0) + ++a; + } + + if (len == 0) { + js_pop(J, 1); + js_pushnull(J); + } +} + +static void Sp_search(js_State *J) +{ + js_Regexp *re; + const char *text; + Resub m; + + text = checkstring(J, 0); + + if (js_isregexp(J, 1)) + js_copy(J, 1); + else if (js_isundefined(J, 1)) + js_newregexp(J, "", 0); + else + js_newregexp(J, js_tostring(J, 1), 0); + + re = js_toregexp(J, -1); + + if (!js_doregexec(J, re->prog, text, &m, 0)) + js_pushnumber(J, js_utfptrtoidx(text, m.sub[0].sp)); + else + js_pushnumber(J, -1); +} + +static void Sp_replace_regexp(js_State *J) +{ + js_Regexp *re; + const char *source, *s, *r; + js_Buffer *sb = NULL; + int n, x; + Resub m; + + source = checkstring(J, 0); + re = js_toregexp(J, 1); + + if (js_doregexec(J, re->prog, source, &m, 0)) { + js_copy(J, 0); + return; + } + + re->last = 0; + +loop: + s = m.sub[0].sp; + n = m.sub[0].ep - m.sub[0].sp; + + if (js_iscallable(J, 2)) { + js_copy(J, 2); + js_pushundefined(J); + for (x = 0; m.sub[x].sp; ++x) /* arg 0..x: substring and subexps that matched */ + js_pushlstring(J, m.sub[x].sp, m.sub[x].ep - m.sub[x].sp); + js_pushnumber(J, s - source); /* arg x+2: offset within search string */ + js_copy(J, 0); /* arg x+3: search string */ + js_call(J, 2 + x); + r = js_tostring(J, -1); + js_putm(J, &sb, source, s); + js_puts(J, &sb, r); + js_pop(J, 1); + } else { + r = js_tostring(J, 2); + js_putm(J, &sb, source, s); + while (*r) { + if (*r == '$') { + switch (*(++r)) { + case 0: --r; /* end of string; back up */ + /* fallthrough */ + case '$': js_putc(J, &sb, '$'); break; + case '`': js_putm(J, &sb, source, s); break; + case '\'': js_puts(J, &sb, s + n); break; + case '&': + js_putm(J, &sb, s, s + n); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + x = *r - '0'; + if (r[1] >= '0' && r[1] <= '9') + x = x * 10 + *(++r) - '0'; + if (x > 0 && x < m.nsub) { + js_putm(J, &sb, m.sub[x].sp, m.sub[x].ep); + } else { + js_putc(J, &sb, '$'); + if (x > 10) { + js_putc(J, &sb, '0' + x / 10); + js_putc(J, &sb, '0' + x % 10); + } else { + js_putc(J, &sb, '0' + x); + } + } + break; + default: + js_putc(J, &sb, '$'); + js_putc(J, &sb, *r); + break; + } + ++r; + } else { + js_putc(J, &sb, *r++); + } + } + } + + if (re->flags & JS_REGEXP_G) { + source = m.sub[0].ep; + if (n == 0) { + if (*source) + js_putc(J, &sb, *source++); + else + goto end; + } + if (!js_doregexec(J, re->prog, source, &m, REG_NOTBOL)) + goto loop; + } + +end: + js_puts(J, &sb, s + n); + js_putc(J, &sb, 0); + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + js_pushstring(J, sb ? sb->s : ""); + js_endtry(J); + js_free(J, sb); +} + +static void Sp_replace_string(js_State *J) +{ + const char *source, *needle, *s, *r; + js_Buffer *sb = NULL; + int n; + + source = checkstring(J, 0); + needle = js_tostring(J, 1); + + s = strstr(source, needle); + if (!s) { + js_copy(J, 0); + return; + } + n = strlen(needle); + + if (js_iscallable(J, 2)) { + js_copy(J, 2); + js_pushundefined(J); + js_pushlstring(J, s, n); /* arg 1: substring that matched */ + js_pushnumber(J, s - source); /* arg 2: offset within search string */ + js_copy(J, 0); /* arg 3: search string */ + js_call(J, 3); + r = js_tostring(J, -1); + js_putm(J, &sb, source, s); + js_puts(J, &sb, r); + js_puts(J, &sb, s + n); + js_putc(J, &sb, 0); + js_pop(J, 1); + } else { + r = js_tostring(J, 2); + js_putm(J, &sb, source, s); + while (*r) { + if (*r == '$') { + switch (*(++r)) { + case 0: --r; /* end of string; back up */ + /* fallthrough */ + case '$': js_putc(J, &sb, '$'); break; + case '&': js_putm(J, &sb, s, s + n); break; + case '`': js_putm(J, &sb, source, s); break; + case '\'': js_puts(J, &sb, s + n); break; + default: js_putc(J, &sb, '$'); js_putc(J, &sb, *r); break; + } + ++r; + } else { + js_putc(J, &sb, *r++); + } + } + js_puts(J, &sb, s + n); + js_putc(J, &sb, 0); + } + + if (js_try(J)) { + js_free(J, sb); + js_throw(J); + } + js_pushstring(J, sb ? sb->s : ""); + js_endtry(J); + js_free(J, sb); +} + +static void Sp_replace(js_State *J) +{ + if (js_isregexp(J, 1)) + Sp_replace_regexp(J); + else + Sp_replace_string(J); +} + +static void Sp_split_regexp(js_State *J) +{ + js_Regexp *re; + const char *text; + int limit, len, k; + const char *p, *a, *b, *c, *e; + Resub m; + + text = checkstring(J, 0); + re = js_toregexp(J, 1); + limit = js_isdefined(J, 2) ? js_tointeger(J, 2) : 1 << 30; + + js_newarray(J); + len = 0; + + if (limit == 0) + return; + + e = text + strlen(text); + + /* splitting the empty string */ + if (e == text) { + if (js_doregexec(J, re->prog, text, &m, 0)) { + js_pushliteral(J, ""); + js_setindex(J, -2, 0); + } + return; + } + + p = a = text; + while (a < e) { + if (js_doregexec(J, re->prog, a, &m, a > text ? REG_NOTBOL : 0)) + break; /* no match */ + + b = m.sub[0].sp; + c = m.sub[0].ep; + + /* empty string at end of last match */ + if (b == c && b == p) { + ++a; + continue; + } + + if (len == limit) return; + js_pushlstring(J, p, b - p); + js_setindex(J, -2, len++); + + for (k = 1; k < m.nsub; ++k) { + if (len == limit) return; + js_pushlstring(J, m.sub[k].sp, m.sub[k].ep - m.sub[k].sp); + js_setindex(J, -2, len++); + } + + a = p = c; + } + + if (len == limit) return; + js_pushstring(J, p); + js_setindex(J, -2, len); +} + +static void Sp_split_string(js_State *J) +{ + const char *str = checkstring(J, 0); + const char *sep = js_tostring(J, 1); + int limit = js_isdefined(J, 2) ? js_tointeger(J, 2) : 1 << 30; + int i, n; + + js_newarray(J); + + if (limit == 0) + return; + + n = strlen(sep); + + /* empty string */ + if (n == 0) { + Rune rune; + for (i = 0; *str && i < limit; ++i) { + n = chartorune(&rune, str); + js_pushlstring(J, str, n); + js_setindex(J, -2, i); + str += n; + } + return; + } + + for (i = 0; str && i < limit; ++i) { + const char *s = strstr(str, sep); + if (s) { + js_pushlstring(J, str, s-str); + js_setindex(J, -2, i); + str = s + n; + } else { + js_pushstring(J, str); + js_setindex(J, -2, i); + str = NULL; + } + } +} + +static void Sp_split(js_State *J) +{ + if (js_isundefined(J, 1)) { + js_newarray(J); + js_pushstring(J, js_tostring(J, 0)); + js_setindex(J, -2, 0); + } else if (js_isregexp(J, 1)) { + Sp_split_regexp(J); + } else { + Sp_split_string(J); + } +} + +void jsB_initstring(js_State *J) +{ + J->String_prototype->u.s.shrstr[0] = 0; + J->String_prototype->u.s.string = J->String_prototype->u.s.shrstr; + J->String_prototype->u.s.length = 0; + + js_pushobject(J, J->String_prototype); + { + jsB_propf(J, "String.prototype.toString", Sp_toString, 0); + jsB_propf(J, "String.prototype.valueOf", Sp_valueOf, 0); + jsB_propf(J, "String.prototype.charAt", Sp_charAt, 1); + jsB_propf(J, "String.prototype.charCodeAt", Sp_charCodeAt, 1); + jsB_propf(J, "String.prototype.concat", Sp_concat, 0); /* 1 */ + jsB_propf(J, "String.prototype.indexOf", Sp_indexOf, 1); + jsB_propf(J, "String.prototype.lastIndexOf", Sp_lastIndexOf, 1); + jsB_propf(J, "String.prototype.localeCompare", Sp_localeCompare, 1); + jsB_propf(J, "String.prototype.match", Sp_match, 1); + jsB_propf(J, "String.prototype.replace", Sp_replace, 2); + jsB_propf(J, "String.prototype.search", Sp_search, 1); + jsB_propf(J, "String.prototype.slice", Sp_slice, 2); + jsB_propf(J, "String.prototype.split", Sp_split, 2); + jsB_propf(J, "String.prototype.substring", Sp_substring, 2); + jsB_propf(J, "String.prototype.toLowerCase", Sp_toLowerCase, 0); + jsB_propf(J, "String.prototype.toLocaleLowerCase", Sp_toLowerCase, 0); + jsB_propf(J, "String.prototype.toUpperCase", Sp_toUpperCase, 0); + jsB_propf(J, "String.prototype.toLocaleUpperCase", Sp_toUpperCase, 0); + + /* ES5 */ + jsB_propf(J, "String.prototype.trim", Sp_trim, 0); + } + js_newcconstructor(J, jsB_String, jsB_new_String, "String", 0); /* 1 */ + { + jsB_propf(J, "String.fromCharCode", S_fromCharCode, 0); /* 1 */ + } + js_defglobal(J, "String", JS_DONTENUM); +} diff --git a/mujs/jsvalue.c b/mujs/jsvalue.c new file mode 100644 index 0000000..4cbdd49 --- /dev/null +++ b/mujs/jsvalue.c @@ -0,0 +1,708 @@ +#include "jsi.h" +#include "utf.h" + +#define JSV_ISSTRING(v) (v->t.type==JS_TSHRSTR || v->t.type==JS_TMEMSTR || v->t.type==JS_TLITSTR) +#define JSV_TOSTRING(v) (v->t.type==JS_TSHRSTR ? v->u.shrstr : v->t.type==JS_TLITSTR ? v->u.litstr : v->t.type==JS_TMEMSTR ? v->u.memstr->p : "") + +double js_strtol(const char *s, char **p, int base) +{ + /* ascii -> digit value. max base is 36. */ + static const unsigned char table[256] = { + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 80, 80, 80, 80, 80, 80, + 80, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 80, 80, 80, 80, 80, + 80, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80 + }; + double x; + unsigned char c; + if (base == 10) + for (x = 0, c = *s++; (0 <= c - '0') && (c - '0' < 10); c = *s++) + x = x * 10 + (c - '0'); + else + for (x = 0, c = *s++; table[c] < base; c = *s++) + x = x * base + table[c]; + if (p) + *p = (char*)s-1; + return x; +} + +int jsV_numbertointeger(double n) +{ + if (n == 0) return 0; + if (isnan(n)) return 0; + n = (n < 0) ? -floor(-n) : floor(n); + if (n < INT_MIN) return INT_MIN; + if (n > INT_MAX) return INT_MAX; + return (int)n; +} + +int jsV_numbertoint32(double n) +{ + double two32 = 4294967296.0; + double two31 = 2147483648.0; + + if (!isfinite(n) || n == 0) + return 0; + + n = fmod(n, two32); + n = n >= 0 ? floor(n) : ceil(n) + two32; + if (n >= two31) + return n - two32; + else + return n; +} + +unsigned int jsV_numbertouint32(double n) +{ + return (unsigned int)jsV_numbertoint32(n); +} + +short jsV_numbertoint16(double n) +{ + return jsV_numbertoint32(n); +} + +unsigned short jsV_numbertouint16(double n) +{ + return jsV_numbertoint32(n); +} + +/* obj.toString() */ +static int jsV_toString(js_State *J, js_Object *obj) +{ + js_pushobject(J, obj); + js_getproperty(J, -1, "toString"); + if (js_iscallable(J, -1)) { + js_rot2(J); + js_call(J, 0); + if (js_isprimitive(J, -1)) + return 1; + js_pop(J, 1); + return 0; + } + js_pop(J, 2); + return 0; +} + +/* obj.valueOf() */ +static int jsV_valueOf(js_State *J, js_Object *obj) +{ + js_pushobject(J, obj); + js_getproperty(J, -1, "valueOf"); + if (js_iscallable(J, -1)) { + js_rot2(J); + js_call(J, 0); + if (js_isprimitive(J, -1)) + return 1; + js_pop(J, 1); + return 0; + } + js_pop(J, 2); + return 0; +} + +/* ToPrimitive() on a value */ +void jsV_toprimitive(js_State *J, js_Value *v, int preferred) +{ + js_Object *obj; + + if (v->t.type != JS_TOBJECT) + return; + + obj = v->u.object; + + if (preferred == JS_HNONE) + preferred = obj->type == JS_CDATE ? JS_HSTRING : JS_HNUMBER; + + if (preferred == JS_HSTRING) { + if (jsV_toString(J, obj) || jsV_valueOf(J, obj)) { + *v = *js_tovalue(J, -1); + js_pop(J, 1); + return; + } + } else { + if (jsV_valueOf(J, obj) || jsV_toString(J, obj)) { + *v = *js_tovalue(J, -1); + js_pop(J, 1); + return; + } + } + + if (J->strict) + js_typeerror(J, "cannot convert object to primitive"); + + v->t.type = JS_TLITSTR; + v->u.litstr = "[object]"; + return; +} + +/* ToBoolean() on a value */ +int jsV_toboolean(js_State *J, js_Value *v) +{ + switch (v->t.type) { + default: + case JS_TSHRSTR: return v->u.shrstr[0] != 0; + case JS_TUNDEFINED: return 0; + case JS_TNULL: return 0; + case JS_TBOOLEAN: return v->u.boolean; + case JS_TNUMBER: return v->u.number != 0 && !isnan(v->u.number); + case JS_TLITSTR: return v->u.litstr[0] != 0; + case JS_TMEMSTR: return v->u.memstr->p[0] != 0; + case JS_TOBJECT: return 1; + } +} + +const char *js_itoa(char *out, int v) +{ + char buf[32], *s = out; + unsigned int a; + int i = 0; + if (v < 0) { + a = -v; + *s++ = '-'; + } else { + a = v; + } + while (a) { + buf[i++] = (a % 10) + '0'; + a /= 10; + } + if (i == 0) + buf[i++] = '0'; + while (i > 0) + *s++ = buf[--i]; + *s = 0; + return out; +} + +double js_stringtofloat(const char *s, char **ep) +{ + char *end; + double n; + const char *e = s; + int isflt = 0; + if (*e == '+' || *e == '-') ++e; + while (*e >= '0' && *e <= '9') ++e; + if (*e == '.') { ++e; isflt = 1; } + while (*e >= '0' && *e <= '9') ++e; + if (*e == 'e' || *e == 'E') { + ++e; + if (*e == '+' || *e == '-') ++e; + while (*e >= '0' && *e <= '9') ++e; + isflt = 1; + } + if (isflt) + n = js_strtod(s, &end); + else { + /* js_strtol doesn't parse the sign */ + if (*s == '-') + n = -js_strtol(s+1, &end, 10); + else if (*s == '+') + n = js_strtol(s+1, &end, 10); + else + n = js_strtol(s, &end, 10); + } + if (end == e) { + *ep = (char*)e; + return n; + } + *ep = (char*)s; + return 0; +} + +/* ToNumber() on a string */ +double jsV_stringtonumber(js_State *J, const char *s) +{ + char *e; + double n; + while (jsY_iswhite(*s) || jsY_isnewline(*s)) ++s; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && s[2] != 0) + n = js_strtol(s + 2, &e, 16); + else if (!strncmp(s, "Infinity", 8)) + n = INFINITY, e = (char*)s + 8; + else if (!strncmp(s, "+Infinity", 9)) + n = INFINITY, e = (char*)s + 9; + else if (!strncmp(s, "-Infinity", 9)) + n = -INFINITY, e = (char*)s + 9; + else + n = js_stringtofloat(s, &e); + while (jsY_iswhite(*e) || jsY_isnewline(*e)) ++e; + if (*e) return NAN; + return n; +} + +/* ToNumber() on a value */ +double jsV_tonumber(js_State *J, js_Value *v) +{ + switch (v->t.type) { + default: + case JS_TSHRSTR: return jsV_stringtonumber(J, v->u.shrstr); + case JS_TUNDEFINED: return NAN; + case JS_TNULL: return 0; + case JS_TBOOLEAN: return v->u.boolean; + case JS_TNUMBER: return v->u.number; + case JS_TLITSTR: return jsV_stringtonumber(J, v->u.litstr); + case JS_TMEMSTR: return jsV_stringtonumber(J, v->u.memstr->p); + case JS_TOBJECT: + jsV_toprimitive(J, v, JS_HNUMBER); + return jsV_tonumber(J, v); + } +} + +double jsV_tointeger(js_State *J, js_Value *v) +{ + return jsV_numbertointeger(jsV_tonumber(J, v)); +} + +/* ToString() on a number */ +const char *jsV_numbertostring(js_State *J, char buf[32], double f) +{ + char digits[32], *p = buf, *s = digits; + int exp, ndigits, point; + + if (f == 0) return "0"; + if (isnan(f)) return "NaN"; + if (isinf(f)) return f < 0 ? "-Infinity" : "Infinity"; + + /* Fast case for integers. This only works assuming all integers can be + * exactly represented by a float. This is true for 32-bit integers and + * 64-bit floats. */ + if (f >= INT_MIN && f <= INT_MAX) { + int i = (int)f; + if ((double)i == f) + return js_itoa(buf, i); + } + + ndigits = js_grisu2(f, digits, &exp); + point = ndigits + exp; + + if (signbit(f)) + *p++ = '-'; + + if (point < -5 || point > 21) { + *p++ = *s++; + if (ndigits > 1) { + int n = ndigits - 1; + *p++ = '.'; + while (n--) + *p++ = *s++; + } + js_fmtexp(p, point - 1); + } + + else if (point <= 0) { + *p++ = '0'; + *p++ = '.'; + while (point++ < 0) + *p++ = '0'; + while (ndigits-- > 0) + *p++ = *s++; + *p = 0; + } + + else { + while (ndigits-- > 0) { + *p++ = *s++; + if (--point == 0 && ndigits > 0) + *p++ = '.'; + } + while (point-- > 0) + *p++ = '0'; + *p = 0; + } + + return buf; +} + +/* ToString() on a value */ +const char *jsV_tostring(js_State *J, js_Value *v) +{ + char buf[32]; + const char *p; + switch (v->t.type) { + default: + case JS_TSHRSTR: return v->u.shrstr; + case JS_TUNDEFINED: return "undefined"; + case JS_TNULL: return "null"; + case JS_TBOOLEAN: return v->u.boolean ? "true" : "false"; + case JS_TLITSTR: return v->u.litstr; + case JS_TMEMSTR: return v->u.memstr->p; + case JS_TNUMBER: + p = jsV_numbertostring(J, buf, v->u.number); + if (p == buf) { + int n = strlen(p); + if (n <= soffsetof(js_Value, t.type)) { + char *s = v->u.shrstr; + while (n--) *s++ = *p++; + *s = 0; + v->t.type = JS_TSHRSTR; + return v->u.shrstr; + } else { + v->u.memstr = jsV_newmemstring(J, p, n); + v->t.type = JS_TMEMSTR; + return v->u.memstr->p; + } + } + return p; + case JS_TOBJECT: + jsV_toprimitive(J, v, JS_HSTRING); + return jsV_tostring(J, v); + } +} + +/* Objects */ + +static js_Object *jsV_newboolean(js_State *J, int v) +{ + js_Object *obj = jsV_newobject(J, JS_CBOOLEAN, J->Boolean_prototype); + obj->u.boolean = v; + return obj; +} + +static js_Object *jsV_newnumber(js_State *J, double v) +{ + js_Object *obj = jsV_newobject(J, JS_CNUMBER, J->Number_prototype); + obj->u.number = v; + return obj; +} + +static js_Object *jsV_newstring(js_State *J, const char *v) +{ + js_Object *obj = jsV_newobject(J, JS_CSTRING, J->String_prototype); + size_t n = strlen(v); + if (n < sizeof(obj->u.s.shrstr)) { + obj->u.s.string = obj->u.s.shrstr; + memcpy(obj->u.s.shrstr, v, n + 1); + } else { + obj->u.s.string = js_strdup(J, v); + } + obj->u.s.length = js_utflen(v); + return obj; +} + +/* ToObject() on a value */ +js_Object *jsV_toobject(js_State *J, js_Value *v) +{ + js_Object *o; + switch (v->t.type) { + default: + case JS_TUNDEFINED: js_typeerror(J, "cannot convert undefined to object"); + case JS_TNULL: js_typeerror(J, "cannot convert null to object"); + case JS_TOBJECT: return v->u.object; + case JS_TSHRSTR: o = jsV_newstring(J, v->u.shrstr); break; + case JS_TLITSTR: o = jsV_newstring(J, v->u.litstr); break; + case JS_TMEMSTR: o = jsV_newstring(J, v->u.memstr->p); break; + case JS_TBOOLEAN: o = jsV_newboolean(J, v->u.boolean); break; + case JS_TNUMBER: o = jsV_newnumber(J, v->u.number); break; + } + v->t.type = JS_TOBJECT; + v->u.object = o; + return o; +} + +void js_newobjectx(js_State *J) +{ + js_Object *prototype = NULL; + if (js_isobject(J, -1)) + prototype = js_toobject(J, -1); + js_pop(J, 1); + js_pushobject(J, jsV_newobject(J, JS_COBJECT, prototype)); +} + +void js_newobject(js_State *J) +{ + js_pushobject(J, jsV_newobject(J, JS_COBJECT, J->Object_prototype)); +} + +void js_newarguments(js_State *J) +{ + js_pushobject(J, jsV_newobject(J, JS_CARGUMENTS, J->Object_prototype)); +} + +void js_newarray(js_State *J) +{ + js_Object *obj = jsV_newobject(J, JS_CARRAY, J->Array_prototype); + obj->u.a.simple = 1; + js_pushobject(J, obj); +} + +void js_newboolean(js_State *J, int v) +{ + js_pushobject(J, jsV_newboolean(J, v)); +} + +void js_newnumber(js_State *J, double v) +{ + js_pushobject(J, jsV_newnumber(J, v)); +} + +void js_newstring(js_State *J, const char *v) +{ + js_pushobject(J, jsV_newstring(J, v)); +} + +void js_newfunction(js_State *J, js_Function *fun, js_Environment *scope) +{ + js_Object *obj = jsV_newobject(J, JS_CFUNCTION, J->Function_prototype); + obj->u.f.function = fun; + obj->u.f.scope = scope; + js_pushobject(J, obj); + { + js_pushnumber(J, fun->numparams); + js_defproperty(J, -2, "length", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + js_newobject(J); + { + js_copy(J, -2); + js_defproperty(J, -2, "constructor", JS_DONTENUM); + } + js_defproperty(J, -2, "prototype", JS_DONTENUM | JS_DONTCONF); + } +} + +void js_newscript(js_State *J, js_Function *fun, js_Environment *scope) +{ + js_Object *obj = jsV_newobject(J, JS_CSCRIPT, NULL); + obj->u.f.function = fun; + obj->u.f.scope = scope; + js_pushobject(J, obj); +} + +void js_newcfunctionx(js_State *J, js_CFunction cfun, const char *name, int length, void *data, js_Finalize finalize) +{ + js_Object *obj; + + if (js_try(J)) { + if (finalize) + finalize(J, data); + js_throw(J); + } + + obj = jsV_newobject(J, JS_CCFUNCTION, J->Function_prototype); + obj->u.c.name = name; + obj->u.c.function = cfun; + obj->u.c.constructor = NULL; + obj->u.c.length = length; + obj->u.c.data = data; + obj->u.c.finalize = finalize; + + js_endtry(J); + + js_pushobject(J, obj); + { + js_pushnumber(J, length); + js_defproperty(J, -2, "length", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + js_newobject(J); + { + js_copy(J, -2); + js_defproperty(J, -2, "constructor", JS_DONTENUM); + } + js_defproperty(J, -2, "prototype", JS_DONTENUM | JS_DONTCONF); + } +} + +void js_newcfunction(js_State *J, js_CFunction cfun, const char *name, int length) +{ + js_newcfunctionx(J, cfun, name, length, NULL, NULL); +} + +/* prototype -- constructor */ +void js_newcconstructor(js_State *J, js_CFunction cfun, js_CFunction ccon, const char *name, int length) +{ + js_Object *obj = jsV_newobject(J, JS_CCFUNCTION, J->Function_prototype); + obj->u.c.name = name; + obj->u.c.function = cfun; + obj->u.c.constructor = ccon; + obj->u.c.length = length; + js_pushobject(J, obj); /* proto obj */ + { + js_pushnumber(J, length); + js_defproperty(J, -2, "length", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + js_rot2(J); /* obj proto */ + js_copy(J, -2); /* obj proto obj */ + js_defproperty(J, -2, "constructor", JS_DONTENUM); + js_defproperty(J, -2, "prototype", JS_DONTENUM | JS_DONTCONF); + } +} + +void js_newuserdatax(js_State *J, const char *tag, void *data, js_HasProperty has, js_Put put, js_Delete delete, js_Finalize finalize) +{ + js_Object *prototype = NULL; + js_Object *obj; + + if (js_isobject(J, -1)) + prototype = js_toobject(J, -1); + js_pop(J, 1); + + if (js_try(J)) { + if (finalize) + finalize(J, data); + js_throw(J); + } + + obj = jsV_newobject(J, JS_CUSERDATA, prototype); + obj->u.user.tag = tag; + obj->u.user.data = data; + obj->u.user.has = has; + obj->u.user.put = put; + obj->u.user.delete = delete; + obj->u.user.finalize = finalize; + + js_endtry(J); + + js_pushobject(J, obj); +} + +void js_newuserdata(js_State *J, const char *tag, void *data, js_Finalize finalize) +{ + js_newuserdatax(J, tag, data, NULL, NULL, NULL, finalize); +} + +/* Non-trivial operations on values. These are implemented using the stack. */ + +int js_instanceof(js_State *J) +{ + js_Object *O, *V; + + if (!js_iscallable(J, -1)) + js_typeerror(J, "instanceof: invalid operand"); + + if (!js_isobject(J, -2)) + return 0; + + js_getproperty(J, -1, "prototype"); + if (!js_isobject(J, -1)) + js_typeerror(J, "instanceof: 'prototype' property is not an object"); + O = js_toobject(J, -1); + js_pop(J, 1); + + V = js_toobject(J, -2); + while (V) { + V = V->prototype; + if (O == V) + return 1; + } + + return 0; +} + +void js_concat(js_State *J) +{ + js_toprimitive(J, -2, JS_HNONE); + js_toprimitive(J, -1, JS_HNONE); + + if (js_isstring(J, -2) || js_isstring(J, -1)) { + const char *sa = js_tostring(J, -2); + const char *sb = js_tostring(J, -1); + char * volatile sab = NULL; + /* TODO: create js_String directly */ + if (js_try(J)) { + js_free(J, sab); + js_throw(J); + } + sab = js_malloc(J, strlen(sa) + strlen(sb) + 1); + strcpy(sab, sa); + strcat(sab, sb); + js_pop(J, 2); + js_pushstring(J, sab); + js_endtry(J); + js_free(J, sab); + } else { + double x = js_tonumber(J, -2); + double y = js_tonumber(J, -1); + js_pop(J, 2); + js_pushnumber(J, x + y); + } +} + +int js_compare(js_State *J, int *okay) +{ + js_toprimitive(J, -2, JS_HNUMBER); + js_toprimitive(J, -1, JS_HNUMBER); + + *okay = 1; + if (js_isstring(J, -2) && js_isstring(J, -1)) { + return strcmp(js_tostring(J, -2), js_tostring(J, -1)); + } else { + double x = js_tonumber(J, -2); + double y = js_tonumber(J, -1); + if (isnan(x) || isnan(y)) + *okay = 0; + return x < y ? -1 : x > y ? 1 : 0; + } +} + +int js_equal(js_State *J) +{ + js_Value *x = js_tovalue(J, -2); + js_Value *y = js_tovalue(J, -1); + +retry: + if (JSV_ISSTRING(x) && JSV_ISSTRING(y)) + return !strcmp(JSV_TOSTRING(x), JSV_TOSTRING(y)); + if (x->t.type == y->t.type) { + if (x->t.type == JS_TUNDEFINED) return 1; + if (x->t.type == JS_TNULL) return 1; + if (x->t.type == JS_TNUMBER) return x->u.number == y->u.number; + if (x->t.type == JS_TBOOLEAN) return x->u.boolean == y->u.boolean; + if (x->t.type == JS_TOBJECT) return x->u.object == y->u.object; + return 0; + } + + if (x->t.type == JS_TNULL && y->t.type == JS_TUNDEFINED) return 1; + if (x->t.type == JS_TUNDEFINED && y->t.type == JS_TNULL) return 1; + + if (x->t.type == JS_TNUMBER && JSV_ISSTRING(y)) + return x->u.number == jsV_tonumber(J, y); + if (JSV_ISSTRING(x) && y->t.type == JS_TNUMBER) + return jsV_tonumber(J, x) == y->u.number; + + if (x->t.type == JS_TBOOLEAN) { + x->t.type = JS_TNUMBER; + x->u.number = x->u.boolean ? 1 : 0; + goto retry; + } + if (y->t.type == JS_TBOOLEAN) { + y->t.type = JS_TNUMBER; + y->u.number = y->u.boolean ? 1 : 0; + goto retry; + } + if ((JSV_ISSTRING(x) || x->t.type == JS_TNUMBER) && y->t.type == JS_TOBJECT) { + jsV_toprimitive(J, y, JS_HNONE); + goto retry; + } + if (x->t.type == JS_TOBJECT && (JSV_ISSTRING(y) || y->t.type == JS_TNUMBER)) { + jsV_toprimitive(J, x, JS_HNONE); + goto retry; + } + + return 0; +} + +int js_strictequal(js_State *J) +{ + js_Value *x = js_tovalue(J, -2); + js_Value *y = js_tovalue(J, -1); + + if (JSV_ISSTRING(x) && JSV_ISSTRING(y)) + return !strcmp(JSV_TOSTRING(x), JSV_TOSTRING(y)); + + if (x->t.type != y->t.type) return 0; + if (x->t.type == JS_TUNDEFINED) return 1; + if (x->t.type == JS_TNULL) return 1; + if (x->t.type == JS_TNUMBER) return x->u.number == y->u.number; + if (x->t.type == JS_TBOOLEAN) return x->u.boolean == y->u.boolean; + if (x->t.type == JS_TOBJECT) return x->u.object == y->u.object; + return 0; +} diff --git a/mujs/main.c b/mujs/main.c new file mode 100644 index 0000000..fb8c986 --- /dev/null +++ b/mujs/main.c @@ -0,0 +1,394 @@ +#include +#include +#include +#ifdef _MSC_VER +#include +#else +#include +#endif +#include + +#include "mujs.h" + +static char *xoptarg; /* Global argument pointer. */ +static int xoptind = 0; /* Global argv index. */ +static int xgetopt(int argc, char *argv[], char *optstring) +{ + static char *scan = NULL; /* Private scan pointer. */ + + char c; + char *place; + + xoptarg = NULL; + + if (!scan || *scan == '\0') { + if (xoptind == 0) + xoptind++; + + if (xoptind >= argc || argv[xoptind][0] != '-' || argv[xoptind][1] == '\0') + return EOF; + if (argv[xoptind][1] == '-' && argv[xoptind][2] == '\0') { + xoptind++; + return EOF; + } + + scan = argv[xoptind]+1; + xoptind++; + } + + c = *scan++; + place = strchr(optstring, c); + + if (!place || c == ':') { + fprintf(stderr, "%s: unknown option -%c\n", argv[0], c); + return '?'; + } + + place++; + if (*place == ':') { + if (*scan != '\0') { + xoptarg = scan; + scan = NULL; + } else if (xoptind < argc) { + xoptarg = argv[xoptind]; + xoptind++; + } else { + fprintf(stderr, "%s: option requires argument -%c\n", argv[0], c); + return ':'; + } + } + + return c; +} + +#ifdef HAVE_READLINE +#include +#include +#else +void using_history(void) { } +void add_history(const char *string) { } +void rl_bind_key(int key, void (*fun)(void)) { } +void rl_insert(void) { } +char *readline(const char *prompt) +{ + static char line[500], *p; + int n; + fputs(prompt, stdout); + p = fgets(line, sizeof line, stdin); + if (p) { + n = strlen(line); + if (n > 0 && line[n-1] == '\n') + line[--n] = 0; + p = malloc(n+1); + memcpy(p, line, n+1); + return p; + } + return NULL; +} +#endif + +#define PS1 "> " + +static void jsB_gc(js_State *J) +{ + int report = js_toboolean(J, 1); + js_gc(J, report); + js_pushundefined(J); +} + +static void jsB_load(js_State *J) +{ + int i, n = js_gettop(J); + for (i = 1; i < n; ++i) { + js_loadfile(J, js_tostring(J, i)); + js_pushundefined(J); + js_call(J, 0); + js_pop(J, 1); + } + js_pushundefined(J); +} + +static void jsB_compile(js_State *J) +{ + const char *source = js_tostring(J, 1); + const char *filename = js_isdefined(J, 2) ? js_tostring(J, 2) : "[string]"; + js_loadstring(J, filename, source); +} + +static void jsB_print(js_State *J) +{ + int i, top = js_gettop(J); + for (i = 1; i < top; ++i) { + const char *s = js_tostring(J, i); + if (i > 1) putchar(' '); + fputs(s, stdout); + } + putchar('\n'); + js_pushundefined(J); +} + +static void jsB_write(js_State *J) +{ + int i, top = js_gettop(J); + for (i = 1; i < top; ++i) { + const char *s = js_tostring(J, i); + if (i > 1) putchar(' '); + fputs(s, stdout); + } + js_pushundefined(J); +} + +static void jsB_read(js_State *J) +{ + const char *filename = js_tostring(J, 1); + FILE *f; + char *s; + int n, t; + + f = fopen(filename, "rb"); + if (!f) { + js_error(J, "cannot open file '%s': %s", filename, strerror(errno)); + } + + if (fseek(f, 0, SEEK_END) < 0) { + fclose(f); + js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); + } + + n = ftell(f); + if (n < 0) { + fclose(f); + js_error(J, "cannot tell in file '%s': %s", filename, strerror(errno)); + } + + if (fseek(f, 0, SEEK_SET) < 0) { + fclose(f); + js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); + } + + s = malloc(n + 1); + if (!s) { + fclose(f); + js_error(J, "out of memory"); + } + + t = fread(s, 1, n, f); + if (t != n) { + free(s); + fclose(f); + js_error(J, "cannot read data from file '%s': %s", filename, strerror(errno)); + } + s[n] = 0; + + js_pushstring(J, s); + free(s); + fclose(f); +} + +static void jsB_readline(js_State *J) +{ + char *line = readline(""); + if (!line) { + js_pushnull(J); + return; + } + js_pushstring(J, line); + if (*line) + add_history(line); + free(line); +} + +static void jsB_quit(js_State *J) +{ + exit(js_tonumber(J, 1)); +} + +static void jsB_repr(js_State *J) +{ + js_repr(J, 1); +} + +static const char *require_js = + "function require(name) {\n" + "var cache = require.cache;\n" + "if (name in cache) return cache[name];\n" + "var exports = {};\n" + "cache[name] = exports;\n" + "Function('exports', read(name+'.js'))(exports);\n" + "return exports;\n" + "}\n" + "require.cache = Object.create(null);\n" +; + + +static const char *stacktrace_js = + "Error.prototype.toString = function() {\n" + "var s = this.name;\n" + "if ('message' in this) s += ': ' + this.message;\n" + "if ('stackTrace' in this) s += this.stackTrace;\n" + "return s;\n" + "};\n" +; + +static const char *console_js = + "var console = { log: print, debug: print, warn: print, error: print };" +; + +static int eval_print(js_State *J, const char *source) +{ + if (js_ploadstring(J, "[stdin]", source)) { + fprintf(stderr, "%s\n", js_trystring(J, -1, "Error")); + js_pop(J, 1); + return 1; + } + js_pushundefined(J); + if (js_pcall(J, 0)) { + fprintf(stderr, "%s\n", js_trystring(J, -1, "Error")); + js_pop(J, 1); + return 1; + } + if (js_isdefined(J, -1)) { + printf("%s\n", js_tryrepr(J, -1, "can't convert to string")); + } + js_pop(J, 1); + return 0; +} + +static char *read_stdin(void) +{ + int n = 0; + int t = 512; + char *s = NULL; + + for (;;) { + char *ss = realloc(s, t); + if (!ss) { + free(s); + fprintf(stderr, "cannot allocate storage for stdin contents\n"); + return NULL; + } + s = ss; + n += fread(s + n, 1, t - n - 1, stdin); + if (n < t - 1) + break; + t *= 2; + } + + if (ferror(stdin)) { + free(s); + fprintf(stderr, "error reading stdin\n"); + return NULL; + } + + s[n] = 0; + return s; +} + +static void usage(void) +{ + fprintf(stderr, "Usage: mujs [options] [script [scriptArgs*]]\n"); + fprintf(stderr, "\t-i: Enter interactive prompt after running code.\n"); + fprintf(stderr, "\t-s: Check strictness.\n"); + exit(1); +} + +int +main(int argc, char **argv) +{ + char *input; + js_State *J; + int status = 0; + int strict = 0; + int interactive = 0; + int i, c; + + while ((c = xgetopt(argc, argv, "is")) != -1) { + switch (c) { + default: usage(); break; + case 'i': interactive = 1; break; + case 's': strict = 1; break; + } + } + + J = js_newstate(NULL, NULL, strict ? JS_STRICT : 0); + if (!J) { + fprintf(stderr, "Could not initialize MuJS.\n"); + exit(1); + } + + js_newcfunction(J, jsB_gc, "gc", 0); + js_setglobal(J, "gc"); + + js_newcfunction(J, jsB_load, "load", 1); + js_setglobal(J, "load"); + + js_newcfunction(J, jsB_compile, "compile", 2); + js_setglobal(J, "compile"); + + js_newcfunction(J, jsB_print, "print", 0); + js_setglobal(J, "print"); + + js_newcfunction(J, jsB_write, "write", 0); + js_setglobal(J, "write"); + + js_newcfunction(J, jsB_read, "read", 1); + js_setglobal(J, "read"); + + js_newcfunction(J, jsB_readline, "readline", 0); + js_setglobal(J, "readline"); + + js_newcfunction(J, jsB_repr, "repr", 0); + js_setglobal(J, "repr"); + + js_newcfunction(J, jsB_quit, "quit", 1); + js_setglobal(J, "quit"); + + js_dostring(J, require_js); + js_dostring(J, stacktrace_js); + js_dostring(J, console_js); + + if (xoptind == argc) { + interactive = 1; + } else { + c = xoptind++; + + js_newarray(J); + i = 0; + while (xoptind < argc) { + js_pushstring(J, argv[xoptind++]); + js_setindex(J, -2, i++); + } + js_setglobal(J, "scriptArgs"); + + if (js_dofile(J, argv[c])) + status = 1; + } + + if (interactive) { + printf("Welcome to MuJS %d.%d.%d.\n", + JS_VERSION_MAJOR, JS_VERSION_MINOR, JS_VERSION_PATCH); + if (isatty(0)) { + using_history(); + rl_bind_key('\t', rl_insert); + input = readline(PS1); + while (input) { + eval_print(J, input); + if (*input) + add_history(input); + free(input); + input = readline(PS1); + } + putchar('\n'); + } else { + input = read_stdin(); + if (!input || !js_dostring(J, input)) + status = 1; + free(input); + } + } + + js_gc(J, 0); + js_freestate(J); + + return status; +} diff --git a/mujs/mujs.h b/mujs/mujs.h new file mode 100644 index 0000000..fa69634 --- /dev/null +++ b/mujs/mujs.h @@ -0,0 +1,252 @@ +#ifndef mujs_h +#define mujs_h + +#include /* required for setjmp in fz_try macro */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define JS_VERSION_MAJOR 1 +#define JS_VERSION_MINOR 3 +#define JS_VERSION_PATCH 5 + +#define JS_VERSION (JS_VERSION_MAJOR * 10000 + JS_VERSION_MINOR * 100 + JS_VERSION_PATCH) +#define JS_CHECKVERSION(x,y,z) (JS_VERSION >= ((x) * 10000 + (y) * 100 + (z))) + +/* noreturn is a GCC extension */ +#ifdef __GNUC__ +#define JS_NORETURN __attribute__((noreturn)) +#else +#ifdef _MSC_VER +#define JS_NORETURN __declspec(noreturn) +#else +#define JS_NORETURN +#endif +#endif + +/* GCC can do type checking of printf strings */ +#ifdef __printflike +#define JS_PRINTFLIKE __printflike +#else +#if __GNUC__ > 2 || __GNUC__ == 2 && __GNUC_MINOR__ >= 7 +#define JS_PRINTFLIKE(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#else +#define JS_PRINTFLIKE(fmtarg, firstvararg) +#endif +#endif + +typedef struct js_State js_State; + +typedef void *(*js_Alloc)(void *memctx, void *ptr, int size); +typedef void (*js_Panic)(js_State *J); +typedef void (*js_CFunction)(js_State *J); +typedef void (*js_Finalize)(js_State *J, void *p); +typedef int (*js_HasProperty)(js_State *J, void *p, const char *name); +typedef int (*js_Put)(js_State *J, void *p, const char *name); +typedef int (*js_Delete)(js_State *J, void *p, const char *name); +typedef void (*js_Report)(js_State *J, const char *message); + +/* Basic functions */ +js_State *js_newstate(js_Alloc alloc, void *actx, int flags); +void js_setcontext(js_State *J, void *uctx); +void *js_getcontext(js_State *J); +void js_setreport(js_State *J, js_Report report); +js_Panic js_atpanic(js_State *J, js_Panic panic); +void js_freestate(js_State *J); +void js_gc(js_State *J, int report); + +int js_dostring(js_State *J, const char *source); +int js_dofile(js_State *J, const char *filename); +int js_ploadstring(js_State *J, const char *filename, const char *source); +int js_ploadfile(js_State *J, const char *filename); +int js_pcall(js_State *J, int n); +int js_pconstruct(js_State *J, int n); + +/* Exception handling */ + +void *js_savetry(js_State *J); /* returns a jmp_buf */ + +#define js_try(J) \ + setjmp(js_savetry(J)) + +void js_endtry(js_State *J); + +/* State constructor flags */ +enum { + JS_STRICT = 1, +}; + +/* RegExp flags */ +enum { + JS_REGEXP_G = 1, + JS_REGEXP_I = 2, + JS_REGEXP_M = 4, +}; + +/* Property attribute flags */ +enum { + JS_READONLY = 1, + JS_DONTENUM = 2, + JS_DONTCONF = 4, +}; + +/* enum for js_type() */ +enum { + JS_ISUNDEFINED, + JS_ISNULL, + JS_ISBOOLEAN, + JS_ISNUMBER, + JS_ISSTRING, + JS_ISFUNCTION, + JS_ISOBJECT +}; + +void js_report(js_State *J, const char *message); + +void js_newerror(js_State *J, const char *message); +void js_newevalerror(js_State *J, const char *message); +void js_newrangeerror(js_State *J, const char *message); +void js_newreferenceerror(js_State *J, const char *message); +void js_newsyntaxerror(js_State *J, const char *message); +void js_newtypeerror(js_State *J, const char *message); +void js_newurierror(js_State *J, const char *message); + +JS_NORETURN void js_error(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); +JS_NORETURN void js_evalerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); +JS_NORETURN void js_rangeerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); +JS_NORETURN void js_referenceerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); +JS_NORETURN void js_syntaxerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); +JS_NORETURN void js_typeerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); +JS_NORETURN void js_urierror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); +JS_NORETURN void js_throw(js_State *J); + +void js_loadstring(js_State *J, const char *filename, const char *source); +void js_loadfile(js_State *J, const char *filename); + +void js_eval(js_State *J); +void js_call(js_State *J, int n); +void js_construct(js_State *J, int n); + +const char *js_ref(js_State *J); +void js_unref(js_State *J, const char *ref); + +void js_getregistry(js_State *J, const char *name); +void js_setregistry(js_State *J, const char *name); +void js_delregistry(js_State *J, const char *name); + +void js_getglobal(js_State *J, const char *name); +void js_setglobal(js_State *J, const char *name); +void js_defglobal(js_State *J, const char *name, int atts); +void js_delglobal(js_State *J, const char *name); + +int js_hasproperty(js_State *J, int idx, const char *name); +void js_getproperty(js_State *J, int idx, const char *name); +void js_setproperty(js_State *J, int idx, const char *name); +void js_defproperty(js_State *J, int idx, const char *name, int atts); +void js_delproperty(js_State *J, int idx, const char *name); +void js_defaccessor(js_State *J, int idx, const char *name, int atts); + +int js_getlength(js_State *J, int idx); +void js_setlength(js_State *J, int idx, int len); +int js_hasindex(js_State *J, int idx, int i); +void js_getindex(js_State *J, int idx, int i); +void js_setindex(js_State *J, int idx, int i); +void js_delindex(js_State *J, int idx, int i); + +void js_currentfunction(js_State *J); +void *js_currentfunctiondata(js_State *J); +void js_pushglobal(js_State *J); +void js_pushundefined(js_State *J); +void js_pushnull(js_State *J); +void js_pushboolean(js_State *J, int v); +void js_pushnumber(js_State *J, double v); +void js_pushstring(js_State *J, const char *v); +void js_pushlstring(js_State *J, const char *v, int n); +void js_pushliteral(js_State *J, const char *v); + +void js_newobjectx(js_State *J); +void js_newobject(js_State *J); +void js_newarray(js_State *J); +void js_newboolean(js_State *J, int v); +void js_newnumber(js_State *J, double v); +void js_newstring(js_State *J, const char *v); +void js_newcfunction(js_State *J, js_CFunction fun, const char *name, int length); +void js_newcfunctionx(js_State *J, js_CFunction fun, const char *name, int length, void *data, js_Finalize finalize); +void js_newcconstructor(js_State *J, js_CFunction fun, js_CFunction con, const char *name, int length); +void js_newuserdata(js_State *J, const char *tag, void *data, js_Finalize finalize); +void js_newuserdatax(js_State *J, const char *tag, void *data, js_HasProperty has, js_Put put, js_Delete del, js_Finalize finalize); +void js_newregexp(js_State *J, const char *pattern, int flags); + +void js_pushiterator(js_State *J, int idx, int own); +const char *js_nextiterator(js_State *J, int idx); + +int js_isdefined(js_State *J, int idx); +int js_isundefined(js_State *J, int idx); +int js_isnull(js_State *J, int idx); +int js_isboolean(js_State *J, int idx); +int js_isnumber(js_State *J, int idx); +int js_isstring(js_State *J, int idx); +int js_isprimitive(js_State *J, int idx); +int js_isobject(js_State *J, int idx); +int js_isarray(js_State *J, int idx); +int js_isregexp(js_State *J, int idx); +int js_iscoercible(js_State *J, int idx); +int js_iscallable(js_State *J, int idx); +int js_isuserdata(js_State *J, int idx, const char *tag); +int js_iserror(js_State *J, int idx); +int js_isnumberobject(js_State *J, int idx); +int js_isstringobject(js_State *J, int idx); +int js_isbooleanobject(js_State *J, int idx); +int js_isdateobject(js_State *J, int idx); + +int js_toboolean(js_State *J, int idx); +double js_tonumber(js_State *J, int idx); +const char *js_tostring(js_State *J, int idx); +void *js_touserdata(js_State *J, int idx, const char *tag); + +const char *js_trystring(js_State *J, int idx, const char *error); +double js_trynumber(js_State *J, int idx, double error); +int js_tryinteger(js_State *J, int idx, int error); +int js_tryboolean(js_State *J, int idx, int error); + +int js_tointeger(js_State *J, int idx); +int js_toint32(js_State *J, int idx); +unsigned int js_touint32(js_State *J, int idx); +short js_toint16(js_State *J, int idx); +unsigned short js_touint16(js_State *J, int idx); + +int js_gettop(js_State *J); +void js_pop(js_State *J, int n); +void js_rot(js_State *J, int n); +void js_copy(js_State *J, int idx); +void js_remove(js_State *J, int idx); +void js_insert(js_State *J, int idx); +void js_replace(js_State* J, int idx); + +void js_dup(js_State *J); +void js_dup2(js_State *J); +void js_rot2(js_State *J); +void js_rot3(js_State *J); +void js_rot4(js_State *J); +void js_rot2pop1(js_State *J); +void js_rot3pop2(js_State *J); + +void js_concat(js_State *J); +int js_compare(js_State *J, int *okay); +int js_equal(js_State *J); +int js_strictequal(js_State *J); +int js_instanceof(js_State *J); +const char *js_typeof(js_State *J, int idx); +int js_type(js_State *J, int idx); + +void js_repr(js_State *J, int idx); +const char *js_torepr(js_State *J, int idx); +const char *js_tryrepr(js_State *J, int idx, const char *error); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/mujs/one.c b/mujs/one.c new file mode 100644 index 0000000..9ca6276 --- /dev/null +++ b/mujs/one.c @@ -0,0 +1,25 @@ +#include "jsarray.c" +#include "jsboolean.c" +#include "jsbuiltin.c" +#include "jscompile.c" +#include "jsdate.c" +#include "jsdtoa.c" +#include "jserror.c" +#include "jsfunction.c" +#include "jsgc.c" +#include "jsintern.c" +#include "jslex.c" +#include "jsmath.c" +#include "jsnumber.c" +#include "jsobject.c" +#include "json.c" +#include "jsparse.c" +#include "jsproperty.c" +#include "jsregexp.c" +#include "jsrepr.c" +#include "jsrun.c" +#include "jsstate.c" +#include "jsstring.c" +#include "jsvalue.c" +#include "regexp.c" +#include "utf.c" diff --git a/mujs/opnames.h b/mujs/opnames.h new file mode 100644 index 0000000..509ca1c --- /dev/null +++ b/mujs/opnames.h @@ -0,0 +1,85 @@ +"pop", +"dup", +"dup2", +"rot2", +"rot3", +"rot4", +"integer", +"number", +"string", +"closure", +"newarray", +"newobject", +"newregexp", +"undef", +"null", +"true", +"false", +"this", +"current", +"getlocal", +"setlocal", +"dellocal", +"hasvar", +"getvar", +"setvar", +"delvar", +"in", +"skiparray", +"initarray", +"initprop", +"initgetter", +"initsetter", +"getprop", +"getprop_s", +"setprop", +"setprop_s", +"delprop", +"delprop_s", +"iterator", +"nextiter", +"eval", +"call", +"new", +"typeof", +"pos", +"neg", +"bitnot", +"lognot", +"inc", +"dec", +"postinc", +"postdec", +"mul", +"div", +"mod", +"add", +"sub", +"shl", +"shr", +"ushr", +"lt", +"gt", +"le", +"ge", +"eq", +"ne", +"stricteq", +"strictne", +"jcase", +"bitand", +"bitxor", +"bitor", +"instanceof", +"throw", +"try", +"endtry", +"catch", +"endcatch", +"with", +"endwith", +"debugger", +"jump", +"jtrue", +"jfalse", +"return", diff --git a/mujs/pp.c b/mujs/pp.c new file mode 100644 index 0000000..c186399 --- /dev/null +++ b/mujs/pp.c @@ -0,0 +1,980 @@ +/* Pretty-print input source by emitting parse tree back as syntax. + * with no flags: pretty-printed source + * with -m: minified source with line breaks + * with -mm: minified source without line breaks + * with -s: s-expression syntax tree + */ + +#include +#include + +#include "jsi.h" +#include "utf.h" + +static const char *astname[] = { +#include "astnames.h" +NULL +}; + +static const char *opname[] = { +#include "opnames.h" +NULL +}; + +static int format = 0; +static int minify = 0; + +static void pc(int c) +{ + putchar(c); +} + +static void ps(const char *s) +{ + fputs(s, stdout); +} + +static void in(int d) +{ + if (minify < 1) + while (d-- > 0) + putchar('\t'); +} + +static void nl(void) +{ + if (minify < 2) + putchar('\n'); +} + +static void sp(void) +{ + if (minify < 1) + putchar(' '); +} + +static void comma(void) +{ + putchar(','); + sp(); +} + +static void pstr(const char *s) +{ + static const char *HEX = "0123456789ABCDEF"; + Rune c; + pc(minify ? '\'' : '"'); + while (*s) { + s += chartorune(&c, s); + switch (c) { + case '\'': ps("\\'"); break; + case '"': ps("\\\""); break; + case '\\': ps("\\\\"); break; + case '\b': ps("\\b"); break; + case '\f': ps("\\f"); break; + case '\n': ps("\\n"); break; + case '\r': ps("\\r"); break; + case '\t': ps("\\t"); break; + default: + if (c < ' ' || c > 127) { + ps("\\u"); + pc(HEX[(c>>12)&15]); + pc(HEX[(c>>8)&15]); + pc(HEX[(c>>4)&15]); + pc(HEX[c&15]); + } else { + pc(c); break; + } + } + } + pc(minify ? '\'' : '"'); +} + +static void pregexp(const char *prog, int flags) +{ + pc('/'); + while (*prog) { + if (*prog == '/') + pc('\\'); + pc(*prog); + ++prog; + } + pc('/'); + if (flags & JS_REGEXP_G) pc('g'); + if (flags & JS_REGEXP_I) pc('i'); + if (flags & JS_REGEXP_M) pc('m'); +} + +/* Bytecode */ + +static void jsC_dumpfunction(js_State *J, js_Function *F) +{ + js_Instruction *p = F->code; + js_Instruction *end = F->code + F->codelen; + char *s; + double n; + int i; + + printf("%s(%d)\n", F->name, F->numparams); + if (F->strict) printf("\tstrict\n"); + if (F->lightweight) printf("\tlightweight\n"); + if (F->arguments) printf("\targuments\n"); + printf("\tsource %s:%d\n", F->filename, F->line); + for (i = 0; i < F->funlen; ++i) + printf("\tfunction %d %s\n", i, F->funtab[i]->name); + for (i = 0; i < F->varlen; ++i) + printf("\tlocal %d %s\n", i + 1, F->vartab[i]); + + printf("{\n"); + while (p < end) { + int ln = *p++; + int c = *p++; + + printf("%5d(%3d): ", (int)(p - F->code) - 2, ln); + ps(opname[c]); + + switch (c) { + case OP_INTEGER: + printf(" %ld", (long)((*p++) - 32768)); + break; + case OP_NUMBER: + memcpy(&n, p, sizeof(n)); + p += sizeof(n) / sizeof(*p); + printf(" %.9g", n); + break; + case OP_STRING: + memcpy(&s, p, sizeof(s)); + p += sizeof(s) / sizeof(*p); + pc(' '); + pstr(s); + break; + case OP_NEWREGEXP: + pc(' '); + memcpy(&s, p, sizeof(s)); + p += sizeof(s) / sizeof(*p); + pregexp(s, *p++); + break; + + case OP_GETVAR: + case OP_HASVAR: + case OP_SETVAR: + case OP_DELVAR: + case OP_GETPROP_S: + case OP_SETPROP_S: + case OP_DELPROP_S: + case OP_CATCH: + memcpy(&s, p, sizeof(s)); + p += sizeof(s) / sizeof(*p); + pc(' '); + ps(s); + break; + + case OP_GETLOCAL: + case OP_SETLOCAL: + case OP_DELLOCAL: + printf(" %s", F->vartab[*p++ - 1]); + break; + + case OP_CLOSURE: + case OP_CALL: + case OP_NEW: + case OP_JUMP: + case OP_JTRUE: + case OP_JFALSE: + case OP_JCASE: + case OP_TRY: + printf(" %ld", (long)*p++); + break; + } + + putchar('\n'); + } + printf("}\n"); + + for (i = 0; i < F->funlen; ++i) { + if (F->funtab[i] != F) { + printf("function %d ", i); + jsC_dumpfunction(J, F->funtab[i]); + } + } +} + +/* Pretty-printed Javascript syntax */ + +static int prec(enum js_AstType type) +{ + switch (type) { + case AST_IDENTIFIER: + case EXP_IDENTIFIER: + case EXP_NUMBER: + case EXP_STRING: + case EXP_REGEXP: + case EXP_ELISION: + case EXP_NULL: + case EXP_TRUE: + case EXP_FALSE: + case EXP_THIS: + case EXP_ARRAY: + case EXP_OBJECT: + return 170; + + case EXP_FUN: + case EXP_INDEX: + case EXP_MEMBER: + case EXP_CALL: + case EXP_NEW: + return 160; + + case EXP_POSTINC: + case EXP_POSTDEC: + return 150; + + case EXP_DELETE: + case EXP_VOID: + case EXP_TYPEOF: + case EXP_PREINC: + case EXP_PREDEC: + case EXP_POS: + case EXP_NEG: + case EXP_BITNOT: + case EXP_LOGNOT: + return 140; + + case EXP_MOD: + case EXP_DIV: + case EXP_MUL: + return 130; + + case EXP_SUB: + case EXP_ADD: + return 120; + + case EXP_USHR: + case EXP_SHR: + case EXP_SHL: + return 110; + + case EXP_IN: + case EXP_INSTANCEOF: + case EXP_GE: + case EXP_LE: + case EXP_GT: + case EXP_LT: + return 100; + + case EXP_STRICTNE: + case EXP_STRICTEQ: + case EXP_NE: + case EXP_EQ: + return 90; + + case EXP_BITAND: return 80; + case EXP_BITXOR: return 70; + case EXP_BITOR: return 60; + case EXP_LOGAND: return 50; + case EXP_LOGOR: return 40; + + case EXP_COND: + return 30; + + case EXP_ASS: + case EXP_ASS_MUL: + case EXP_ASS_DIV: + case EXP_ASS_MOD: + case EXP_ASS_ADD: + case EXP_ASS_SUB: + case EXP_ASS_SHL: + case EXP_ASS_SHR: + case EXP_ASS_USHR: + case EXP_ASS_BITAND: + case EXP_ASS_BITXOR: + case EXP_ASS_BITOR: + return 20; + +#define COMMA 15 + + case EXP_COMMA: + return 10; + + default: + return 0; + } +} + +static void pstmlist(int d, js_Ast *list); +static void pexpi(int d, int i, js_Ast *exp); +static void pstm(int d, js_Ast *stm); +static void slist(int d, js_Ast *list); +static void sblock(int d, js_Ast *list); + +static void pargs(int d, js_Ast *list) +{ + while (list) { + assert(list->type == AST_LIST); + pexpi(d, COMMA, list->a); + list = list->b; + if (list) + comma(); + } +} + +static void parray(int d, js_Ast *list) +{ + pc('['); + while (list) { + assert(list->type == AST_LIST); + pexpi(d, COMMA, list->a); + list = list->b; + if (list) + comma(); + } + pc(']'); +} + +static void pobject(int d, js_Ast *list) +{ + pc('{'); + if (list) { + nl(); + in(d+1); + } + while (list) { + js_Ast *kv = list->a; + assert(list->type == AST_LIST); + switch (kv->type) { + default: break; + case EXP_PROP_VAL: + pexpi(d+1, COMMA, kv->a); + pc(':'); sp(); + pexpi(d+1, COMMA, kv->b); + break; + case EXP_PROP_GET: + ps("get "); + pexpi(d+1, COMMA, kv->a); + ps("()"); sp(); pc('{'); nl(); + pstmlist(d+1, kv->c); + in(d+1); pc('}'); + break; + case EXP_PROP_SET: + ps("set "); + pexpi(d+1, COMMA, kv->a); + pc('('); + pargs(d+1, kv->b); + pc(')'); sp(); pc('{'); nl(); + pstmlist(d+1, kv->c); + in(d+1); pc('}'); + break; + } + list = list->b; + if (list) { + pc(','); + nl(); + in(d+1); + } else { + nl(); + in(d); + } + } + pc('}'); +} + +static void pbin(int d, int p, js_Ast *exp, const char *op) +{ + pexpi(d, p, exp->a); + sp(); + ps(op); + sp(); + pexpi(d, p, exp->b); +} + +static void puna(int d, int p, js_Ast *exp, const char *pre, const char *suf) +{ + ps(pre); + pexpi(d, p, exp->a); + ps(suf); +} + +static void pexpi(int d, int p, js_Ast *exp) +{ + int tp, paren; + + if (!exp) return; + + tp = prec(exp->type); + paren = 0; + if (tp < p) { + pc('('); + paren = 1; + } + p = tp; + + switch (exp->type) { + case AST_IDENTIFIER: ps(exp->string); break; + case EXP_IDENTIFIER: ps(exp->string); break; + case EXP_NUMBER: printf("%.9g", exp->number); break; + case EXP_STRING: pstr(exp->string); break; + case EXP_REGEXP: pregexp(exp->string, exp->number); break; + + case EXP_ELISION: ps("elision"); break; + case EXP_NULL: ps("null"); break; + case EXP_TRUE: ps("true"); break; + case EXP_FALSE: ps("false"); break; + case EXP_THIS: ps("this"); break; + + case EXP_OBJECT: pobject(d, exp->a); break; + case EXP_ARRAY: parray(d, exp->a); break; + + case EXP_DELETE: puna(d, p, exp, "delete ", ""); break; + case EXP_VOID: puna(d, p, exp, "void ", ""); break; + case EXP_TYPEOF: puna(d, p, exp, "typeof ", ""); break; + case EXP_PREINC: puna(d, p, exp, "++", ""); break; + case EXP_PREDEC: puna(d, p, exp, "--", ""); break; + case EXP_POSTINC: puna(d, p, exp, "", "++"); break; + case EXP_POSTDEC: puna(d, p, exp, "", "--"); break; + case EXP_POS: puna(d, p, exp, "+", ""); break; + case EXP_NEG: puna(d, p, exp, "-", ""); break; + case EXP_BITNOT: puna(d, p, exp, "~", ""); break; + case EXP_LOGNOT: puna(d, p, exp, "!", ""); break; + + case EXP_LOGOR: pbin(d, p, exp, "||"); break; + case EXP_LOGAND: pbin(d, p, exp, "&&"); break; + case EXP_BITOR: pbin(d, p, exp, "|"); break; + case EXP_BITXOR: pbin(d, p, exp, "^"); break; + case EXP_BITAND: pbin(d, p, exp, "&"); break; + case EXP_EQ: pbin(d, p, exp, "=="); break; + case EXP_NE: pbin(d, p, exp, "!="); break; + case EXP_STRICTEQ: pbin(d, p, exp, "==="); break; + case EXP_STRICTNE: pbin(d, p, exp, "!=="); break; + case EXP_LT: pbin(d, p, exp, "<"); break; + case EXP_GT: pbin(d, p, exp, ">"); break; + case EXP_LE: pbin(d, p, exp, "<="); break; + case EXP_GE: pbin(d, p, exp, ">="); break; + case EXP_IN: pbin(d, p, exp, "in"); break; + case EXP_SHL: pbin(d, p, exp, "<<"); break; + case EXP_SHR: pbin(d, p, exp, ">>"); break; + case EXP_USHR: pbin(d, p, exp, ">>>"); break; + case EXP_ADD: pbin(d, p, exp, "+"); break; + case EXP_SUB: pbin(d, p, exp, "-"); break; + case EXP_MUL: pbin(d, p, exp, "*"); break; + case EXP_DIV: pbin(d, p, exp, "/"); break; + case EXP_MOD: pbin(d, p, exp, "%"); break; + case EXP_ASS: pbin(d, p, exp, "="); break; + case EXP_ASS_MUL: pbin(d, p, exp, "*="); break; + case EXP_ASS_DIV: pbin(d, p, exp, "/="); break; + case EXP_ASS_MOD: pbin(d, p, exp, "%="); break; + case EXP_ASS_ADD: pbin(d, p, exp, "+="); break; + case EXP_ASS_SUB: pbin(d, p, exp, "-="); break; + case EXP_ASS_SHL: pbin(d, p, exp, "<<="); break; + case EXP_ASS_SHR: pbin(d, p, exp, ">>="); break; + case EXP_ASS_USHR: pbin(d, p, exp, ">>>="); break; + case EXP_ASS_BITAND: pbin(d, p, exp, "&="); break; + case EXP_ASS_BITXOR: pbin(d, p, exp, "^="); break; + case EXP_ASS_BITOR: pbin(d, p, exp, "|="); break; + + case EXP_INSTANCEOF: + pexpi(d, p, exp->a); + ps(" instanceof "); + pexpi(d, p, exp->b); + break; + + case EXP_COMMA: + pexpi(d, p, exp->a); + pc(','); sp(); + pexpi(d, p, exp->b); + break; + + case EXP_COND: + pexpi(d, p, exp->a); + sp(); pc('?'); sp(); + pexpi(d, p, exp->b); + sp(); pc(':'); sp(); + pexpi(d, p, exp->c); + break; + + case EXP_INDEX: + pexpi(d, p, exp->a); + pc('['); + pexpi(d, 0, exp->b); + pc(']'); + break; + + case EXP_MEMBER: + pexpi(d, p, exp->a); + pc('.'); + pexpi(d, 0, exp->b); + break; + + case EXP_CALL: + pexpi(d, p, exp->a); + pc('('); + pargs(d, exp->b); + pc(')'); + break; + + case EXP_NEW: + ps("new "); + pexpi(d, p, exp->a); + pc('('); + pargs(d, exp->b); + pc(')'); + break; + + case EXP_FUN: + if (p == 0) pc('('); + ps("function "); + pexpi(d, 0, exp->a); + pc('('); + pargs(d, exp->b); + pc(')'); sp(); pc('{'); nl(); + pstmlist(d, exp->c); + in(d); pc('}'); + if (p == 0) pc(')'); + break; + + default: + ps(""); + break; + } + + if (paren) pc(')'); +} + +static void pexp(int d, js_Ast *exp) +{ + pexpi(d, 0, exp); +} + +static void pvar(int d, js_Ast *var) +{ + assert(var->type == EXP_VAR); + pexp(d, var->a); + if (var->b) { + sp(); pc('='); sp(); + pexp(d, var->b); + } +} + +static void pvarlist(int d, js_Ast *list) +{ + while (list) { + assert(list->type == AST_LIST); + pvar(d, list->a); + list = list->b; + if (list) + comma(); + } +} + +static void pblock(int d, js_Ast *block) +{ + assert(block->type == STM_BLOCK); + pc('{'); nl(); + pstmlist(d, block->a); + in(d); pc('}'); +} + +static void pstmh(int d, js_Ast *stm) +{ + if (stm->type == STM_BLOCK) { + sp(); + pblock(d, stm); + } else { + nl(); + pstm(d+1, stm); + } +} + +static void pcaselist(int d, js_Ast *list) +{ + while (list) { + js_Ast *stm = list->a; + if (stm->type == STM_CASE) { + in(d); ps("case "); pexp(d, stm->a); pc(':'); nl(); + pstmlist(d, stm->b); + } + if (stm->type == STM_DEFAULT) { + in(d); ps("default:"); nl(); + pstmlist(d, stm->a); + } + list = list->b; + } +} + +static void pstm(int d, js_Ast *stm) +{ + if (stm->type == STM_BLOCK) { + pblock(d, stm); + return; + } + + in(d); + + switch (stm->type) { + case AST_FUNDEC: + ps("function "); + pexp(d, stm->a); + pc('('); + pargs(d, stm->b); + pc(')'); sp(); pc('{'); nl(); + pstmlist(d, stm->c); + in(d); pc('}'); + break; + + case STM_EMPTY: + pc(';'); + break; + + case STM_VAR: + ps("var "); + pvarlist(d, stm->a); + pc(';'); + break; + + case STM_IF: + ps("if"); sp(); pc('('); pexp(d, stm->a); pc(')'); + pstmh(d, stm->b); + if (stm->c) { + nl(); in(d); ps("else"); + pstmh(d, stm->c); + } + break; + + case STM_DO: + ps("do"); + pstmh(d, stm->a); + nl(); + in(d); ps("while"); sp(); pc('('); pexp(d, stm->b); pc(')'); pc(';'); + break; + + case STM_WHILE: + ps("while"); sp(); pc('('); pexp(d, stm->a); pc(')'); + pstmh(d, stm->b); + break; + + case STM_FOR: + ps("for"); sp(); pc('('); + pexp(d, stm->a); pc(';'); sp(); + pexp(d, stm->b); pc(';'); sp(); + pexp(d, stm->c); pc(')'); + pstmh(d, stm->d); + break; + case STM_FOR_VAR: + ps("for"); sp(); ps("(var "); + pvarlist(d, stm->a); pc(';'); sp(); + pexp(d, stm->b); pc(';'); sp(); + pexp(d, stm->c); pc(')'); + pstmh(d, stm->d); + break; + case STM_FOR_IN: + ps("for"); sp(); pc('('); + pexp(d, stm->a); ps(" in "); + pexp(d, stm->b); pc(')'); + pstmh(d, stm->c); + break; + case STM_FOR_IN_VAR: + ps("for"); sp(); ps("(var "); + pvarlist(d, stm->a); ps(" in "); + pexp(d, stm->b); pc(')'); + pstmh(d, stm->c); + break; + + case STM_CONTINUE: + ps("continue"); + if (stm->a) { + pc(' '); pexp(d, stm->a); + } + pc(';'); + break; + + case STM_BREAK: + ps("break"); + if (stm->a) { + pc(' '); pexp(d, stm->a); + } + pc(';'); + break; + + case STM_RETURN: + ps("return"); + if (stm->a) { + pc(' '); pexp(d, stm->a); + } + pc(';'); + break; + + case STM_WITH: + ps("with"); sp(); pc('('); pexp(d, stm->a); pc(')'); + pstmh(d, stm->b); + break; + + case STM_SWITCH: + ps("switch"); sp(); pc('('); + pexp(d, stm->a); + pc(')'); sp(); pc('{'); nl(); + pcaselist(d, stm->b); + in(d); pc('}'); + break; + + case STM_THROW: + ps("throw "); pexp(d, stm->a); pc(';'); + break; + + case STM_TRY: + ps("try"); + if (minify && stm->a->type != STM_BLOCK) + pc(' '); + pstmh(d, stm->a); + if (stm->b && stm->c) { + nl(); in(d); ps("catch"); sp(); pc('('); pexp(d, stm->b); pc(')'); + pstmh(d, stm->c); + } + if (stm->d) { + nl(); in(d); ps("finally"); + pstmh(d, stm->d); + } + break; + + case STM_LABEL: + pexp(d, stm->a); pc(':'); sp(); pstm(d, stm->b); + break; + + case STM_DEBUGGER: + ps("debugger"); + pc(';'); + break; + + default: + pexp(d, stm); + pc(';'); + } +} + +static void pstmlist(int d, js_Ast *list) +{ + while (list) { + assert(list->type == AST_LIST); + pstm(d+1, list->a); + nl(); + list = list->b; + } +} + +static void jsP_dumpsyntax(js_State *J, js_Ast *prog) +{ + if (prog) { + if (prog->type == AST_LIST) + pstmlist(-1, prog); + else { + pstm(0, prog); + nl(); + } + } + if (minify > 1) + putchar('\n'); +} + +/* S-expression list representation */ + +static void snode(int d, js_Ast *node) +{ + void (*afun)(int,js_Ast*) = snode; + void (*bfun)(int,js_Ast*) = snode; + void (*cfun)(int,js_Ast*) = snode; + void (*dfun)(int,js_Ast*) = snode; + + if (!node) { + return; + } + + if (node->type == AST_LIST) { + slist(d, node); + return; + } + + pc('('); + ps(astname[node->type]); + switch (node->type) { + default: break; + case AST_IDENTIFIER: pc(' '); ps(node->string); break; + case EXP_IDENTIFIER: pc(' '); ps(node->string); break; + case EXP_STRING: pc(' '); pstr(node->string); break; + case EXP_REGEXP: pc(' '); pregexp(node->string, node->number); break; + case EXP_NUMBER: printf(" %.9g", node->number); break; + case STM_BLOCK: afun = sblock; break; + case AST_FUNDEC: case EXP_FUN: cfun = sblock; break; + case EXP_PROP_GET: cfun = sblock; break; + case EXP_PROP_SET: cfun = sblock; break; + case STM_SWITCH: bfun = sblock; break; + case STM_CASE: bfun = sblock; break; + case STM_DEFAULT: afun = sblock; break; + } + if (node->a) { pc(' '); afun(d, node->a); } + if (node->b) { pc(' '); bfun(d, node->b); } + if (node->c) { pc(' '); cfun(d, node->c); } + if (node->d) { pc(' '); dfun(d, node->d); } + pc(')'); +} + +static void slist(int d, js_Ast *list) +{ + pc('['); + while (list) { + assert(list->type == AST_LIST); + snode(d, list->a); + list = list->b; + if (list) + pc(' '); + } + pc(']'); +} + +static void sblock(int d, js_Ast *list) +{ + ps("[\n"); + in(d+1); + while (list) { + assert(list->type == AST_LIST); + snode(d+1, list->a); + list = list->b; + if (list) { + nl(); + in(d+1); + } + } + nl(); in(d); pc(']'); +} + +static void jsP_dumplist(js_State *J, js_Ast *prog) +{ + if (prog) { + if (prog->type == AST_LIST) + sblock(0, prog); + else + snode(0, prog); + nl(); + } +} + +static void js_ppstring(js_State *J, const char *filename, const char *source) +{ + js_Ast *P; + js_Function *F; + + if (js_try(J)) { + jsP_freeparse(J); + js_throw(J); + } + + P = jsP_parse(J, filename, source); + F = jsC_compilescript(J, P, J->default_strict); + + switch (format) { + case 0: + jsP_dumpsyntax(J, P); + break; + case 1: + jsP_dumplist(J, P); + break; + case 2: + jsC_dumpfunction(J, F); + break; + } + + jsP_freeparse(J); + js_endtry(J); +} + +static void js_ppfile(js_State *J, const char *filename) +{ + FILE * volatile f = NULL; + char * volatile s = NULL; + int n, t; + + if (js_try(J)) { + js_free(J, s); + if (f) fclose(f); + js_throw(J); + } + + f = fopen(filename, "rb"); + if (!f) { + js_error(J, "cannot open file: '%s'", filename); + } + + if (fseek(f, 0, SEEK_END) < 0) { + js_error(J, "cannot seek in file: '%s'", filename); + } + + n = ftell(f); + if (n < 0) { + js_error(J, "cannot tell in file: '%s'", filename); + } + + if (fseek(f, 0, SEEK_SET) < 0) { + js_error(J, "cannot seek in file: '%s'", filename); + } + + s = js_malloc(J, n + 1); /* add space for string terminator */ + if (!s) { + js_error(J, "cannot allocate storage for file contents: '%s'", filename); + } + + t = fread(s, 1, (size_t)n, f); + if (t != n) { + js_error(J, "cannot read data from file: '%s'", filename); + } + + s[n] = 0; /* zero-terminate string containing file data */ + + js_ppstring(J, filename, s); + + js_endtry(J); + js_free(J, s); + fclose(f); +} + +static void js_tryppfile(js_State *J, const char *file) +{ + if (js_try(J)) { + js_report(J, js_trystring(J, -1, "Error")); + js_pop(J, 1); + return; + } + js_ppfile(J, file); + js_endtry(J); +} + +int +main(int argc, char **argv) +{ + js_State *J; + int i; + + if (argc < 2) { + fprintf(stderr, "usage: mujs-pp [-m | -mm | -s | -c] input.js\n"); + fprintf(stderr, " -m\tminify output\n"); + fprintf(stderr, " -mm\tminify output more\n"); + fprintf(stderr, " -s\tprint syntax tree\n"); + fprintf(stderr, " -c\tprint bytecode\n"); + } + + J = js_newstate(NULL, NULL, 0); + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "-m")) + format = 0, minify = 1; + else if (!strcmp(argv[i], "-mm")) + format = 0, minify = 2; + else if (!strcmp(argv[i], "-s")) + format = 1, minify = 0; + else if (!strcmp(argv[i], "-c")) + format = 2, minify = 0; + else + js_tryppfile(J, argv[i]); + } + + js_gc(J, 0); + js_freestate(J); + + return 0; +} diff --git a/mujs/regexp.c b/mujs/regexp.c new file mode 100644 index 0000000..952a1d2 --- /dev/null +++ b/mujs/regexp.c @@ -0,0 +1,1277 @@ +#include +#include +#include +#include +#include + +#include "regexp.h" +#include "utf.h" + +#define emit regemit +#define next regnext +#define accept regaccept + +#define nelem(a) (int)(sizeof (a) / sizeof (a)[0]) + +#define REPINF 255 +#ifndef REG_MAXPROG +#define REG_MAXPROG (32 << 10) +#endif +#ifndef REG_MAXREC +#define REG_MAXREC 1024 +#endif +#ifndef REG_MAXSPAN +#define REG_MAXSPAN 64 +#endif +#ifndef REG_MAXCLASS +#define REG_MAXCLASS 128 +#endif + +typedef struct Reclass Reclass; +typedef struct Renode Renode; +typedef struct Reinst Reinst; +typedef struct Rethread Rethread; + +struct Reclass { + Rune *end; + Rune spans[REG_MAXSPAN]; +}; + +struct Reprog { + Reinst *start, *end; + Reclass *cclass; + int flags; + int nsub; +}; + +struct cstate { + Reprog *prog; + Renode *pstart, *pend; + + const char *source; + int ncclass; + int nsub; + Renode *sub[REG_MAXSUB]; + + int lookahead; + Rune yychar; + Reclass *yycc; + int yymin, yymax; + + const char *error; + jmp_buf kaboom; + + Reclass cclass[REG_MAXCLASS]; +}; + +static void die(struct cstate *g, const char *message) +{ + g->error = message; + longjmp(g->kaboom, 1); +} + +static int canon(Rune c) +{ + Rune u = toupperrune(c); + if (c >= 128 && u < 128) + return c; + return u; +} + +/* Scan */ + +enum { + L_CHAR = 256, + L_CCLASS, /* character class */ + L_NCCLASS, /* negative character class */ + L_NC, /* "(?:" no capture */ + L_PLA, /* "(?=" positive lookahead */ + L_NLA, /* "(?!" negative lookahead */ + L_WORD, /* "\b" word boundary */ + L_NWORD, /* "\B" non-word boundary */ + L_REF, /* "\1" back-reference */ + L_COUNT, /* {M,N} */ +}; + +static int hex(struct cstate *g, int c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 0xA; + if (c >= 'A' && c <= 'F') return c - 'A' + 0xA; + die(g, "invalid escape sequence"); + return 0; +} + +static int dec(struct cstate *g, int c) +{ + if (c >= '0' && c <= '9') return c - '0'; + die(g, "invalid quantifier"); + return 0; +} + +#define ESCAPES "BbDdSsWw^$\\.*+?()[]{}|-0123456789" + +static int isunicodeletter(int c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || isalpharune(c); +} + +static int nextrune(struct cstate *g) +{ + if (!*g->source) { + g->yychar = EOF; + return 0; + } + g->source += chartorune(&g->yychar, g->source); + if (g->yychar == '\\') { + if (!*g->source) + die(g, "unterminated escape sequence"); + g->source += chartorune(&g->yychar, g->source); + switch (g->yychar) { + case 'f': g->yychar = '\f'; return 0; + case 'n': g->yychar = '\n'; return 0; + case 'r': g->yychar = '\r'; return 0; + case 't': g->yychar = '\t'; return 0; + case 'v': g->yychar = '\v'; return 0; + case 'c': + if (!g->source[0]) + die(g, "unterminated escape sequence"); + g->yychar = (*g->source++) & 31; + return 0; + case 'x': + if (!g->source[0] || !g->source[1]) + die(g, "unterminated escape sequence"); + g->yychar = hex(g, *g->source++) << 4; + g->yychar += hex(g, *g->source++); + if (g->yychar == 0) { + g->yychar = '0'; + return 1; + } + return 0; + case 'u': + if (!g->source[0] || !g->source[1] || !g->source[2] || !g->source[3]) + die(g, "unterminated escape sequence"); + g->yychar = hex(g, *g->source++) << 12; + g->yychar += hex(g, *g->source++) << 8; + g->yychar += hex(g, *g->source++) << 4; + g->yychar += hex(g, *g->source++); + if (g->yychar == 0) { + g->yychar = '0'; + return 1; + } + return 0; + case 0: + g->yychar = '0'; + return 1; + } + if (strchr(ESCAPES, g->yychar)) + return 1; + if (isunicodeletter(g->yychar) || g->yychar == '_') /* check identity escape */ + die(g, "invalid escape character"); + return 0; + } + return 0; +} + +static int lexcount(struct cstate *g) +{ + g->yychar = *g->source++; + + g->yymin = dec(g, g->yychar); + g->yychar = *g->source++; + while (g->yychar != ',' && g->yychar != '}') { + g->yymin = g->yymin * 10 + dec(g, g->yychar); + g->yychar = *g->source++; + if (g->yymin >= REPINF) + die(g, "numeric overflow"); + } + + if (g->yychar == ',') { + g->yychar = *g->source++; + if (g->yychar == '}') { + g->yymax = REPINF; + } else { + g->yymax = dec(g, g->yychar); + g->yychar = *g->source++; + while (g->yychar != '}') { + g->yymax = g->yymax * 10 + dec(g, g->yychar); + g->yychar = *g->source++; + if (g->yymax >= REPINF) + die(g, "numeric overflow"); + } + } + } else { + g->yymax = g->yymin; + } + + return L_COUNT; +} + +static void newcclass(struct cstate *g) +{ + if (g->ncclass >= REG_MAXCLASS) + die(g, "too many character classes"); + g->yycc = g->cclass + g->ncclass++; + g->yycc->end = g->yycc->spans; +} + +static void addrange(struct cstate *g, Rune a, Rune b) +{ + Reclass *cc = g->yycc; + Rune *p; + + if (a > b) + die(g, "invalid character class range"); + + /* extend existing ranges if they overlap */ + for (p = cc->spans; p < cc->end; p += 2) { + /* completely inside old range */ + if (a >= p[0] && b <= p[1]) + return; + + /* completely swallows old range */ + if (a < p[0] && b >= p[1]) { + p[0] = a; + p[1] = b; + return; + } + + /* extend at start */ + if (b >= p[0] - 1 && b <= p[1] && a < p[0]) { + p[0] = a; + return; + } + + /* extend at end */ + if (a >= p[0] && a <= p[1] + 1 && b > p[1]) { + p[1] = b; + return; + } + } + + if (cc->end + 2 >= cc->spans + nelem(cc->spans)) + die(g, "too many character class ranges"); + *cc->end++ = a; + *cc->end++ = b; +} + +static void addranges_d(struct cstate *g) +{ + addrange(g, '0', '9'); +} + +static void addranges_D(struct cstate *g) +{ + addrange(g, 0, '0'-1); + addrange(g, '9'+1, 0xFFFF); +} + +static void addranges_s(struct cstate *g) +{ + addrange(g, 0x9, 0xD); + addrange(g, 0x20, 0x20); + addrange(g, 0xA0, 0xA0); + addrange(g, 0x2028, 0x2029); + addrange(g, 0xFEFF, 0xFEFF); +} + +static void addranges_S(struct cstate *g) +{ + addrange(g, 0, 0x9-1); + addrange(g, 0xD+1, 0x20-1); + addrange(g, 0x20+1, 0xA0-1); + addrange(g, 0xA0+1, 0x2028-1); + addrange(g, 0x2029+1, 0xFEFF-1); + addrange(g, 0xFEFF+1, 0xFFFF); +} + +static void addranges_w(struct cstate *g) +{ + addrange(g, '0', '9'); + addrange(g, 'A', 'Z'); + addrange(g, '_', '_'); + addrange(g, 'a', 'z'); +} + +static void addranges_W(struct cstate *g) +{ + addrange(g, 0, '0'-1); + addrange(g, '9'+1, 'A'-1); + addrange(g, 'Z'+1, '_'-1); + addrange(g, '_'+1, 'a'-1); + addrange(g, 'z'+1, 0xFFFF); +} + +static int lexclass(struct cstate *g) +{ + int type = L_CCLASS; + int quoted, havesave, havedash; + Rune save = 0; + + newcclass(g); + + quoted = nextrune(g); + if (!quoted && g->yychar == '^') { + type = L_NCCLASS; + quoted = nextrune(g); + } + + havesave = havedash = 0; + for (;;) { + if (g->yychar == EOF) + die(g, "unterminated character class"); + if (!quoted && g->yychar == ']') + break; + + if (!quoted && g->yychar == '-') { + if (havesave) { + if (havedash) { + addrange(g, save, '-'); + havesave = havedash = 0; + } else { + havedash = 1; + } + } else { + save = '-'; + havesave = 1; + } + } else if (quoted && strchr("DSWdsw", g->yychar)) { + if (havesave) { + addrange(g, save, save); + if (havedash) + addrange(g, '-', '-'); + } + switch (g->yychar) { + case 'd': addranges_d(g); break; + case 's': addranges_s(g); break; + case 'w': addranges_w(g); break; + case 'D': addranges_D(g); break; + case 'S': addranges_S(g); break; + case 'W': addranges_W(g); break; + } + havesave = havedash = 0; + } else { + if (quoted) { + if (g->yychar == 'b') + g->yychar = '\b'; + else if (g->yychar == '0') + g->yychar = 0; + /* else identity escape */ + } + if (havesave) { + if (havedash) { + addrange(g, save, g->yychar); + havesave = havedash = 0; + } else { + addrange(g, save, save); + save = g->yychar; + } + } else { + save = g->yychar; + havesave = 1; + } + } + + quoted = nextrune(g); + } + + if (havesave) { + addrange(g, save, save); + if (havedash) + addrange(g, '-', '-'); + } + + return type; +} + +static int lex(struct cstate *g) +{ + int quoted = nextrune(g); + if (quoted) { + switch (g->yychar) { + case 'b': return L_WORD; + case 'B': return L_NWORD; + case 'd': newcclass(g); addranges_d(g); return L_CCLASS; + case 's': newcclass(g); addranges_s(g); return L_CCLASS; + case 'w': newcclass(g); addranges_w(g); return L_CCLASS; + case 'D': newcclass(g); addranges_d(g); return L_NCCLASS; + case 'S': newcclass(g); addranges_s(g); return L_NCCLASS; + case 'W': newcclass(g); addranges_w(g); return L_NCCLASS; + case '0': g->yychar = 0; return L_CHAR; + } + if (g->yychar >= '0' && g->yychar <= '9') { + g->yychar -= '0'; + if (*g->source >= '0' && *g->source <= '9') + g->yychar = g->yychar * 10 + *g->source++ - '0'; + return L_REF; + } + return L_CHAR; + } + + switch (g->yychar) { + case EOF: + case '$': case ')': case '*': case '+': + case '.': case '?': case '^': case '|': + return g->yychar; + } + + if (g->yychar == '{') + return lexcount(g); + if (g->yychar == '[') + return lexclass(g); + if (g->yychar == '(') { + if (g->source[0] == '?') { + if (g->source[1] == ':') { + g->source += 2; + return L_NC; + } + if (g->source[1] == '=') { + g->source += 2; + return L_PLA; + } + if (g->source[1] == '!') { + g->source += 2; + return L_NLA; + } + } + return '('; + } + + return L_CHAR; +} + +/* Parse */ + +enum { + P_CAT, P_ALT, P_REP, + P_BOL, P_EOL, P_WORD, P_NWORD, + P_PAR, P_PLA, P_NLA, + P_ANY, P_CHAR, P_CCLASS, P_NCCLASS, + P_REF, +}; + +struct Renode { + unsigned char type; + unsigned char ng, m, n; + Rune c; + int cc; + Renode *x; + Renode *y; +}; + +static Renode *newnode(struct cstate *g, int type) +{ + Renode *node = g->pend++; + node->type = type; + node->cc = -1; + node->c = 0; + node->ng = 0; + node->m = 0; + node->n = 0; + node->x = node->y = NULL; + return node; +} + +static int empty(Renode *node) +{ + if (!node) return 1; + switch (node->type) { + default: return 1; + case P_CAT: return empty(node->x) && empty(node->y); + case P_ALT: return empty(node->x) || empty(node->y); + case P_REP: return empty(node->x) || node->m == 0; + case P_PAR: return empty(node->x); + case P_REF: return empty(node->x); + case P_ANY: case P_CHAR: case P_CCLASS: case P_NCCLASS: return 0; + } +} + +static Renode *newrep(struct cstate *g, Renode *atom, int ng, int min, int max) +{ + Renode *rep = newnode(g, P_REP); + if (max == REPINF && empty(atom)) + die(g, "infinite loop matching the empty string"); + rep->ng = ng; + rep->m = min; + rep->n = max; + rep->x = atom; + return rep; +} + +static void next(struct cstate *g) +{ + g->lookahead = lex(g); +} + +static int accept(struct cstate *g, int t) +{ + if (g->lookahead == t) { + next(g); + return 1; + } + return 0; +} + +static Renode *parsealt(struct cstate *g); + +static Renode *parseatom(struct cstate *g) +{ + Renode *atom; + if (g->lookahead == L_CHAR) { + atom = newnode(g, P_CHAR); + atom->c = g->yychar; + next(g); + return atom; + } + if (g->lookahead == L_CCLASS) { + atom = newnode(g, P_CCLASS); + atom->cc = g->yycc - g->cclass; + next(g); + return atom; + } + if (g->lookahead == L_NCCLASS) { + atom = newnode(g, P_NCCLASS); + atom->cc = g->yycc - g->cclass; + next(g); + return atom; + } + if (g->lookahead == L_REF) { + atom = newnode(g, P_REF); + if (g->yychar == 0 || g->yychar >= g->nsub || !g->sub[g->yychar]) + die(g, "invalid back-reference"); + atom->n = g->yychar; + atom->x = g->sub[g->yychar]; + next(g); + return atom; + } + if (accept(g, '.')) + return newnode(g, P_ANY); + if (accept(g, '(')) { + atom = newnode(g, P_PAR); + if (g->nsub == REG_MAXSUB) + die(g, "too many captures"); + atom->n = g->nsub++; + atom->x = parsealt(g); + g->sub[atom->n] = atom; + if (!accept(g, ')')) + die(g, "unmatched '('"); + return atom; + } + if (accept(g, L_NC)) { + atom = parsealt(g); + if (!accept(g, ')')) + die(g, "unmatched '('"); + return atom; + } + if (accept(g, L_PLA)) { + atom = newnode(g, P_PLA); + atom->x = parsealt(g); + if (!accept(g, ')')) + die(g, "unmatched '('"); + return atom; + } + if (accept(g, L_NLA)) { + atom = newnode(g, P_NLA); + atom->x = parsealt(g); + if (!accept(g, ')')) + die(g, "unmatched '('"); + return atom; + } + die(g, "syntax error"); + return NULL; +} + +static Renode *parserep(struct cstate *g) +{ + Renode *atom; + + if (accept(g, '^')) return newnode(g, P_BOL); + if (accept(g, '$')) return newnode(g, P_EOL); + if (accept(g, L_WORD)) return newnode(g, P_WORD); + if (accept(g, L_NWORD)) return newnode(g, P_NWORD); + + atom = parseatom(g); + if (g->lookahead == L_COUNT) { + int min = g->yymin, max = g->yymax; + next(g); + if (max < min) + die(g, "invalid quantifier"); + return newrep(g, atom, accept(g, '?'), min, max); + } + if (accept(g, '*')) return newrep(g, atom, accept(g, '?'), 0, REPINF); + if (accept(g, '+')) return newrep(g, atom, accept(g, '?'), 1, REPINF); + if (accept(g, '?')) return newrep(g, atom, accept(g, '?'), 0, 1); + return atom; +} + +static Renode *parsecat(struct cstate *g) +{ + Renode *cat, *head, **tail; + if (g->lookahead != EOF && g->lookahead != '|' && g->lookahead != ')') { + /* Build a right-leaning tree by splicing in new 'cat' at the tail. */ + head = parserep(g); + tail = &head; + while (g->lookahead != EOF && g->lookahead != '|' && g->lookahead != ')') { + cat = newnode(g, P_CAT); + cat->x = *tail; + cat->y = parserep(g); + *tail = cat; + tail = &cat->y; + } + return head; + } + return NULL; +} + +static Renode *parsealt(struct cstate *g) +{ + Renode *alt, *x; + alt = parsecat(g); + while (accept(g, '|')) { + x = alt; + alt = newnode(g, P_ALT); + alt->x = x; + alt->y = parsecat(g); + } + return alt; +} + +/* Compile */ + +enum { + I_END, I_JUMP, I_SPLIT, I_PLA, I_NLA, + I_ANYNL, I_ANY, I_CHAR, I_CCLASS, I_NCCLASS, I_REF, + I_BOL, I_EOL, I_WORD, I_NWORD, + I_LPAR, I_RPAR +}; + +struct Reinst { + unsigned char opcode; + unsigned char n; + Rune c; + Reclass *cc; + Reinst *x; + Reinst *y; +}; + +static int count(struct cstate *g, Renode *node, int depth) +{ + int min, max, n; + if (!node) return 0; + if (++depth > REG_MAXREC) die(g, "stack overflow"); + switch (node->type) { + default: return 1; + case P_CAT: return count(g, node->x, depth) + count(g, node->y, depth); + case P_ALT: return count(g, node->x, depth) + count(g, node->y, depth) + 2; + case P_REP: + min = node->m; + max = node->n; + if (min == max) n = count(g, node->x, depth) * min; + else if (max < REPINF) n = count(g, node->x, depth) * max + (max - min); + else n = count(g, node->x, depth) * (min + 1) + 2; + if (n < 0 || n > REG_MAXPROG) die(g, "program too large"); + return n; + case P_PAR: return count(g, node->x, depth) + 2; + case P_PLA: return count(g, node->x, depth) + 2; + case P_NLA: return count(g, node->x, depth) + 2; + } +} + +static Reinst *emit(Reprog *prog, int opcode) +{ + Reinst *inst = prog->end++; + inst->opcode = opcode; + inst->n = 0; + inst->c = 0; + inst->cc = NULL; + inst->x = inst->y = NULL; + return inst; +} + +static void compile(Reprog *prog, Renode *node) +{ + Reinst *inst, *split, *jump; + int i; + +loop: + if (!node) + return; + + switch (node->type) { + case P_CAT: + compile(prog, node->x); + node = node->y; + goto loop; + + case P_ALT: + split = emit(prog, I_SPLIT); + compile(prog, node->x); + jump = emit(prog, I_JUMP); + compile(prog, node->y); + split->x = split + 1; + split->y = jump + 1; + jump->x = prog->end; + break; + + case P_REP: + inst = NULL; /* silence compiler warning. assert(node->m > 0). */ + for (i = 0; i < node->m; ++i) { + inst = prog->end; + compile(prog, node->x); + } + if (node->m == node->n) + break; + if (node->n < REPINF) { + for (i = node->m; i < node->n; ++i) { + split = emit(prog, I_SPLIT); + compile(prog, node->x); + if (node->ng) { + split->y = split + 1; + split->x = prog->end; + } else { + split->x = split + 1; + split->y = prog->end; + } + } + } else if (node->m == 0) { + split = emit(prog, I_SPLIT); + compile(prog, node->x); + jump = emit(prog, I_JUMP); + if (node->ng) { + split->y = split + 1; + split->x = prog->end; + } else { + split->x = split + 1; + split->y = prog->end; + } + jump->x = split; + } else { + split = emit(prog, I_SPLIT); + if (node->ng) { + split->y = inst; + split->x = prog->end; + } else { + split->x = inst; + split->y = prog->end; + } + } + break; + + case P_BOL: emit(prog, I_BOL); break; + case P_EOL: emit(prog, I_EOL); break; + case P_WORD: emit(prog, I_WORD); break; + case P_NWORD: emit(prog, I_NWORD); break; + + case P_PAR: + inst = emit(prog, I_LPAR); + inst->n = node->n; + compile(prog, node->x); + inst = emit(prog, I_RPAR); + inst->n = node->n; + break; + case P_PLA: + split = emit(prog, I_PLA); + compile(prog, node->x); + emit(prog, I_END); + split->x = split + 1; + split->y = prog->end; + break; + case P_NLA: + split = emit(prog, I_NLA); + compile(prog, node->x); + emit(prog, I_END); + split->x = split + 1; + split->y = prog->end; + break; + + case P_ANY: + emit(prog, I_ANY); + break; + case P_CHAR: + inst = emit(prog, I_CHAR); + inst->c = (prog->flags & REG_ICASE) ? canon(node->c) : node->c; + break; + case P_CCLASS: + inst = emit(prog, I_CCLASS); + inst->cc = prog->cclass + node->cc; + break; + case P_NCCLASS: + inst = emit(prog, I_NCCLASS); + inst->cc = prog->cclass + node->cc; + break; + case P_REF: + inst = emit(prog, I_REF); + inst->n = node->n; + break; + } +} + +#ifdef TEST +static void dumpnode(struct cstate *g, Renode *node) +{ + Rune *p; + Reclass *cc; + if (!node) { printf("Empty"); return; } + switch (node->type) { + case P_CAT: printf("Cat("); dumpnode(g, node->x); printf(", "); dumpnode(g, node->y); printf(")"); break; + case P_ALT: printf("Alt("); dumpnode(g, node->x); printf(", "); dumpnode(g, node->y); printf(")"); break; + case P_REP: + printf(node->ng ? "NgRep(%d,%d," : "Rep(%d,%d,", node->m, node->n); + dumpnode(g, node->x); + printf(")"); + break; + case P_BOL: printf("Bol"); break; + case P_EOL: printf("Eol"); break; + case P_WORD: printf("Word"); break; + case P_NWORD: printf("NotWord"); break; + case P_PAR: printf("Par(%d,", node->n); dumpnode(g, node->x); printf(")"); break; + case P_PLA: printf("PLA("); dumpnode(g, node->x); printf(")"); break; + case P_NLA: printf("NLA("); dumpnode(g, node->x); printf(")"); break; + case P_ANY: printf("Any"); break; + case P_CHAR: printf("Char(%c)", node->c); break; + case P_CCLASS: + printf("Class("); + cc = g->cclass + node->cc; + for (p = cc->spans; p < cc->end; p += 2) printf("%02X-%02X,", p[0], p[1]); + printf(")"); + break; + case P_NCCLASS: + printf("NotClass("); + cc = g->cclass + node->cc; + for (p = cc->spans; p < cc->end; p += 2) printf("%02X-%02X,", p[0], p[1]); + printf(")"); + break; + case P_REF: printf("Ref(%d)", node->n); break; + } +} + +static void dumpcclass(Reclass *cc) { + Rune *p; + for (p = cc->spans; p < cc->end; p += 2) { + if (p[0] > 32 && p[0] < 127) + printf(" %c", p[0]); + else + printf(" \\x%02x", p[0]); + if (p[1] > 32 && p[1] < 127) + printf("-%c", p[1]); + else + printf("-\\x%02x", p[1]); + } + putchar('\n'); +} + +static void dumpprog(Reprog *prog) +{ + Reinst *inst; + int i; + for (i = 0, inst = prog->start; inst < prog->end; ++i, ++inst) { + printf("% 5d: ", i); + switch (inst->opcode) { + case I_END: puts("end"); break; + case I_JUMP: printf("jump %d\n", (int)(inst->x - prog->start)); break; + case I_SPLIT: printf("split %d %d\n", (int)(inst->x - prog->start), (int)(inst->y - prog->start)); break; + case I_PLA: printf("pla %d %d\n", (int)(inst->x - prog->start), (int)(inst->y - prog->start)); break; + case I_NLA: printf("nla %d %d\n", (int)(inst->x - prog->start), (int)(inst->y - prog->start)); break; + case I_ANY: puts("any"); break; + case I_ANYNL: puts("anynl"); break; + case I_CHAR: printf(inst->c >= 32 && inst->c < 127 ? "char '%c'\n" : "char U+%04X\n", inst->c); break; + case I_CCLASS: printf("cclass"); dumpcclass(inst->cc); break; + case I_NCCLASS: printf("ncclass"); dumpcclass(inst->cc); break; + case I_REF: printf("ref %d\n", inst->n); break; + case I_BOL: puts("bol"); break; + case I_EOL: puts("eol"); break; + case I_WORD: puts("word"); break; + case I_NWORD: puts("nword"); break; + case I_LPAR: printf("lpar %d\n", inst->n); break; + case I_RPAR: printf("rpar %d\n", inst->n); break; + } + } +} +#endif + +Reprog *regcompx(void *(*alloc)(void *ctx, void *p, int n), void *ctx, + const char *pattern, int cflags, const char **errorp) +{ + struct cstate g; + Renode *node; + Reinst *split, *jump; + int i, n; + + g.pstart = NULL; + g.prog = NULL; + + if (setjmp(g.kaboom)) { + if (errorp) *errorp = g.error; + alloc(ctx, g.pstart, 0); + if (g.prog) { + alloc(ctx, g.prog->cclass, 0); + alloc(ctx, g.prog->start, 0); + alloc(ctx, g.prog, 0); + } + return NULL; + } + + g.prog = alloc(ctx, NULL, sizeof (Reprog)); + if (!g.prog) + die(&g, "cannot allocate regular expression"); + g.prog->start = NULL; + g.prog->cclass = NULL; + + n = strlen(pattern) * 2; + if (n > REG_MAXPROG) + die(&g, "program too large"); + if (n > 0) { + g.pstart = g.pend = alloc(ctx, NULL, sizeof (Renode) * n); + if (!g.pstart) + die(&g, "cannot allocate regular expression parse list"); + } + + g.source = pattern; + g.ncclass = 0; + g.nsub = 1; + for (i = 0; i < REG_MAXSUB; ++i) + g.sub[i] = 0; + + g.prog->flags = cflags; + + next(&g); + node = parsealt(&g); + if (g.lookahead == ')') + die(&g, "unmatched ')'"); + if (g.lookahead != EOF) + die(&g, "syntax error"); + +#ifdef TEST + dumpnode(&g, node); + putchar('\n'); +#endif + + n = 6 + count(&g, node, 0); + if (n < 0 || n > REG_MAXPROG) + die(&g, "program too large"); + + g.prog->nsub = g.nsub; + g.prog->start = g.prog->end = alloc(ctx, NULL, n * sizeof (Reinst)); + if (!g.prog->start) + die(&g, "cannot allocate regular expression instruction list"); + + if (g.ncclass > 0) { + g.prog->cclass = alloc(ctx, NULL, g.ncclass * sizeof (Reclass)); + if (!g.prog->cclass) + die(&g, "cannot allocate regular expression character class list"); + memcpy(g.prog->cclass, g.cclass, g.ncclass * sizeof (Reclass)); + for (i = 0; i < g.ncclass; ++i) + g.prog->cclass[i].end = g.prog->cclass[i].spans + (g.cclass[i].end - g.cclass[i].spans); + } + + split = emit(g.prog, I_SPLIT); + split->x = split + 3; + split->y = split + 1; + emit(g.prog, I_ANYNL); + jump = emit(g.prog, I_JUMP); + jump->x = split; + emit(g.prog, I_LPAR); + compile(g.prog, node); + emit(g.prog, I_RPAR); + emit(g.prog, I_END); + +#ifdef TEST + dumpprog(g.prog); +#endif + + alloc(ctx, g.pstart, 0); + + if (errorp) *errorp = NULL; + return g.prog; +} + +void regfreex(void *(*alloc)(void *ctx, void *p, int n), void *ctx, Reprog *prog) +{ + if (prog) { + if (prog->cclass) + alloc(ctx, prog->cclass, 0); + alloc(ctx, prog->start, 0); + alloc(ctx, prog, 0); + } +} + +static void *default_alloc(void *ctx, void *p, int n) +{ + if (n == 0) { + free(p); + return NULL; + } + return realloc(p, (size_t)n); +} + +Reprog *regcomp(const char *pattern, int cflags, const char **errorp) +{ + return regcompx(default_alloc, NULL, pattern, cflags, errorp); +} + +void regfree(Reprog *prog) +{ + regfreex(default_alloc, NULL, prog); +} + +/* Match */ + +static int isnewline(int c) +{ + return c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029; +} + +static int iswordchar(int c) +{ + return c == '_' || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9'); +} + +static int incclass(Reclass *cc, Rune c) +{ + Rune *p; + for (p = cc->spans; p < cc->end; p += 2) + if (p[0] <= c && c <= p[1]) + return 1; + return 0; +} + +static int incclasscanon(Reclass *cc, Rune c) +{ + Rune *p, r; + for (p = cc->spans; p < cc->end; p += 2) + for (r = p[0]; r <= p[1]; ++r) + if (c == canon(r)) + return 1; + return 0; +} + +static int strncmpcanon(const char *a, const char *b, int n) +{ + Rune ra, rb; + int c; + while (n--) { + if (!*a) return -1; + if (!*b) return 1; + a += chartorune(&ra, a); + b += chartorune(&rb, b); + c = canon(ra) - canon(rb); + if (c) + return c; + } + return 0; +} + +static int match(Reinst *pc, const char *sp, const char *bol, int flags, Resub *out, int depth) +{ + Resub scratch; + int result; + int i; + Rune c; + + /* stack overflow */ + if (depth > REG_MAXREC) + return -1; + + for (;;) { + switch (pc->opcode) { + case I_END: + return 0; + case I_JUMP: + pc = pc->x; + break; + case I_SPLIT: + scratch = *out; + result = match(pc->x, sp, bol, flags, &scratch, depth+1); + if (result == -1) + return -1; + if (result == 0) { + *out = scratch; + return 0; + } + pc = pc->y; + break; + + case I_PLA: + result = match(pc->x, sp, bol, flags, out, depth+1); + if (result == -1) + return -1; + if (result == 1) + return 1; + pc = pc->y; + break; + case I_NLA: + scratch = *out; + result = match(pc->x, sp, bol, flags, &scratch, depth+1); + if (result == -1) + return -1; + if (result == 0) + return 1; + pc = pc->y; + break; + + case I_ANYNL: + if (!*sp) return 1; + sp += chartorune(&c, sp); + pc = pc + 1; + break; + case I_ANY: + if (!*sp) return 1; + sp += chartorune(&c, sp); + if (isnewline(c)) + return 1; + pc = pc + 1; + break; + case I_CHAR: + if (!*sp) return 1; + sp += chartorune(&c, sp); + if (flags & REG_ICASE) + c = canon(c); + if (c != pc->c) + return 1; + pc = pc + 1; + break; + case I_CCLASS: + if (!*sp) return 1; + sp += chartorune(&c, sp); + if (flags & REG_ICASE) { + if (!incclasscanon(pc->cc, canon(c))) + return 1; + } else { + if (!incclass(pc->cc, c)) + return 1; + } + pc = pc + 1; + break; + case I_NCCLASS: + if (!*sp) return 1; + sp += chartorune(&c, sp); + if (flags & REG_ICASE) { + if (incclasscanon(pc->cc, canon(c))) + return 1; + } else { + if (incclass(pc->cc, c)) + return 1; + } + pc = pc + 1; + break; + case I_REF: + i = out->sub[pc->n].ep - out->sub[pc->n].sp; + if (flags & REG_ICASE) { + if (strncmpcanon(sp, out->sub[pc->n].sp, i)) + return 1; + } else { + if (strncmp(sp, out->sub[pc->n].sp, i)) + return 1; + } + if (i > 0) + sp += i; + pc = pc + 1; + break; + + case I_BOL: + if (sp == bol && !(flags & REG_NOTBOL)) { + pc = pc + 1; + break; + } + if (flags & REG_NEWLINE) { + if (sp > bol && isnewline(sp[-1])) { + pc = pc + 1; + break; + } + } + return 1; + case I_EOL: + if (*sp == 0) { + pc = pc + 1; + break; + } + if (flags & REG_NEWLINE) { + if (isnewline(*sp)) { + pc = pc + 1; + break; + } + } + return 1; + case I_WORD: + i = sp > bol && iswordchar(sp[-1]); + i ^= iswordchar(sp[0]); + if (!i) + return 1; + pc = pc + 1; + break; + case I_NWORD: + i = sp > bol && iswordchar(sp[-1]); + i ^= iswordchar(sp[0]); + if (i) + return 1; + pc = pc + 1; + break; + + case I_LPAR: + out->sub[pc->n].sp = sp; + pc = pc + 1; + break; + case I_RPAR: + out->sub[pc->n].ep = sp; + pc = pc + 1; + break; + default: + return 1; + } + } +} + +int regexec(Reprog *prog, const char *sp, Resub *sub, int eflags) +{ + Resub scratch; + int i; + + if (!sub) + sub = &scratch; + + sub->nsub = prog->nsub; + for (i = 0; i < REG_MAXSUB; ++i) + sub->sub[i].sp = sub->sub[i].ep = NULL; + + return match(prog->start, sp, sp, prog->flags | eflags, sub, 0); +} + +#ifdef TEST +int main(int argc, char **argv) +{ + const char *error; + const char *s; + Reprog *p; + Resub m; + int i; + + if (argc > 1) { + p = regcomp(argv[1], 0, &error); + if (!p) { + fprintf(stderr, "regcomp: %s\n", error); + return 1; + } + + if (argc > 2) { + s = argv[2]; + printf("nsub = %d\n", p->nsub); + if (!regexec(p, s, &m, 0)) { + for (i = 0; i < m.nsub; ++i) { + int n = m.sub[i].ep - m.sub[i].sp; + if (n > 0) + printf("match %d: s=%d e=%d n=%d '%.*s'\n", i, (int)(m.sub[i].sp - s), (int)(m.sub[i].ep - s), n, n, m.sub[i].sp); + else + printf("match %d: n=0 ''\n", i); + } + } else { + printf("no match\n"); + } + } + } + + return 0; +} +#endif diff --git a/mujs/regexp.h b/mujs/regexp.h new file mode 100644 index 0000000..d627e66 --- /dev/null +++ b/mujs/regexp.h @@ -0,0 +1,46 @@ +#ifndef regexp_h +#define regexp_h + +#define regcompx js_regcompx +#define regfreex js_regfreex +#define regcomp js_regcomp +#define regexec js_regexec +#define regfree js_regfree + +typedef struct Reprog Reprog; +typedef struct Resub Resub; + +Reprog *regcompx(void *(*alloc)(void *ctx, void *p, int n), void *ctx, + const char *pattern, int cflags, const char **errorp); +void regfreex(void *(*alloc)(void *ctx, void *p, int n), void *ctx, + Reprog *prog); + +Reprog *regcomp(const char *pattern, int cflags, const char **errorp); +int regexec(Reprog *prog, const char *string, Resub *sub, int eflags); +void regfree(Reprog *prog); + +enum { + /* regcomp flags */ + REG_ICASE = 1, + REG_NEWLINE = 2, + + /* regexec flags */ + REG_NOTBOL = 4, +}; + +/* If you redefine REG_MAXSUB, you must make sure both the calling + * code and the regexp.c compilation unit use the same value! + */ +#ifndef REG_MAXSUB +#define REG_MAXSUB 16 +#endif + +struct Resub { + int nsub; + struct { + const char *sp; + const char *ep; + } sub[REG_MAXSUB]; +}; + +#endif diff --git a/mujs/tools/test262 b/mujs/tools/test262 new file mode 100755 index 0000000..204184e --- /dev/null +++ b/mujs/tools/test262 @@ -0,0 +1,139 @@ +#!/bin/sh + +usage() { + [ "${1-}" ] && { to=2; >&$to printf "Error: %s\n" "$1"; } || to=1 + >&$to echo "Usage: ${0##*/} [OPTIONS] test-file | test-dir" + >&$to echo "Run test-262 ES5 test file or directory (at the test262 dir)." + >&$to echo " -s Print source code of failed tests." + >&$to echo " -p Print every test name before running it" + >&$to echo " -f Display full paths and full stack trace when possible" + >&$to echo " -l file.js Load file.js after the harness and before the test (up to one)" + >&$to echo " -m MUJS MUJS is [path/to/]mujs binary to test" + >&$to echo " Default is $(dirname "$0")/../build/release/mujs or mujs at \$PATH" + >&$to echo " -b Don't skip known bad (crashing/hanging) tests" + >&$to echo " -B Run only known bad tests" + exit $((to-1)) +} + +KNOWN_BAD=" + --hang-with-sta.js: + S15.1.3.2_A2.5_T1.js + S15.1.3.1_A2.5_T1.js + + --Hang-(or-taking-more-than-few-seconds): + 15.4.4.18-3-14.js + 15.4.4.20-3-14.js + S15.4.4.10_A3_T2.js + S15.4.4.10_A3_T1.js + 15.4.4.19-3-29.js + 15.4.4.19-3-28.js + 15.4.4.19-3-8.js + 15.4.4.19-3-14.js + S15.4.4.8_A3_T3.js + 15.4.4.22-3-9.js + 15.4.4.22-3-7.js + 15.4.4.22-3-25.js + 15.4.4.22-3-14.js + 15.4.4.21-3-14.js + 15.4.4.15-3-28.js + 15.4.4.15-3-14.js + 15.4.4.15-3-7.js + 15.4.4.15-3-25.js + 15.4.4.15-3-9.js +" + +SKIP_KNOWN=yes # "yes": skip bad "no": don't skip "neg": run only bad +PRINT_ALL= +EXTRA_ARGS= +mujs= lopt= + +while getopts bBfhl:ps o; do + case $o in + h) usage ;; + b) SKIP_KNOWN=no ;; + B) SKIP_KNOWN=neg ;; + p) PRINT_ALL=yes ;; + s) EXTRA_ARGS="$EXTRA_ARGS -s" ;; + f) EXTRA_ARGS="$EXTRA_ARGS -f" ;; + l) [ "$OPTARG" ] && lopt=$OPTARG || usage "empty file for -l" ;; + m) mujs=$OPTARG;; + *) usage "unknown option -$o" ;; + esac +done +shift $((OPTIND-1)) +[ $# = 1 ] || usage "expecting one file/dir" + +BAD= +if [ "$SKIP_KNOWN" != no ]; then + for b in $KNOWN_BAD; do + BAD="$BAD $b " + done +fi + +find_root() { + ROOT=$1 + n=0 + while ! [ -e "$ROOT"/test/harness/sta.js ]; do + ROOT=$ROOT/.. + n=$((n+1)) + [ $n -lt 10 ] || usage "can't find test-suite root" + done +} + +if [ -d "$1" ]; then + find_root "$1" + + if [ "$ROOT" = "$1" ]; then + FILES_CMD='find "$1/test/suite" -name "*.js" | sort -V' + else + FILES_CMD='find "$1" -name "*.js" | sort -V' + fi +else + find_root "$(dirname "$1")" + FILES_CMD='printf "%s\n" "$1"' +fi + +if ! [ "$mujs" ]; then + # try to use a recently built mujs rather than a global one + mujs=$(dirname "$0")/../build/release/mujs + [ -e "$mujs" ] || mujs=mujs +fi +jsharness=$(dirname "$0")/test262-harness.js + +total=0 +skipped=0 +failed=0 + +eval "$FILES_CMD" | ( + while IFS= read -r f && [ "$f" ]; do + total=$((total+1)) + base=${f##*/} + + case $BAD in *" $base "*) bad=yes;; *) bad=no;; esac + + case $bad-$SKIP_KNOWN in + yes-yes) + skipped=$((skipped+1)) + printf "[Skipping: $base]\n\n" + ;; + no-neg) # not known bad and running only bad - don't print anything + skipped=$((skipped+1)) + ;; + *) + [ "$PRINT_ALL" ] && echo "Testing: $f" + if ! "$mujs" -- "$jsharness" $EXTRA_ARGS ${lopt:+-l "$lopt"} "$ROOT" "$f" 2>&1; then + failed=$((failed+1)) + echo + fi + esac + done + + if [ $total -gt 1 ]; then + printf "Total: $total\n" + printf "Pass: %${#total}s\n" $((total - skipped - failed)) + printf "Skip: %${#total}s\n" $skipped + printf "Fail: %${#total}s\n" $failed + fi + + [ "$failed" = 0 ] +) diff --git a/mujs/tools/test262-harness.js b/mujs/tools/test262-harness.js new file mode 100644 index 0000000..1b7eab1 --- /dev/null +++ b/mujs/tools/test262-harness.js @@ -0,0 +1,152 @@ +/* + * Runs one test file from the ES5 test suite test-262 + * Usage: mujs [-s ] [-f] [-l file1.js -l ...] suit-root test-file + * -s: print test source on failure + * -f: print full paths/stacktraces if possible + * -l: load a js file after the harness and before the test (to override things) + * + * If there are errors, print them and exits with code 1, else exit code is 0. + * + * The test suite is at: https://github.com/tc39/test262.git + * The ES5 suite is at branch "es5-tests" + * + * - The test suite throws on any error, possibly with info at ex.message . + * - Some tests make irreversible changes to global attrubutes, therefore it's + * required to run each test file in a new mujs instance. + */ + +(function(global) { + "use strict"; + + // clean the global environment + var mujs = {}; + + ["gc", "load", "compile", "print", "write", "read", "readline", "quit", "scriptArgs"] + .forEach(function(a) { + mujs[a] = global[a]; + delete global[a]; + }); + + // restore the original Error.toString behavior - it's being tested too + Error.prototype.toString = function() { + return this.name + ': ' + this.message; + } + + function die_usage(str) { + if (str) + mujs.print(str); + mujs.print("Usage: mujs [-f] [-l file1.js -l ...] suit-root test-file"); + mujs.quit(1); + } + + // our file loader + function load(str, as_filename) { + try { + var runtime_err = false; + var compiled = mujs.compile(str, as_filename); + runtime_err = true; + compiled(); + return false; + } catch (e) { + return {err: e, runtime: runtime_err}; + } + } + + var args = mujs.scriptArgs; + var full_mode = false; + var print_src = false; + var overrides = []; + while ((""+args[0])[0] == "-") { + switch (args[0]) { + case "-f": full_mode = true; + break; + case "-s": print_src = true; + break; + case "-l": args.shift(); + overrides.push(args[0]); + break; + default: die_usage("Unknown option " + args[0]); + } + args.shift(); + } + if (args.length != 2) + die_usage("Exactly 2 paths are expected"); + var root_path = args[0]; + var test_path = args[1]; + + // load suite utils + ["sta.js", "testBuiltInObject.js", "testIntl.js"] + .forEach(function(u) { + var path = root_path + "/test/harness/" + u; + var as_file = full_mode ? path : "test/harness/" + u; + var err = load(mujs.read(path), as_file); + if (err) throw (err.err); + }); + + // load user overrides (e.g. reduced getPrecision), with a global mujs + if (overrides.length) { + global.mujs = mujs + overrides.forEach(function(f) { + var err = load(mujs.read(f), f); + if (err) throw (err.err); + }); + delete global.mujs; + } + + // the actual test + var source = mujs.read(test_path); + var negative = !!source.match(/@negative/); + if (negative) + var neg_str = (source.match(/@negative (.*)/) || [])[1]; + var as_file = test_path; + if (!full_mode) { + as_file = test_path.replace(/\\/g, "/"); + var sub = as_file.indexOf("/suite/"); + if (sub >= 0) + as_file = "test" + as_file.substring(sub); + } + + var result = load(mujs.read(test_path), as_file); + if (!!result == negative) { + // The docs don't really help about matching str, but this covers all cases + if (neg_str) + var err_for_match = /NotEarlyError/.test(neg_str) ? result.err.message : result.err.name; + if (!negative || !neg_str || RegExp(neg_str).exec(err_for_match)) + mujs.quit(0); + } + + // failed + // FIXME: @description can span lines. E.g. test/suite/bestPractice/Sbp_A3_T2.js + var desc = source.match(/@description (.*)/); + var info = "[File] " + as_file + + (desc ? "\n[Desc] " + desc[1] : "") + + "\n"; + + if (result) { + var err = result.err; + var msg = !neg_str ? err : "[Mismatch @negative " + neg_str + "]" + "\n " + err; + + info += (result.runtime ? "[run] " : "[load] ") + msg; + if (err && err.stackTrace && (result.runtime || full_mode)) { + if (full_mode) { + info += err.stackTrace; + } else { + // trim the internal loader from the trace + var internal = err.stackTrace.indexOf("\n" + load("mujs_blahblah()").err.stackTrace.trim().split("\n")[1]); + if (internal >= 0) + info += err.stackTrace.substring(0, internal); + else + info += err.stackTrace; + } + } + } else { + info += "[run] [Error expected but none thrown]"; + } + + if (print_src) + info += "\n[Source]\n" + source; + + mujs.print(info); + mujs.quit(1); + +})(this) diff --git a/mujs/utf.c b/mujs/utf.c new file mode 100644 index 0000000..0d3847f --- /dev/null +++ b/mujs/utf.c @@ -0,0 +1,285 @@ +/* + * The authors of this software are Rob Pike and Ken Thompson. + * Copyright (c) 2002 by Lucent Technologies. + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE + * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + */ +#include +#include + +#include "utf.h" +#include "utfdata.h" + +#define nelem(a) (int)(sizeof (a) / sizeof (a)[0]) + +typedef unsigned char uchar; + +enum +{ + Bit1 = 7, + Bitx = 6, + Bit2 = 5, + Bit3 = 4, + Bit4 = 3, + Bit5 = 2, + + T1 = ((1<<(Bit1+1))-1) ^ 0xFF, /* 0000 0000 */ + Tx = ((1<<(Bitx+1))-1) ^ 0xFF, /* 1000 0000 */ + T2 = ((1<<(Bit2+1))-1) ^ 0xFF, /* 1100 0000 */ + T3 = ((1<<(Bit3+1))-1) ^ 0xFF, /* 1110 0000 */ + T4 = ((1<<(Bit4+1))-1) ^ 0xFF, /* 1111 0000 */ + T5 = ((1<<(Bit5+1))-1) ^ 0xFF, /* 1111 1000 */ + + Rune1 = (1<<(Bit1+0*Bitx))-1, /* 0000 0000 0000 0000 0111 1111 */ + Rune2 = (1<<(Bit2+1*Bitx))-1, /* 0000 0000 0000 0111 1111 1111 */ + Rune3 = (1<<(Bit3+2*Bitx))-1, /* 0000 0000 1111 1111 1111 1111 */ + Rune4 = (1<<(Bit4+3*Bitx))-1, /* 0001 1111 1111 1111 1111 1111 */ + + Maskx = (1< T1 + */ + c = *(uchar*)str; + if(c < Tx) { + *rune = c; + return 1; + } + + /* + * two character sequence + * 0080-07FF => T2 Tx + */ + c1 = *(uchar*)(str+1) ^ Tx; + if(c1 & Testx) + goto bad; + if(c < T3) { + if(c < T2) + goto bad; + l = ((c << Bitx) | c1) & Rune2; + if(l <= Rune1) + goto bad; + *rune = l; + return 2; + } + + /* + * three character sequence + * 0800-FFFF => T3 Tx Tx + */ + c2 = *(uchar*)(str+2) ^ Tx; + if(c2 & Testx) + goto bad; + if(c < T4) { + l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3; + if(l <= Rune2) + goto bad; + *rune = l; + return 3; + } + + /* + * four character sequence + * 10000-10FFFF => T4 Tx Tx Tx + */ + if(UTFmax >= 4) { + c3 = *(uchar*)(str+3) ^ Tx; + if(c3 & Testx) + goto bad; + if(c < T5) { + l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) & Rune4; + if(l <= Rune3) + goto bad; + if(l > Runemax) + goto bad; + *rune = l; + return 4; + } + } + + /* + * bad decoding + */ +bad: + *rune = Bad; + return 1; +} + +int +runetochar(char *str, const Rune *rune) +{ + int c = *rune; + + /* overlong null character */ + if (c == 0) { + str[0] = (char)0xc0; + str[1] = (char)0x80; + return 2; + } + + /* + * one character sequence + * 00000-0007F => 00-7F + */ + if(c <= Rune1) { + str[0] = c; + return 1; + } + + /* + * two character sequence + * 00080-007FF => T2 Tx + */ + if(c <= Rune2) { + str[0] = T2 | (c >> 1*Bitx); + str[1] = Tx | (c & Maskx); + return 2; + } + + /* + * three character sequence + * 00800-0FFFF => T3 Tx Tx + */ + if(c > Runemax) + c = Runeerror; + if(c <= Rune3) { + str[0] = T3 | (c >> 2*Bitx); + str[1] = Tx | ((c >> 1*Bitx) & Maskx); + str[2] = Tx | (c & Maskx); + return 3; + } + + /* + * four character sequence + * 010000-1FFFFF => T4 Tx Tx Tx + */ + str[0] = T4 | (c >> 3*Bitx); + str[1] = Tx | ((c >> 2*Bitx) & Maskx); + str[2] = Tx | ((c >> 1*Bitx) & Maskx); + str[3] = Tx | (c & Maskx); + return 4; +} + +int +runelen(int c) +{ + Rune rune; + char str[10]; + + rune = c; + return runetochar(str, &rune); +} + +static const Rune * +ucd_bsearch(Rune c, const Rune *t, int n, int ne) +{ + const Rune *p; + int m; + + while(n > 1) { + m = n/2; + p = t + m*ne; + if(c >= p[0]) { + t = p; + n = n-m; + } else + n = m; + } + if(n && c >= t[0]) + return t; + return 0; +} + +Rune +tolowerrune(Rune c) +{ + const Rune *p; + + p = ucd_bsearch(c, ucd_tolower2, nelem(ucd_tolower2)/3, 3); + if(p && c >= p[0] && c <= p[1]) + return c + p[2]; + p = ucd_bsearch(c, ucd_tolower1, nelem(ucd_tolower1)/2, 2); + if(p && c == p[0]) + return c + p[1]; + return c; +} + +Rune +toupperrune(Rune c) +{ + const Rune *p; + + p = ucd_bsearch(c, ucd_toupper2, nelem(ucd_toupper2)/3, 3); + if(p && c >= p[0] && c <= p[1]) + return c + p[2]; + p = ucd_bsearch(c, ucd_toupper1, nelem(ucd_toupper1)/2, 2); + if(p && c == p[0]) + return c + p[1]; + return c; +} + +int +islowerrune(Rune c) +{ + const Rune *p; + + p = ucd_bsearch(c, ucd_toupper2, nelem(ucd_toupper2)/3, 3); + if(p && c >= p[0] && c <= p[1]) + return 1; + p = ucd_bsearch(c, ucd_toupper1, nelem(ucd_toupper1)/2, 2); + if(p && c == p[0]) + return 1; + return 0; +} + +int +isupperrune(Rune c) +{ + const Rune *p; + + p = ucd_bsearch(c, ucd_tolower2, nelem(ucd_tolower2)/3, 3); + if(p && c >= p[0] && c <= p[1]) + return 1; + p = ucd_bsearch(c, ucd_tolower1, nelem(ucd_tolower1)/2, 2); + if(p && c == p[0]) + return 1; + return 0; +} + +int +isalpharune(Rune c) +{ + const Rune *p; + + p = ucd_bsearch(c, ucd_alpha2, nelem(ucd_alpha2)/2, 2); + if(p && c >= p[0] && c <= p[1]) + return 1; + p = ucd_bsearch(c, ucd_alpha1, nelem(ucd_alpha1), 1); + if(p && c == p[0]) + return 1; + return 0; +} diff --git a/mujs/utf.h b/mujs/utf.h new file mode 100644 index 0000000..2578eac --- /dev/null +++ b/mujs/utf.h @@ -0,0 +1,48 @@ +/* + * The authors of this software are Rob Pike and Ken Thompson. + * Copyright (c) 2002 by Lucent Technologies. + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE + * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + */ +#ifndef js_utf_h +#define js_utf_h + +typedef int Rune; /* 32 bits */ + +#define chartorune jsU_chartorune +#define runetochar jsU_runetochar +#define runelen jsU_runelen + +#define isalpharune jsU_isalpharune +#define islowerrune jsU_islowerrune +#define isupperrune jsU_isupperrune +#define tolowerrune jsU_tolowerrune +#define toupperrune jsU_toupperrune + +enum +{ + UTFmax = 4, /* maximum bytes per rune */ + Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */ + Runeself = 0x80, /* rune and UTF sequences are the same (<) */ + Runeerror = 0xFFFD, /* decoding error in UTF */ + Runemax = 0x10FFFF, /* maximum rune value */ +}; + +int chartorune(Rune *rune, const char *str); +int runetochar(char *str, const Rune *rune); +int runelen(int c); + +int isalpharune(Rune c); +int islowerrune(Rune c); +int isupperrune(Rune c); +Rune tolowerrune(Rune c); +Rune toupperrune(Rune c); + +#endif diff --git a/mujs/utfdata.h b/mujs/utfdata.h new file mode 100644 index 0000000..8b55682 --- /dev/null +++ b/mujs/utfdata.h @@ -0,0 +1,2073 @@ +/* This file was automatically created from UnicodeData.txt */ + +static const Rune ucd_alpha2[] = { +0x41,0x5a, +0x61,0x7a, +0xc0,0xd6, +0xd8,0xf6, +0xf8,0x2c1, +0x2c6,0x2d1, +0x2e0,0x2e4, +0x370,0x374, +0x376,0x377, +0x37a,0x37d, +0x388,0x38a, +0x38e,0x3a1, +0x3a3,0x3f5, +0x3f7,0x481, +0x48a,0x52f, +0x531,0x556, +0x560,0x588, +0x5d0,0x5ea, +0x5ef,0x5f2, +0x620,0x64a, +0x66e,0x66f, +0x671,0x6d3, +0x6e5,0x6e6, +0x6ee,0x6ef, +0x6fa,0x6fc, +0x712,0x72f, +0x74d,0x7a5, +0x7ca,0x7ea, +0x7f4,0x7f5, +0x800,0x815, +0x840,0x858, +0x860,0x86a, +0x870,0x887, +0x889,0x88e, +0x8a0,0x8c9, +0x904,0x939, +0x958,0x961, +0x971,0x980, +0x985,0x98c, +0x98f,0x990, +0x993,0x9a8, +0x9aa,0x9b0, +0x9b6,0x9b9, +0x9dc,0x9dd, +0x9df,0x9e1, +0x9f0,0x9f1, +0xa05,0xa0a, +0xa0f,0xa10, +0xa13,0xa28, +0xa2a,0xa30, +0xa32,0xa33, +0xa35,0xa36, +0xa38,0xa39, +0xa59,0xa5c, +0xa72,0xa74, +0xa85,0xa8d, +0xa8f,0xa91, +0xa93,0xaa8, +0xaaa,0xab0, +0xab2,0xab3, +0xab5,0xab9, +0xae0,0xae1, +0xb05,0xb0c, +0xb0f,0xb10, +0xb13,0xb28, +0xb2a,0xb30, +0xb32,0xb33, +0xb35,0xb39, +0xb5c,0xb5d, +0xb5f,0xb61, +0xb85,0xb8a, +0xb8e,0xb90, +0xb92,0xb95, +0xb99,0xb9a, +0xb9e,0xb9f, +0xba3,0xba4, +0xba8,0xbaa, +0xbae,0xbb9, +0xc05,0xc0c, +0xc0e,0xc10, +0xc12,0xc28, +0xc2a,0xc39, +0xc58,0xc5a, +0xc60,0xc61, +0xc85,0xc8c, +0xc8e,0xc90, +0xc92,0xca8, +0xcaa,0xcb3, +0xcb5,0xcb9, +0xcdd,0xcde, +0xce0,0xce1, +0xcf1,0xcf2, +0xd04,0xd0c, +0xd0e,0xd10, +0xd12,0xd3a, +0xd54,0xd56, +0xd5f,0xd61, +0xd7a,0xd7f, +0xd85,0xd96, +0xd9a,0xdb1, +0xdb3,0xdbb, +0xdc0,0xdc6, +0xe01,0xe30, +0xe32,0xe33, +0xe40,0xe46, +0xe81,0xe82, +0xe86,0xe8a, +0xe8c,0xea3, +0xea7,0xeb0, +0xeb2,0xeb3, +0xec0,0xec4, +0xedc,0xedf, +0xf40,0xf47, +0xf49,0xf6c, +0xf88,0xf8c, +0x1000,0x102a, +0x1050,0x1055, +0x105a,0x105d, +0x1065,0x1066, +0x106e,0x1070, +0x1075,0x1081, +0x10a0,0x10c5, +0x10d0,0x10fa, +0x10fc,0x1248, +0x124a,0x124d, +0x1250,0x1256, +0x125a,0x125d, +0x1260,0x1288, +0x128a,0x128d, +0x1290,0x12b0, +0x12b2,0x12b5, +0x12b8,0x12be, +0x12c2,0x12c5, +0x12c8,0x12d6, +0x12d8,0x1310, +0x1312,0x1315, +0x1318,0x135a, +0x1380,0x138f, +0x13a0,0x13f5, +0x13f8,0x13fd, +0x1401,0x166c, +0x166f,0x167f, +0x1681,0x169a, +0x16a0,0x16ea, +0x16f1,0x16f8, +0x1700,0x1711, +0x171f,0x1731, +0x1740,0x1751, +0x1760,0x176c, +0x176e,0x1770, +0x1780,0x17b3, +0x1820,0x1878, +0x1880,0x1884, +0x1887,0x18a8, +0x18b0,0x18f5, +0x1900,0x191e, +0x1950,0x196d, +0x1970,0x1974, +0x1980,0x19ab, +0x19b0,0x19c9, +0x1a00,0x1a16, +0x1a20,0x1a54, +0x1b05,0x1b33, +0x1b45,0x1b4c, +0x1b83,0x1ba0, +0x1bae,0x1baf, +0x1bba,0x1be5, +0x1c00,0x1c23, +0x1c4d,0x1c4f, +0x1c5a,0x1c7d, +0x1c80,0x1c8a, +0x1c90,0x1cba, +0x1cbd,0x1cbf, +0x1ce9,0x1cec, +0x1cee,0x1cf3, +0x1cf5,0x1cf6, +0x1d00,0x1dbf, +0x1e00,0x1f15, +0x1f18,0x1f1d, +0x1f20,0x1f45, +0x1f48,0x1f4d, +0x1f50,0x1f57, +0x1f5f,0x1f7d, +0x1f80,0x1fb4, +0x1fb6,0x1fbc, +0x1fc2,0x1fc4, +0x1fc6,0x1fcc, +0x1fd0,0x1fd3, +0x1fd6,0x1fdb, +0x1fe0,0x1fec, +0x1ff2,0x1ff4, +0x1ff6,0x1ffc, +0x2090,0x209c, +0x210a,0x2113, +0x2119,0x211d, +0x212a,0x212d, +0x212f,0x2139, +0x213c,0x213f, +0x2145,0x2149, +0x2183,0x2184, +0x2c00,0x2ce4, +0x2ceb,0x2cee, +0x2cf2,0x2cf3, +0x2d00,0x2d25, +0x2d30,0x2d67, +0x2d80,0x2d96, +0x2da0,0x2da6, +0x2da8,0x2dae, +0x2db0,0x2db6, +0x2db8,0x2dbe, +0x2dc0,0x2dc6, +0x2dc8,0x2dce, +0x2dd0,0x2dd6, +0x2dd8,0x2dde, +0x3005,0x3006, +0x3031,0x3035, +0x303b,0x303c, +0x3041,0x3096, +0x309d,0x309f, +0x30a1,0x30fa, +0x30fc,0x30ff, +0x3105,0x312f, +0x3131,0x318e, +0x31a0,0x31bf, +0x31f0,0x31ff, +0x9fff,0xa48c, +0xa4d0,0xa4fd, +0xa500,0xa60c, +0xa610,0xa61f, +0xa62a,0xa62b, +0xa640,0xa66e, +0xa67f,0xa69d, +0xa6a0,0xa6e5, +0xa717,0xa71f, +0xa722,0xa788, +0xa78b,0xa7cd, +0xa7d0,0xa7d1, +0xa7d5,0xa7dc, +0xa7f2,0xa801, +0xa803,0xa805, +0xa807,0xa80a, +0xa80c,0xa822, +0xa840,0xa873, +0xa882,0xa8b3, +0xa8f2,0xa8f7, +0xa8fd,0xa8fe, +0xa90a,0xa925, +0xa930,0xa946, +0xa960,0xa97c, +0xa984,0xa9b2, +0xa9e0,0xa9e4, +0xa9e6,0xa9ef, +0xa9fa,0xa9fe, +0xaa00,0xaa28, +0xaa40,0xaa42, +0xaa44,0xaa4b, +0xaa60,0xaa76, +0xaa7e,0xaaaf, +0xaab5,0xaab6, +0xaab9,0xaabd, +0xaadb,0xaadd, +0xaae0,0xaaea, +0xaaf2,0xaaf4, +0xab01,0xab06, +0xab09,0xab0e, +0xab11,0xab16, +0xab20,0xab26, +0xab28,0xab2e, +0xab30,0xab5a, +0xab5c,0xab69, +0xab70,0xabe2, +0xd7b0,0xd7c6, +0xd7cb,0xd7fb, +0xf900,0xfa6d, +0xfa70,0xfad9, +0xfb00,0xfb06, +0xfb13,0xfb17, +0xfb1f,0xfb28, +0xfb2a,0xfb36, +0xfb38,0xfb3c, +0xfb40,0xfb41, +0xfb43,0xfb44, +0xfb46,0xfbb1, +0xfbd3,0xfd3d, +0xfd50,0xfd8f, +0xfd92,0xfdc7, +0xfdf0,0xfdfb, +0xfe70,0xfe74, +0xfe76,0xfefc, +0xff21,0xff3a, +0xff41,0xff5a, +0xff66,0xffbe, +0xffc2,0xffc7, +0xffca,0xffcf, +0xffd2,0xffd7, +0xffda,0xffdc, +0x10000,0x1000b, +0x1000d,0x10026, +0x10028,0x1003a, +0x1003c,0x1003d, +0x1003f,0x1004d, +0x10050,0x1005d, +0x10080,0x100fa, +0x10280,0x1029c, +0x102a0,0x102d0, +0x10300,0x1031f, +0x1032d,0x10340, +0x10342,0x10349, +0x10350,0x10375, +0x10380,0x1039d, +0x103a0,0x103c3, +0x103c8,0x103cf, +0x10400,0x1049d, +0x104b0,0x104d3, +0x104d8,0x104fb, +0x10500,0x10527, +0x10530,0x10563, +0x10570,0x1057a, +0x1057c,0x1058a, +0x1058c,0x10592, +0x10594,0x10595, +0x10597,0x105a1, +0x105a3,0x105b1, +0x105b3,0x105b9, +0x105bb,0x105bc, +0x105c0,0x105f3, +0x10600,0x10736, +0x10740,0x10755, +0x10760,0x10767, +0x10780,0x10785, +0x10787,0x107b0, +0x107b2,0x107ba, +0x10800,0x10805, +0x1080a,0x10835, +0x10837,0x10838, +0x1083f,0x10855, +0x10860,0x10876, +0x10880,0x1089e, +0x108e0,0x108f2, +0x108f4,0x108f5, +0x10900,0x10915, +0x10920,0x10939, +0x10980,0x109b7, +0x109be,0x109bf, +0x10a10,0x10a13, +0x10a15,0x10a17, +0x10a19,0x10a35, +0x10a60,0x10a7c, +0x10a80,0x10a9c, +0x10ac0,0x10ac7, +0x10ac9,0x10ae4, +0x10b00,0x10b35, +0x10b40,0x10b55, +0x10b60,0x10b72, +0x10b80,0x10b91, +0x10c00,0x10c48, +0x10c80,0x10cb2, +0x10cc0,0x10cf2, +0x10d00,0x10d23, +0x10d4a,0x10d65, +0x10d6f,0x10d85, +0x10e80,0x10ea9, +0x10eb0,0x10eb1, +0x10ec2,0x10ec4, +0x10f00,0x10f1c, +0x10f30,0x10f45, +0x10f70,0x10f81, +0x10fb0,0x10fc4, +0x10fe0,0x10ff6, +0x11003,0x11037, +0x11071,0x11072, +0x11083,0x110af, +0x110d0,0x110e8, +0x11103,0x11126, +0x11150,0x11172, +0x11183,0x111b2, +0x111c1,0x111c4, +0x11200,0x11211, +0x11213,0x1122b, +0x1123f,0x11240, +0x11280,0x11286, +0x1128a,0x1128d, +0x1128f,0x1129d, +0x1129f,0x112a8, +0x112b0,0x112de, +0x11305,0x1130c, +0x1130f,0x11310, +0x11313,0x11328, +0x1132a,0x11330, +0x11332,0x11333, +0x11335,0x11339, +0x1135d,0x11361, +0x11380,0x11389, +0x11390,0x113b5, +0x11400,0x11434, +0x11447,0x1144a, +0x1145f,0x11461, +0x11480,0x114af, +0x114c4,0x114c5, +0x11580,0x115ae, +0x115d8,0x115db, +0x11600,0x1162f, +0x11680,0x116aa, +0x11700,0x1171a, +0x11740,0x11746, +0x11800,0x1182b, +0x118a0,0x118df, +0x118ff,0x11906, +0x1190c,0x11913, +0x11915,0x11916, +0x11918,0x1192f, +0x119a0,0x119a7, +0x119aa,0x119d0, +0x11a0b,0x11a32, +0x11a5c,0x11a89, +0x11ab0,0x11af8, +0x11bc0,0x11be0, +0x11c00,0x11c08, +0x11c0a,0x11c2e, +0x11c72,0x11c8f, +0x11d00,0x11d06, +0x11d08,0x11d09, +0x11d0b,0x11d30, +0x11d60,0x11d65, +0x11d67,0x11d68, +0x11d6a,0x11d89, +0x11ee0,0x11ef2, +0x11f04,0x11f10, +0x11f12,0x11f33, +0x12000,0x12399, +0x12480,0x12543, +0x12f90,0x12ff0, +0x13000,0x1342f, +0x13441,0x13446, +0x13460,0x143fa, +0x14400,0x14646, +0x16100,0x1611d, +0x16800,0x16a38, +0x16a40,0x16a5e, +0x16a70,0x16abe, +0x16ad0,0x16aed, +0x16b00,0x16b2f, +0x16b40,0x16b43, +0x16b63,0x16b77, +0x16b7d,0x16b8f, +0x16d40,0x16d6c, +0x16e40,0x16e7f, +0x16f00,0x16f4a, +0x16f93,0x16f9f, +0x16fe0,0x16fe1, +0x18800,0x18cd5, +0x18cff,0x18d00, +0x1aff0,0x1aff3, +0x1aff5,0x1affb, +0x1affd,0x1affe, +0x1b000,0x1b122, +0x1b150,0x1b152, +0x1b164,0x1b167, +0x1b170,0x1b2fb, +0x1bc00,0x1bc6a, +0x1bc70,0x1bc7c, +0x1bc80,0x1bc88, +0x1bc90,0x1bc99, +0x1d400,0x1d454, +0x1d456,0x1d49c, +0x1d49e,0x1d49f, +0x1d4a5,0x1d4a6, +0x1d4a9,0x1d4ac, +0x1d4ae,0x1d4b9, +0x1d4bd,0x1d4c3, +0x1d4c5,0x1d505, +0x1d507,0x1d50a, +0x1d50d,0x1d514, +0x1d516,0x1d51c, +0x1d51e,0x1d539, +0x1d53b,0x1d53e, +0x1d540,0x1d544, +0x1d54a,0x1d550, +0x1d552,0x1d6a5, +0x1d6a8,0x1d6c0, +0x1d6c2,0x1d6da, +0x1d6dc,0x1d6fa, +0x1d6fc,0x1d714, +0x1d716,0x1d734, +0x1d736,0x1d74e, +0x1d750,0x1d76e, +0x1d770,0x1d788, +0x1d78a,0x1d7a8, +0x1d7aa,0x1d7c2, +0x1d7c4,0x1d7cb, +0x1df00,0x1df1e, +0x1df25,0x1df2a, +0x1e030,0x1e06d, +0x1e100,0x1e12c, +0x1e137,0x1e13d, +0x1e290,0x1e2ad, +0x1e2c0,0x1e2eb, +0x1e4d0,0x1e4eb, +0x1e5d0,0x1e5ed, +0x1e7e0,0x1e7e6, +0x1e7e8,0x1e7eb, +0x1e7ed,0x1e7ee, +0x1e7f0,0x1e7fe, +0x1e800,0x1e8c4, +0x1e900,0x1e943, +0x1ee00,0x1ee03, +0x1ee05,0x1ee1f, +0x1ee21,0x1ee22, +0x1ee29,0x1ee32, +0x1ee34,0x1ee37, +0x1ee4d,0x1ee4f, +0x1ee51,0x1ee52, +0x1ee61,0x1ee62, +0x1ee67,0x1ee6a, +0x1ee6c,0x1ee72, +0x1ee74,0x1ee77, +0x1ee79,0x1ee7c, +0x1ee80,0x1ee89, +0x1ee8b,0x1ee9b, +0x1eea1,0x1eea3, +0x1eea5,0x1eea9, +0x1eeab,0x1eebb, +0x2f800,0x2fa1d, +}; + +static const Rune ucd_alpha1[] = { +0xaa, +0xb5, +0xba, +0x2ec, +0x2ee, +0x37f, +0x386, +0x38c, +0x559, +0x6d5, +0x6ff, +0x710, +0x7b1, +0x7fa, +0x81a, +0x824, +0x828, +0x93d, +0x950, +0x9b2, +0x9bd, +0x9ce, +0x9fc, +0xa5e, +0xabd, +0xad0, +0xaf9, +0xb3d, +0xb71, +0xb83, +0xb9c, +0xbd0, +0xc3d, +0xc5d, +0xc80, +0xcbd, +0xd3d, +0xd4e, +0xdbd, +0xe84, +0xea5, +0xebd, +0xec6, +0xf00, +0x103f, +0x1061, +0x108e, +0x10c7, +0x10cd, +0x1258, +0x12c0, +0x17d7, +0x17dc, +0x18aa, +0x1aa7, +0x1cfa, +0x1f59, +0x1f5b, +0x1f5d, +0x1fbe, +0x2071, +0x207f, +0x2102, +0x2107, +0x2115, +0x2124, +0x2126, +0x2128, +0x214e, +0x2d27, +0x2d2d, +0x2d6f, +0x2e2f, +0x3400, +0x4dbf, +0x4e00, +0xa7d3, +0xa8fb, +0xa9cf, +0xaa7a, +0xaab1, +0xaac0, +0xaac2, +0xac00, +0xd7a3, +0xfb1d, +0xfb3e, +0x10808, +0x1083c, +0x10a00, +0x10f27, +0x11075, +0x11144, +0x11147, +0x11176, +0x111da, +0x111dc, +0x11288, +0x1133d, +0x11350, +0x1138b, +0x1138e, +0x113b7, +0x113d1, +0x113d3, +0x114c7, +0x11644, +0x116b8, +0x11909, +0x1193f, +0x11941, +0x119e1, +0x119e3, +0x11a00, +0x11a3a, +0x11a50, +0x11a9d, +0x11c40, +0x11d46, +0x11d98, +0x11f02, +0x11fb0, +0x16f50, +0x16fe3, +0x17000, +0x187f7, +0x18d08, +0x1b132, +0x1b155, +0x1d4a2, +0x1d4bb, +0x1d546, +0x1e14e, +0x1e5f0, +0x1e94b, +0x1ee24, +0x1ee27, +0x1ee39, +0x1ee3b, +0x1ee42, +0x1ee47, +0x1ee49, +0x1ee4b, +0x1ee54, +0x1ee57, +0x1ee59, +0x1ee5b, +0x1ee5d, +0x1ee5f, +0x1ee64, +0x1ee7e, +0x20000, +0x2a6df, +0x2a700, +0x2b739, +0x2b740, +0x2b81d, +0x2b820, +0x2cea1, +0x2ceb0, +0x2ebe0, +0x2ebf0, +0x2ee5d, +0x30000, +0x3134a, +0x31350, +0x323af, +}; + +static const Rune ucd_tolower2[] = { +0x41,0x5a,32, +0xc0,0xd6,32, +0xd8,0xde,32, +0x189,0x18a,205, +0x1b1,0x1b2,217, +0x388,0x38a,37, +0x38e,0x38f,63, +0x391,0x3a1,32, +0x3a3,0x3ab,32, +0x3fd,0x3ff,-130, +0x400,0x40f,80, +0x410,0x42f,32, +0x531,0x556,48, +0x10a0,0x10c5,7264, +0x13a0,0x13ef,38864, +0x13f0,0x13f5,8, +0x1c90,0x1cba,-3008, +0x1cbd,0x1cbf,-3008, +0x1f08,0x1f0f,-8, +0x1f18,0x1f1d,-8, +0x1f28,0x1f2f,-8, +0x1f38,0x1f3f,-8, +0x1f48,0x1f4d,-8, +0x1f68,0x1f6f,-8, +0x1f88,0x1f8f,-8, +0x1f98,0x1f9f,-8, +0x1fa8,0x1faf,-8, +0x1fb8,0x1fb9,-8, +0x1fba,0x1fbb,-74, +0x1fc8,0x1fcb,-86, +0x1fd8,0x1fd9,-8, +0x1fda,0x1fdb,-100, +0x1fe8,0x1fe9,-8, +0x1fea,0x1feb,-112, +0x1ff8,0x1ff9,-128, +0x1ffa,0x1ffb,-126, +0x2160,0x216f,16, +0x24b6,0x24cf,26, +0x2c00,0x2c2f,48, +0x2c7e,0x2c7f,-10815, +0xff21,0xff3a,32, +0x10400,0x10427,40, +0x104b0,0x104d3,40, +0x10570,0x1057a,39, +0x1057c,0x1058a,39, +0x1058c,0x10592,39, +0x10594,0x10595,39, +0x10c80,0x10cb2,64, +0x10d50,0x10d65,32, +0x118a0,0x118bf,32, +0x16e40,0x16e5f,32, +0x1e900,0x1e921,34, +}; + +static const Rune ucd_tolower1[] = { +0x100,1, +0x102,1, +0x104,1, +0x106,1, +0x108,1, +0x10a,1, +0x10c,1, +0x10e,1, +0x110,1, +0x112,1, +0x114,1, +0x116,1, +0x118,1, +0x11a,1, +0x11c,1, +0x11e,1, +0x120,1, +0x122,1, +0x124,1, +0x126,1, +0x128,1, +0x12a,1, +0x12c,1, +0x12e,1, +0x130,-199, +0x132,1, +0x134,1, +0x136,1, +0x139,1, +0x13b,1, +0x13d,1, +0x13f,1, +0x141,1, +0x143,1, +0x145,1, +0x147,1, +0x14a,1, +0x14c,1, +0x14e,1, +0x150,1, +0x152,1, +0x154,1, +0x156,1, +0x158,1, +0x15a,1, +0x15c,1, +0x15e,1, +0x160,1, +0x162,1, +0x164,1, +0x166,1, +0x168,1, +0x16a,1, +0x16c,1, +0x16e,1, +0x170,1, +0x172,1, +0x174,1, +0x176,1, +0x178,-121, +0x179,1, +0x17b,1, +0x17d,1, +0x181,210, +0x182,1, +0x184,1, +0x186,206, +0x187,1, +0x18b,1, +0x18e,79, +0x18f,202, +0x190,203, +0x191,1, +0x193,205, +0x194,207, +0x196,211, +0x197,209, +0x198,1, +0x19c,211, +0x19d,213, +0x19f,214, +0x1a0,1, +0x1a2,1, +0x1a4,1, +0x1a6,218, +0x1a7,1, +0x1a9,218, +0x1ac,1, +0x1ae,218, +0x1af,1, +0x1b3,1, +0x1b5,1, +0x1b7,219, +0x1b8,1, +0x1bc,1, +0x1c4,2, +0x1c5,1, +0x1c7,2, +0x1c8,1, +0x1ca,2, +0x1cb,1, +0x1cd,1, +0x1cf,1, +0x1d1,1, +0x1d3,1, +0x1d5,1, +0x1d7,1, +0x1d9,1, +0x1db,1, +0x1de,1, +0x1e0,1, +0x1e2,1, +0x1e4,1, +0x1e6,1, +0x1e8,1, +0x1ea,1, +0x1ec,1, +0x1ee,1, +0x1f1,2, +0x1f2,1, +0x1f4,1, +0x1f6,-97, +0x1f7,-56, +0x1f8,1, +0x1fa,1, +0x1fc,1, +0x1fe,1, +0x200,1, +0x202,1, +0x204,1, +0x206,1, +0x208,1, +0x20a,1, +0x20c,1, +0x20e,1, +0x210,1, +0x212,1, +0x214,1, +0x216,1, +0x218,1, +0x21a,1, +0x21c,1, +0x21e,1, +0x220,-130, +0x222,1, +0x224,1, +0x226,1, +0x228,1, +0x22a,1, +0x22c,1, +0x22e,1, +0x230,1, +0x232,1, +0x23a,10795, +0x23b,1, +0x23d,-163, +0x23e,10792, +0x241,1, +0x243,-195, +0x244,69, +0x245,71, +0x246,1, +0x248,1, +0x24a,1, +0x24c,1, +0x24e,1, +0x370,1, +0x372,1, +0x376,1, +0x37f,116, +0x386,38, +0x38c,64, +0x3cf,8, +0x3d8,1, +0x3da,1, +0x3dc,1, +0x3de,1, +0x3e0,1, +0x3e2,1, +0x3e4,1, +0x3e6,1, +0x3e8,1, +0x3ea,1, +0x3ec,1, +0x3ee,1, +0x3f4,-60, +0x3f7,1, +0x3f9,-7, +0x3fa,1, +0x460,1, +0x462,1, +0x464,1, +0x466,1, +0x468,1, +0x46a,1, +0x46c,1, +0x46e,1, +0x470,1, +0x472,1, +0x474,1, +0x476,1, +0x478,1, +0x47a,1, +0x47c,1, +0x47e,1, +0x480,1, +0x48a,1, +0x48c,1, +0x48e,1, +0x490,1, +0x492,1, +0x494,1, +0x496,1, +0x498,1, +0x49a,1, +0x49c,1, +0x49e,1, +0x4a0,1, +0x4a2,1, +0x4a4,1, +0x4a6,1, +0x4a8,1, +0x4aa,1, +0x4ac,1, +0x4ae,1, +0x4b0,1, +0x4b2,1, +0x4b4,1, +0x4b6,1, +0x4b8,1, +0x4ba,1, +0x4bc,1, +0x4be,1, +0x4c0,15, +0x4c1,1, +0x4c3,1, +0x4c5,1, +0x4c7,1, +0x4c9,1, +0x4cb,1, +0x4cd,1, +0x4d0,1, +0x4d2,1, +0x4d4,1, +0x4d6,1, +0x4d8,1, +0x4da,1, +0x4dc,1, +0x4de,1, +0x4e0,1, +0x4e2,1, +0x4e4,1, +0x4e6,1, +0x4e8,1, +0x4ea,1, +0x4ec,1, +0x4ee,1, +0x4f0,1, +0x4f2,1, +0x4f4,1, +0x4f6,1, +0x4f8,1, +0x4fa,1, +0x4fc,1, +0x4fe,1, +0x500,1, +0x502,1, +0x504,1, +0x506,1, +0x508,1, +0x50a,1, +0x50c,1, +0x50e,1, +0x510,1, +0x512,1, +0x514,1, +0x516,1, +0x518,1, +0x51a,1, +0x51c,1, +0x51e,1, +0x520,1, +0x522,1, +0x524,1, +0x526,1, +0x528,1, +0x52a,1, +0x52c,1, +0x52e,1, +0x10c7,7264, +0x10cd,7264, +0x1c89,1, +0x1e00,1, +0x1e02,1, +0x1e04,1, +0x1e06,1, +0x1e08,1, +0x1e0a,1, +0x1e0c,1, +0x1e0e,1, +0x1e10,1, +0x1e12,1, +0x1e14,1, +0x1e16,1, +0x1e18,1, +0x1e1a,1, +0x1e1c,1, +0x1e1e,1, +0x1e20,1, +0x1e22,1, +0x1e24,1, +0x1e26,1, +0x1e28,1, +0x1e2a,1, +0x1e2c,1, +0x1e2e,1, +0x1e30,1, +0x1e32,1, +0x1e34,1, +0x1e36,1, +0x1e38,1, +0x1e3a,1, +0x1e3c,1, +0x1e3e,1, +0x1e40,1, +0x1e42,1, +0x1e44,1, +0x1e46,1, +0x1e48,1, +0x1e4a,1, +0x1e4c,1, +0x1e4e,1, +0x1e50,1, +0x1e52,1, +0x1e54,1, +0x1e56,1, +0x1e58,1, +0x1e5a,1, +0x1e5c,1, +0x1e5e,1, +0x1e60,1, +0x1e62,1, +0x1e64,1, +0x1e66,1, +0x1e68,1, +0x1e6a,1, +0x1e6c,1, +0x1e6e,1, +0x1e70,1, +0x1e72,1, +0x1e74,1, +0x1e76,1, +0x1e78,1, +0x1e7a,1, +0x1e7c,1, +0x1e7e,1, +0x1e80,1, +0x1e82,1, +0x1e84,1, +0x1e86,1, +0x1e88,1, +0x1e8a,1, +0x1e8c,1, +0x1e8e,1, +0x1e90,1, +0x1e92,1, +0x1e94,1, +0x1e9e,-7615, +0x1ea0,1, +0x1ea2,1, +0x1ea4,1, +0x1ea6,1, +0x1ea8,1, +0x1eaa,1, +0x1eac,1, +0x1eae,1, +0x1eb0,1, +0x1eb2,1, +0x1eb4,1, +0x1eb6,1, +0x1eb8,1, +0x1eba,1, +0x1ebc,1, +0x1ebe,1, +0x1ec0,1, +0x1ec2,1, +0x1ec4,1, +0x1ec6,1, +0x1ec8,1, +0x1eca,1, +0x1ecc,1, +0x1ece,1, +0x1ed0,1, +0x1ed2,1, +0x1ed4,1, +0x1ed6,1, +0x1ed8,1, +0x1eda,1, +0x1edc,1, +0x1ede,1, +0x1ee0,1, +0x1ee2,1, +0x1ee4,1, +0x1ee6,1, +0x1ee8,1, +0x1eea,1, +0x1eec,1, +0x1eee,1, +0x1ef0,1, +0x1ef2,1, +0x1ef4,1, +0x1ef6,1, +0x1ef8,1, +0x1efa,1, +0x1efc,1, +0x1efe,1, +0x1f59,-8, +0x1f5b,-8, +0x1f5d,-8, +0x1f5f,-8, +0x1fbc,-9, +0x1fcc,-9, +0x1fec,-7, +0x1ffc,-9, +0x2126,-7517, +0x212a,-8383, +0x212b,-8262, +0x2132,28, +0x2183,1, +0x2c60,1, +0x2c62,-10743, +0x2c63,-3814, +0x2c64,-10727, +0x2c67,1, +0x2c69,1, +0x2c6b,1, +0x2c6d,-10780, +0x2c6e,-10749, +0x2c6f,-10783, +0x2c70,-10782, +0x2c72,1, +0x2c75,1, +0x2c80,1, +0x2c82,1, +0x2c84,1, +0x2c86,1, +0x2c88,1, +0x2c8a,1, +0x2c8c,1, +0x2c8e,1, +0x2c90,1, +0x2c92,1, +0x2c94,1, +0x2c96,1, +0x2c98,1, +0x2c9a,1, +0x2c9c,1, +0x2c9e,1, +0x2ca0,1, +0x2ca2,1, +0x2ca4,1, +0x2ca6,1, +0x2ca8,1, +0x2caa,1, +0x2cac,1, +0x2cae,1, +0x2cb0,1, +0x2cb2,1, +0x2cb4,1, +0x2cb6,1, +0x2cb8,1, +0x2cba,1, +0x2cbc,1, +0x2cbe,1, +0x2cc0,1, +0x2cc2,1, +0x2cc4,1, +0x2cc6,1, +0x2cc8,1, +0x2cca,1, +0x2ccc,1, +0x2cce,1, +0x2cd0,1, +0x2cd2,1, +0x2cd4,1, +0x2cd6,1, +0x2cd8,1, +0x2cda,1, +0x2cdc,1, +0x2cde,1, +0x2ce0,1, +0x2ce2,1, +0x2ceb,1, +0x2ced,1, +0x2cf2,1, +0xa640,1, +0xa642,1, +0xa644,1, +0xa646,1, +0xa648,1, +0xa64a,1, +0xa64c,1, +0xa64e,1, +0xa650,1, +0xa652,1, +0xa654,1, +0xa656,1, +0xa658,1, +0xa65a,1, +0xa65c,1, +0xa65e,1, +0xa660,1, +0xa662,1, +0xa664,1, +0xa666,1, +0xa668,1, +0xa66a,1, +0xa66c,1, +0xa680,1, +0xa682,1, +0xa684,1, +0xa686,1, +0xa688,1, +0xa68a,1, +0xa68c,1, +0xa68e,1, +0xa690,1, +0xa692,1, +0xa694,1, +0xa696,1, +0xa698,1, +0xa69a,1, +0xa722,1, +0xa724,1, +0xa726,1, +0xa728,1, +0xa72a,1, +0xa72c,1, +0xa72e,1, +0xa732,1, +0xa734,1, +0xa736,1, +0xa738,1, +0xa73a,1, +0xa73c,1, +0xa73e,1, +0xa740,1, +0xa742,1, +0xa744,1, +0xa746,1, +0xa748,1, +0xa74a,1, +0xa74c,1, +0xa74e,1, +0xa750,1, +0xa752,1, +0xa754,1, +0xa756,1, +0xa758,1, +0xa75a,1, +0xa75c,1, +0xa75e,1, +0xa760,1, +0xa762,1, +0xa764,1, +0xa766,1, +0xa768,1, +0xa76a,1, +0xa76c,1, +0xa76e,1, +0xa779,1, +0xa77b,1, +0xa77d,-35332, +0xa77e,1, +0xa780,1, +0xa782,1, +0xa784,1, +0xa786,1, +0xa78b,1, +0xa78d,-42280, +0xa790,1, +0xa792,1, +0xa796,1, +0xa798,1, +0xa79a,1, +0xa79c,1, +0xa79e,1, +0xa7a0,1, +0xa7a2,1, +0xa7a4,1, +0xa7a6,1, +0xa7a8,1, +0xa7aa,-42308, +0xa7ab,-42319, +0xa7ac,-42315, +0xa7ad,-42305, +0xa7ae,-42308, +0xa7b0,-42258, +0xa7b1,-42282, +0xa7b2,-42261, +0xa7b3,928, +0xa7b4,1, +0xa7b6,1, +0xa7b8,1, +0xa7ba,1, +0xa7bc,1, +0xa7be,1, +0xa7c0,1, +0xa7c2,1, +0xa7c4,-48, +0xa7c5,-42307, +0xa7c6,-35384, +0xa7c7,1, +0xa7c9,1, +0xa7cb,-42343, +0xa7cc,1, +0xa7d0,1, +0xa7d6,1, +0xa7d8,1, +0xa7da,1, +0xa7dc,-42561, +0xa7f5,1, +}; + +static const Rune ucd_toupper2[] = { +0x61,0x7a,-32, +0xe0,0xf6,-32, +0xf8,0xfe,-32, +0x23f,0x240,10815, +0x256,0x257,-205, +0x28a,0x28b,-217, +0x37b,0x37d,130, +0x3ad,0x3af,-37, +0x3b1,0x3c1,-32, +0x3c3,0x3cb,-32, +0x3cd,0x3ce,-63, +0x430,0x44f,-32, +0x450,0x45f,-80, +0x561,0x586,-48, +0x10d0,0x10fa,3008, +0x10fd,0x10ff,3008, +0x13f8,0x13fd,-8, +0x1c83,0x1c84,-6242, +0x1f00,0x1f07,8, +0x1f10,0x1f15,8, +0x1f20,0x1f27,8, +0x1f30,0x1f37,8, +0x1f40,0x1f45,8, +0x1f60,0x1f67,8, +0x1f70,0x1f71,74, +0x1f72,0x1f75,86, +0x1f76,0x1f77,100, +0x1f78,0x1f79,128, +0x1f7a,0x1f7b,112, +0x1f7c,0x1f7d,126, +0x1f80,0x1f87,8, +0x1f90,0x1f97,8, +0x1fa0,0x1fa7,8, +0x1fb0,0x1fb1,8, +0x1fd0,0x1fd1,8, +0x1fe0,0x1fe1,8, +0x2170,0x217f,-16, +0x24d0,0x24e9,-26, +0x2c30,0x2c5f,-48, +0x2d00,0x2d25,-7264, +0xab70,0xabbf,-38864, +0xff41,0xff5a,-32, +0x10428,0x1044f,-40, +0x104d8,0x104fb,-40, +0x10597,0x105a1,-39, +0x105a3,0x105b1,-39, +0x105b3,0x105b9,-39, +0x105bb,0x105bc,-39, +0x10cc0,0x10cf2,-64, +0x10d70,0x10d85,-32, +0x118c0,0x118df,-32, +0x16e60,0x16e7f,-32, +0x1e922,0x1e943,-34, +}; + +static const Rune ucd_toupper1[] = { +0xb5,743, +0xff,121, +0x101,-1, +0x103,-1, +0x105,-1, +0x107,-1, +0x109,-1, +0x10b,-1, +0x10d,-1, +0x10f,-1, +0x111,-1, +0x113,-1, +0x115,-1, +0x117,-1, +0x119,-1, +0x11b,-1, +0x11d,-1, +0x11f,-1, +0x121,-1, +0x123,-1, +0x125,-1, +0x127,-1, +0x129,-1, +0x12b,-1, +0x12d,-1, +0x12f,-1, +0x131,-232, +0x133,-1, +0x135,-1, +0x137,-1, +0x13a,-1, +0x13c,-1, +0x13e,-1, +0x140,-1, +0x142,-1, +0x144,-1, +0x146,-1, +0x148,-1, +0x14b,-1, +0x14d,-1, +0x14f,-1, +0x151,-1, +0x153,-1, +0x155,-1, +0x157,-1, +0x159,-1, +0x15b,-1, +0x15d,-1, +0x15f,-1, +0x161,-1, +0x163,-1, +0x165,-1, +0x167,-1, +0x169,-1, +0x16b,-1, +0x16d,-1, +0x16f,-1, +0x171,-1, +0x173,-1, +0x175,-1, +0x177,-1, +0x17a,-1, +0x17c,-1, +0x17e,-1, +0x17f,-300, +0x180,195, +0x183,-1, +0x185,-1, +0x188,-1, +0x18c,-1, +0x192,-1, +0x195,97, +0x199,-1, +0x19a,163, +0x19b,42561, +0x19e,130, +0x1a1,-1, +0x1a3,-1, +0x1a5,-1, +0x1a8,-1, +0x1ad,-1, +0x1b0,-1, +0x1b4,-1, +0x1b6,-1, +0x1b9,-1, +0x1bd,-1, +0x1bf,56, +0x1c5,-1, +0x1c6,-2, +0x1c8,-1, +0x1c9,-2, +0x1cb,-1, +0x1cc,-2, +0x1ce,-1, +0x1d0,-1, +0x1d2,-1, +0x1d4,-1, +0x1d6,-1, +0x1d8,-1, +0x1da,-1, +0x1dc,-1, +0x1dd,-79, +0x1df,-1, +0x1e1,-1, +0x1e3,-1, +0x1e5,-1, +0x1e7,-1, +0x1e9,-1, +0x1eb,-1, +0x1ed,-1, +0x1ef,-1, +0x1f2,-1, +0x1f3,-2, +0x1f5,-1, +0x1f9,-1, +0x1fb,-1, +0x1fd,-1, +0x1ff,-1, +0x201,-1, +0x203,-1, +0x205,-1, +0x207,-1, +0x209,-1, +0x20b,-1, +0x20d,-1, +0x20f,-1, +0x211,-1, +0x213,-1, +0x215,-1, +0x217,-1, +0x219,-1, +0x21b,-1, +0x21d,-1, +0x21f,-1, +0x223,-1, +0x225,-1, +0x227,-1, +0x229,-1, +0x22b,-1, +0x22d,-1, +0x22f,-1, +0x231,-1, +0x233,-1, +0x23c,-1, +0x242,-1, +0x247,-1, +0x249,-1, +0x24b,-1, +0x24d,-1, +0x24f,-1, +0x250,10783, +0x251,10780, +0x252,10782, +0x253,-210, +0x254,-206, +0x259,-202, +0x25b,-203, +0x25c,42319, +0x260,-205, +0x261,42315, +0x263,-207, +0x264,42343, +0x265,42280, +0x266,42308, +0x268,-209, +0x269,-211, +0x26a,42308, +0x26b,10743, +0x26c,42305, +0x26f,-211, +0x271,10749, +0x272,-213, +0x275,-214, +0x27d,10727, +0x280,-218, +0x282,42307, +0x283,-218, +0x287,42282, +0x288,-218, +0x289,-69, +0x28c,-71, +0x292,-219, +0x29d,42261, +0x29e,42258, +0x345,84, +0x371,-1, +0x373,-1, +0x377,-1, +0x3ac,-38, +0x3c2,-31, +0x3cc,-64, +0x3d0,-62, +0x3d1,-57, +0x3d5,-47, +0x3d6,-54, +0x3d7,-8, +0x3d9,-1, +0x3db,-1, +0x3dd,-1, +0x3df,-1, +0x3e1,-1, +0x3e3,-1, +0x3e5,-1, +0x3e7,-1, +0x3e9,-1, +0x3eb,-1, +0x3ed,-1, +0x3ef,-1, +0x3f0,-86, +0x3f1,-80, +0x3f2,7, +0x3f3,-116, +0x3f5,-96, +0x3f8,-1, +0x3fb,-1, +0x461,-1, +0x463,-1, +0x465,-1, +0x467,-1, +0x469,-1, +0x46b,-1, +0x46d,-1, +0x46f,-1, +0x471,-1, +0x473,-1, +0x475,-1, +0x477,-1, +0x479,-1, +0x47b,-1, +0x47d,-1, +0x47f,-1, +0x481,-1, +0x48b,-1, +0x48d,-1, +0x48f,-1, +0x491,-1, +0x493,-1, +0x495,-1, +0x497,-1, +0x499,-1, +0x49b,-1, +0x49d,-1, +0x49f,-1, +0x4a1,-1, +0x4a3,-1, +0x4a5,-1, +0x4a7,-1, +0x4a9,-1, +0x4ab,-1, +0x4ad,-1, +0x4af,-1, +0x4b1,-1, +0x4b3,-1, +0x4b5,-1, +0x4b7,-1, +0x4b9,-1, +0x4bb,-1, +0x4bd,-1, +0x4bf,-1, +0x4c2,-1, +0x4c4,-1, +0x4c6,-1, +0x4c8,-1, +0x4ca,-1, +0x4cc,-1, +0x4ce,-1, +0x4cf,-15, +0x4d1,-1, +0x4d3,-1, +0x4d5,-1, +0x4d7,-1, +0x4d9,-1, +0x4db,-1, +0x4dd,-1, +0x4df,-1, +0x4e1,-1, +0x4e3,-1, +0x4e5,-1, +0x4e7,-1, +0x4e9,-1, +0x4eb,-1, +0x4ed,-1, +0x4ef,-1, +0x4f1,-1, +0x4f3,-1, +0x4f5,-1, +0x4f7,-1, +0x4f9,-1, +0x4fb,-1, +0x4fd,-1, +0x4ff,-1, +0x501,-1, +0x503,-1, +0x505,-1, +0x507,-1, +0x509,-1, +0x50b,-1, +0x50d,-1, +0x50f,-1, +0x511,-1, +0x513,-1, +0x515,-1, +0x517,-1, +0x519,-1, +0x51b,-1, +0x51d,-1, +0x51f,-1, +0x521,-1, +0x523,-1, +0x525,-1, +0x527,-1, +0x529,-1, +0x52b,-1, +0x52d,-1, +0x52f,-1, +0x1c80,-6254, +0x1c81,-6253, +0x1c82,-6244, +0x1c85,-6243, +0x1c86,-6236, +0x1c87,-6181, +0x1c88,35266, +0x1c8a,-1, +0x1d79,35332, +0x1d7d,3814, +0x1d8e,35384, +0x1e01,-1, +0x1e03,-1, +0x1e05,-1, +0x1e07,-1, +0x1e09,-1, +0x1e0b,-1, +0x1e0d,-1, +0x1e0f,-1, +0x1e11,-1, +0x1e13,-1, +0x1e15,-1, +0x1e17,-1, +0x1e19,-1, +0x1e1b,-1, +0x1e1d,-1, +0x1e1f,-1, +0x1e21,-1, +0x1e23,-1, +0x1e25,-1, +0x1e27,-1, +0x1e29,-1, +0x1e2b,-1, +0x1e2d,-1, +0x1e2f,-1, +0x1e31,-1, +0x1e33,-1, +0x1e35,-1, +0x1e37,-1, +0x1e39,-1, +0x1e3b,-1, +0x1e3d,-1, +0x1e3f,-1, +0x1e41,-1, +0x1e43,-1, +0x1e45,-1, +0x1e47,-1, +0x1e49,-1, +0x1e4b,-1, +0x1e4d,-1, +0x1e4f,-1, +0x1e51,-1, +0x1e53,-1, +0x1e55,-1, +0x1e57,-1, +0x1e59,-1, +0x1e5b,-1, +0x1e5d,-1, +0x1e5f,-1, +0x1e61,-1, +0x1e63,-1, +0x1e65,-1, +0x1e67,-1, +0x1e69,-1, +0x1e6b,-1, +0x1e6d,-1, +0x1e6f,-1, +0x1e71,-1, +0x1e73,-1, +0x1e75,-1, +0x1e77,-1, +0x1e79,-1, +0x1e7b,-1, +0x1e7d,-1, +0x1e7f,-1, +0x1e81,-1, +0x1e83,-1, +0x1e85,-1, +0x1e87,-1, +0x1e89,-1, +0x1e8b,-1, +0x1e8d,-1, +0x1e8f,-1, +0x1e91,-1, +0x1e93,-1, +0x1e95,-1, +0x1e9b,-59, +0x1ea1,-1, +0x1ea3,-1, +0x1ea5,-1, +0x1ea7,-1, +0x1ea9,-1, +0x1eab,-1, +0x1ead,-1, +0x1eaf,-1, +0x1eb1,-1, +0x1eb3,-1, +0x1eb5,-1, +0x1eb7,-1, +0x1eb9,-1, +0x1ebb,-1, +0x1ebd,-1, +0x1ebf,-1, +0x1ec1,-1, +0x1ec3,-1, +0x1ec5,-1, +0x1ec7,-1, +0x1ec9,-1, +0x1ecb,-1, +0x1ecd,-1, +0x1ecf,-1, +0x1ed1,-1, +0x1ed3,-1, +0x1ed5,-1, +0x1ed7,-1, +0x1ed9,-1, +0x1edb,-1, +0x1edd,-1, +0x1edf,-1, +0x1ee1,-1, +0x1ee3,-1, +0x1ee5,-1, +0x1ee7,-1, +0x1ee9,-1, +0x1eeb,-1, +0x1eed,-1, +0x1eef,-1, +0x1ef1,-1, +0x1ef3,-1, +0x1ef5,-1, +0x1ef7,-1, +0x1ef9,-1, +0x1efb,-1, +0x1efd,-1, +0x1eff,-1, +0x1f51,8, +0x1f53,8, +0x1f55,8, +0x1f57,8, +0x1fb3,9, +0x1fbe,-7205, +0x1fc3,9, +0x1fe5,7, +0x1ff3,9, +0x214e,-28, +0x2184,-1, +0x2c61,-1, +0x2c65,-10795, +0x2c66,-10792, +0x2c68,-1, +0x2c6a,-1, +0x2c6c,-1, +0x2c73,-1, +0x2c76,-1, +0x2c81,-1, +0x2c83,-1, +0x2c85,-1, +0x2c87,-1, +0x2c89,-1, +0x2c8b,-1, +0x2c8d,-1, +0x2c8f,-1, +0x2c91,-1, +0x2c93,-1, +0x2c95,-1, +0x2c97,-1, +0x2c99,-1, +0x2c9b,-1, +0x2c9d,-1, +0x2c9f,-1, +0x2ca1,-1, +0x2ca3,-1, +0x2ca5,-1, +0x2ca7,-1, +0x2ca9,-1, +0x2cab,-1, +0x2cad,-1, +0x2caf,-1, +0x2cb1,-1, +0x2cb3,-1, +0x2cb5,-1, +0x2cb7,-1, +0x2cb9,-1, +0x2cbb,-1, +0x2cbd,-1, +0x2cbf,-1, +0x2cc1,-1, +0x2cc3,-1, +0x2cc5,-1, +0x2cc7,-1, +0x2cc9,-1, +0x2ccb,-1, +0x2ccd,-1, +0x2ccf,-1, +0x2cd1,-1, +0x2cd3,-1, +0x2cd5,-1, +0x2cd7,-1, +0x2cd9,-1, +0x2cdb,-1, +0x2cdd,-1, +0x2cdf,-1, +0x2ce1,-1, +0x2ce3,-1, +0x2cec,-1, +0x2cee,-1, +0x2cf3,-1, +0x2d27,-7264, +0x2d2d,-7264, +0xa641,-1, +0xa643,-1, +0xa645,-1, +0xa647,-1, +0xa649,-1, +0xa64b,-1, +0xa64d,-1, +0xa64f,-1, +0xa651,-1, +0xa653,-1, +0xa655,-1, +0xa657,-1, +0xa659,-1, +0xa65b,-1, +0xa65d,-1, +0xa65f,-1, +0xa661,-1, +0xa663,-1, +0xa665,-1, +0xa667,-1, +0xa669,-1, +0xa66b,-1, +0xa66d,-1, +0xa681,-1, +0xa683,-1, +0xa685,-1, +0xa687,-1, +0xa689,-1, +0xa68b,-1, +0xa68d,-1, +0xa68f,-1, +0xa691,-1, +0xa693,-1, +0xa695,-1, +0xa697,-1, +0xa699,-1, +0xa69b,-1, +0xa723,-1, +0xa725,-1, +0xa727,-1, +0xa729,-1, +0xa72b,-1, +0xa72d,-1, +0xa72f,-1, +0xa733,-1, +0xa735,-1, +0xa737,-1, +0xa739,-1, +0xa73b,-1, +0xa73d,-1, +0xa73f,-1, +0xa741,-1, +0xa743,-1, +0xa745,-1, +0xa747,-1, +0xa749,-1, +0xa74b,-1, +0xa74d,-1, +0xa74f,-1, +0xa751,-1, +0xa753,-1, +0xa755,-1, +0xa757,-1, +0xa759,-1, +0xa75b,-1, +0xa75d,-1, +0xa75f,-1, +0xa761,-1, +0xa763,-1, +0xa765,-1, +0xa767,-1, +0xa769,-1, +0xa76b,-1, +0xa76d,-1, +0xa76f,-1, +0xa77a,-1, +0xa77c,-1, +0xa77f,-1, +0xa781,-1, +0xa783,-1, +0xa785,-1, +0xa787,-1, +0xa78c,-1, +0xa791,-1, +0xa793,-1, +0xa794,48, +0xa797,-1, +0xa799,-1, +0xa79b,-1, +0xa79d,-1, +0xa79f,-1, +0xa7a1,-1, +0xa7a3,-1, +0xa7a5,-1, +0xa7a7,-1, +0xa7a9,-1, +0xa7b5,-1, +0xa7b7,-1, +0xa7b9,-1, +0xa7bb,-1, +0xa7bd,-1, +0xa7bf,-1, +0xa7c1,-1, +0xa7c3,-1, +0xa7c8,-1, +0xa7ca,-1, +0xa7cd,-1, +0xa7d1,-1, +0xa7d7,-1, +0xa7d9,-1, +0xa7db,-1, +0xa7f6,-1, +0xab53,-928, +};