├── .gitattributes ├── .gitignore ├── AUTHORS ├── COPYING ├── Makefile ├── README ├── astnames.h ├── docs ├── artifex-logo.png ├── examples.html ├── index.html ├── introduction.html ├── license.html ├── logo.ps ├── mujs-logo.png ├── reference.html └── style.css ├── genucd.py ├── jsarray.c ├── jsboolean.c ├── jsbuiltin.c ├── jscompile.c ├── jsdate.c ├── jsdtoa.c ├── jserror.c ├── jsfunction.c ├── jsgc.c ├── jsi.h ├── 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 ├── main.c ├── mujs.h ├── one.c ├── opnames.h ├── pp.c ├── regexp.c ├── regexp.h ├── tools ├── test262 └── test262-harness.js ├── utf.c ├── utf.h └── utfdata.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Define macro for whitespace settings: 2 | [attr]tabs whitespace=trailing-space,space-before-tab,indent-with-non-tab 3 | 4 | * text=auto 5 | Makefile tabs 6 | *.[ch] tabs 7 | *.js tabs 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Optional testsuites 2 | tests 3 | specs 4 | 5 | # Generated files: 6 | build 7 | tags 8 | 9 | # Editor settings: 10 | user.make 11 | .exrc 12 | .vimrc 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Tor Andersson 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2013-2020 Artifex Software, Inc. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building MuJS libraries, shell, and pretty-printer. 2 | # 3 | # Useful targets are: release, install, uninstall. 4 | 5 | default: build/debug/mujs build/debug/mujs-pp 6 | 7 | CFLAGS = -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter 8 | 9 | OPTIM = -O3 10 | 11 | prefix = /usr/local 12 | bindir = $(prefix)/bin 13 | incdir = $(prefix)/include 14 | libdir = $(prefix)/lib 15 | 16 | ifeq ($(wildcard .git),.git) 17 | VERSION = $(shell git describe --tags --always) 18 | else 19 | VERSION = $(patsubst mujs-%,%,$(notdir $(CURDIR))) 20 | endif 21 | 22 | ifeq ($(shell uname),Darwin) 23 | SO = dylib 24 | else 25 | SO = so 26 | endif 27 | 28 | ifeq ($(shell uname),FreeBSD) 29 | CFLAGS += -I/usr/local/include -L/usr/local/lib 30 | endif 31 | 32 | HDRS = mujs.h jsi.h regexp.h utf.h astnames.h opnames.h utfdata.h 33 | 34 | ifneq ($(HAVE_READLINE),no) 35 | READLINE_CFLAGS = -DHAVE_READLINE 36 | READLINE_LIBS = -lreadline 37 | endif 38 | 39 | SRCS = \ 40 | jsarray.c \ 41 | jsboolean.c \ 42 | jsbuiltin.c \ 43 | jscompile.c \ 44 | jsdate.c \ 45 | jsdtoa.c \ 46 | jserror.c \ 47 | jsfunction.c \ 48 | jsgc.c \ 49 | jsintern.c \ 50 | jslex.c \ 51 | jsmath.c \ 52 | jsnumber.c \ 53 | jsobject.c \ 54 | json.c \ 55 | jsparse.c \ 56 | jsproperty.c \ 57 | jsregexp.c \ 58 | jsrepr.c \ 59 | jsrun.c \ 60 | jsstate.c \ 61 | jsstring.c \ 62 | jsvalue.c \ 63 | regexp.c \ 64 | utf.c 65 | 66 | one.c: 67 | for F in $(SRCS); do echo "#include \"$$F\""; done > $@ 68 | 69 | astnames.h: jsi.h 70 | grep -E '\<(AST|EXP|STM)_' jsi.h | sed 's/^[^A-Z]*\(AST_\)*/"/;s/,.*/",/' | tr A-Z a-z > $@ 71 | 72 | opnames.h: jsi.h 73 | grep -E '\ $@ 74 | 75 | UnicodeData.txt: 76 | curl -s -o $@ https://www.unicode.org/Public/16.0.0/ucd/UnicodeData.txt 77 | SpecialCasing.txt: 78 | curl -s -o $@ https://www.unicode.org/Public/16.0.0/ucd/SpecialCasing.txt 79 | 80 | utfdata.h: genucd.py UnicodeData.txt SpecialCasing.txt 81 | python3 genucd.py UnicodeData.txt SpecialCasing.txt >$@ 82 | 83 | build/sanitize/mujs: main.c one.c $(SRCS) $(HDRS) 84 | @mkdir -p $(@D) 85 | $(CC) $(CFLAGS) -g -fsanitize=address -fno-omit-frame-pointer -o $@ main.c one.c -lm $(READLINE_CFLAGS) $(READLINE_LIBS) 86 | 87 | build/debug/libmujs.$(SO): one.c $(SRCS) $(HDRS) 88 | @mkdir -p $(@D) 89 | $(CC) $(CFLAGS) -g -fPIC -shared -o $@ one.c -lm 90 | build/debug/libmujs.o: one.c $(SRCS) $(HDRS) 91 | @mkdir -p $(@D) 92 | $(CC) $(CFLAGS) -g -c -o $@ one.c 93 | build/debug/libmujs.a: build/debug/libmujs.o 94 | $(AR) cr $@ $^ 95 | build/debug/mujs: main.c build/debug/libmujs.o 96 | $(CC) $(CFLAGS) -g -o $@ $^ -lm $(READLINE_CFLAGS) $(READLINE_LIBS) 97 | build/debug/mujs-pp: pp.c build/debug/libmujs.o 98 | $(CC) $(CFLAGS) -g -o $@ $^ -lm 99 | 100 | build/release/libmujs.$(SO): one.c $(SRCS) $(HDRS) 101 | @mkdir -p $(@D) 102 | $(CC) $(CFLAGS) $(OPTIM) -fPIC -shared -o $@ one.c -lm 103 | build/release/libmujs.o: one.c $(SRCS) $(HDRS) 104 | @mkdir -p $(@D) 105 | $(CC) $(CFLAGS) $(OPTIM) -c -o $@ one.c 106 | build/release/libmujs.a: build/release/libmujs.o 107 | $(AR) cr $@ $^ 108 | build/release/mujs: main.c build/release/libmujs.o 109 | $(CC) $(CFLAGS) $(OPTIM) -o $@ $^ -lm $(READLINE_CFLAGS) $(READLINE_LIBS) 110 | build/release/mujs-pp: pp.c build/release/libmujs.o 111 | $(CC) $(CFLAGS) $(OPTIM) -o $@ $^ -lm 112 | 113 | build/release/mujs.pc: 114 | @mkdir -p $(@D) 115 | echo > $@ Name: mujs 116 | echo >> $@ Description: MuJS embeddable Javascript interpreter 117 | echo >> $@ Version: $(VERSION) 118 | echo >> $@ Cflags: -I$(incdir) 119 | echo >> $@ Libs: -L$(libdir) -lmujs 120 | echo >> $@ Libs.private: -lm 121 | 122 | install-common: build/release/mujs build/release/mujs-pp build/release/mujs.pc 123 | install -d $(DESTDIR)$(incdir) 124 | install -d $(DESTDIR)$(libdir) 125 | install -d $(DESTDIR)$(libdir)/pkgconfig 126 | install -d $(DESTDIR)$(bindir) 127 | install -m 644 mujs.h $(DESTDIR)$(incdir) 128 | install -m 644 build/release/mujs.pc $(DESTDIR)$(libdir)/pkgconfig 129 | install -m 755 build/release/mujs $(DESTDIR)$(bindir) 130 | install -m 755 build/release/mujs-pp $(DESTDIR)$(bindir) 131 | 132 | install-static: install-common build/release/libmujs.a 133 | install -m 644 build/release/libmujs.a $(DESTDIR)$(libdir) 134 | 135 | install-shared: install-common build/release/libmujs.$(SO) 136 | install -m 755 build/release/libmujs.$(SO) $(DESTDIR)$(libdir) 137 | 138 | install: install-static 139 | 140 | uninstall: 141 | rm -f $(DESTDIR)$(bindir)/mujs 142 | rm -f $(DESTDIR)$(bindir)/mujs-pp 143 | rm -f $(DESTDIR)$(incdir)/mujs.h 144 | rm -f $(DESTDIR)$(libdir)/pkgconfig/mujs.pc 145 | rm -f $(DESTDIR)$(libdir)/libmujs.a 146 | rm -f $(DESTDIR)$(libdir)/libmujs.$(SO) 147 | 148 | tarball: 149 | git archive --format=zip --prefix=mujs-$(VERSION)/ HEAD > mujs-$(VERSION).zip 150 | git archive --format=tar --prefix=mujs-$(VERSION)/ HEAD | gzip > mujs-$(VERSION).tar.gz 151 | 152 | tags: $(SRCS) $(HDRS) main.c pp.c 153 | ctags $^ 154 | 155 | clean: 156 | rm -rf build 157 | 158 | nuke: clean 159 | rm -f one.c astnames.h opnames.h 160 | 161 | sanitize: build/sanitize/mujs 162 | 163 | debug: build/debug/libmujs.a 164 | debug: build/debug/libmujs.$(SO) 165 | debug: build/debug/mujs 166 | debug: build/debug/mujs-pp 167 | 168 | release: build/release/mujs.pc 169 | release: build/release/libmujs.a 170 | release: build/release/libmujs.$(SO) 171 | release: build/release/mujs 172 | release: build/release/mujs-pp 173 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | MuJS: an embeddable Javascript interpreter in C. 2 | 3 | ABOUT 4 | 5 | MuJS is a lightweight Javascript interpreter designed for embedding in 6 | other software to extend them with scripting capabilities. 7 | 8 | LICENSE 9 | 10 | MuJS is Copyright 2013-2017 Artifex Software, Inc. 11 | 12 | Permission to use, copy, modify, and/or distribute this software for any 13 | purpose with or without fee is hereby granted, provided that the above 14 | copyright notice and this permission notice appear in all copies. 15 | 16 | The software is provided "as is" and the author disclaims all warranties with 17 | regard to this software including all implied warranties of merchantability 18 | and fitness. In no event shall the author be liable for any special, direct, 19 | indirect, or consequential damages or any damages whatsoever resulting from 20 | loss of use, data or profits, whether in an action of contract, negligence 21 | or other tortious action, arising out of or in connection with the use or 22 | performance of this software. 23 | 24 | COMPILING 25 | 26 | If you are building from source you can either use the provided Unix Makefile: 27 | 28 | make release 29 | 30 | Or compile the source with your preferred compiler: 31 | 32 | cc -O2 -c one.c -o libmujs.o 33 | 34 | INSTALLING 35 | 36 | To install the MuJS command line interpreter, static library and header file: 37 | 38 | make prefix=/usr/local install 39 | 40 | DOWNLOAD 41 | 42 | The latest development source is available directly from the git repository: 43 | 44 | git clone http://git.ghostscript.com/mujs.git 45 | 46 | REPORTING BUGS AND PROBLEMS 47 | 48 | Report bugs on the ghostscript bugzilla, with MuJS as the selected component. 49 | 50 | http://bugs.ghostscript.com/ 51 | -------------------------------------------------------------------------------- /astnames.h: -------------------------------------------------------------------------------- 1 | "list", 2 | "fundec", 3 | "identifier", 4 | "exp_identifier", 5 | "exp_number", 6 | "exp_string", 7 | "exp_regexp", 8 | "exp_elision", 9 | "exp_null", 10 | "exp_true", 11 | "exp_false", 12 | "exp_this", 13 | "exp_array", 14 | "exp_object", 15 | "exp_prop_val", 16 | "exp_prop_get", 17 | "exp_prop_set", 18 | "exp_fun", 19 | "exp_index", 20 | "exp_member", 21 | "exp_call", 22 | "exp_new", 23 | "exp_postinc", 24 | "exp_postdec", 25 | "exp_delete", 26 | "exp_void", 27 | "exp_typeof", 28 | "exp_preinc", 29 | "exp_predec", 30 | "exp_pos", 31 | "exp_neg", 32 | "exp_bitnot", 33 | "exp_lognot", 34 | "exp_mod", 35 | "exp_div", 36 | "exp_mul", 37 | "exp_sub", 38 | "exp_add", 39 | "exp_ushr", 40 | "exp_shr", 41 | "exp_shl", 42 | "exp_in", 43 | "exp_instanceof", 44 | "exp_ge", 45 | "exp_le", 46 | "exp_gt", 47 | "exp_lt", 48 | "exp_strictne", 49 | "exp_stricteq", 50 | "exp_ne", 51 | "exp_eq", 52 | "exp_bitand", 53 | "exp_bitxor", 54 | "exp_bitor", 55 | "exp_logand", 56 | "exp_logor", 57 | "exp_cond", 58 | "exp_ass", 59 | "exp_ass_mul", 60 | "exp_ass_div", 61 | "exp_ass_mod", 62 | "exp_ass_add", 63 | "exp_ass_sub", 64 | "exp_ass_shl", 65 | "exp_ass_shr", 66 | "exp_ass_ushr", 67 | "exp_ass_bitand", 68 | "exp_ass_bitxor", 69 | "exp_ass_bitor", 70 | "exp_comma", 71 | "exp_var", 72 | "stm_block", 73 | "stm_empty", 74 | "stm_var", 75 | "stm_if", 76 | "stm_do", 77 | "stm_while", 78 | "stm_for", 79 | "stm_for_var", 80 | "stm_for_in", 81 | "stm_for_in_var", 82 | "stm_continue", 83 | "stm_break", 84 | "stm_return", 85 | "stm_with", 86 | "stm_switch", 87 | "stm_throw", 88 | "stm_try", 89 | "stm_debugger", 90 | "stm_label", 91 | "stm_case", 92 | "stm_default", 93 | -------------------------------------------------------------------------------- /docs/artifex-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtifexSoftware/mujs/94ec2f2d7c0a48200dcc433e98fc172454c8d683/docs/artifex-logo.png -------------------------------------------------------------------------------- /docs/examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MuJS Examples 6 | 7 | 8 | 9 | 10 |
11 |

MuJS Examples

12 |
13 | 14 | 22 | 23 |
24 | 25 |

A stand-alone interpreter

26 | 27 |
 28 | #include <stdio.h>
 29 | #include <mujs.h>
 30 | 
 31 | int main(int argc, char **argv)
 32 | {
 33 | 	char line[256];
 34 | 	js_State *J = js_newstate(NULL, NULL, JS_STRICT);
 35 | 	while (fgets(line, sizeof line, stdin))
 36 | 		js_dostring(J, line);
 37 | 	js_freestate(J);
 38 | }
 39 | 
40 | 41 |

Hello, world!

42 | 43 |
 44 | #include <stdio.h>
 45 | #include <mujs.h>
 46 | 
 47 | static void hello(js_State *J)
 48 | {
 49 | 	const char *name = js_tostring(J, 1);
 50 | 	printf("Hello, %s!\n", name);
 51 | 	js_pushundefined(J);
 52 | }
 53 | 
 54 | int main(int argc, char **argv)
 55 | {
 56 | 	js_State *J = js_newstate(NULL, NULL, JS_STRICT);
 57 | 
 58 | 	js_newcfunction(J, hello, "hello", 1);
 59 | 	js_setglobal(J, "hello");
 60 | 
 61 | 	js_dostring(J, "hello('world');");
 62 | 
 63 | 	js_freestate(J);
 64 | }
 65 | 
66 | 67 |

Configuration file

68 | 69 |
 70 | js_dofile(J, "config.js")
 71 | 
 72 | js_getglobal(J, "foo");
 73 | foo = js_tonumber(J, -1);
 74 | js_pop(J, 1);
 75 | 
76 | 77 |

Object manipulation

78 | 79 |
 80 | // t = { foo: 42, bar: true }
 81 | 
 82 | js_newobject(J);
 83 | {
 84 | 	js_pushnumber(J, 42);
 85 | 	js_setproperty(J, -2, "foo");
 86 | 	js_pushboolean(J, 1);
 87 | 	js_setproperty(J, -2, "bar");
 88 | }
 89 | js_setglobal(J, "t");
 90 | 
91 | 92 |

Callbacks from C to JS (by name)

93 | 94 |
 95 | static int call_callback(js_State *J, const char *arg1, int arg2)
 96 | {
 97 | 	int result;
 98 | 
 99 | 	/* Find the function to call. */
100 | 	js_getglobal(J, "my_callback");
101 | 
102 | 	/* Push arguments to function. */
103 | 	js_pushnull(J); /* the 'this' object to use */
104 | 	js_pushstring(J, arg1);
105 | 	js_pushnumber(J, arg2);
106 | 
107 | 	/* Call function and check for exceptions. */
108 | 	if (js_pcall(J, 2)) {
109 | 		fprintf(stderr, "an exception occurred in the javascript callback\n");
110 | 		js_pop(J, 1);
111 | 		return -1;
112 | 	}
113 | 
114 | 	/* Retrieve return value. */
115 | 	result = js_tonumber(J, -1);
116 | 	js_pop(J, 1);
117 | 
118 | 	return result;
119 | }
120 | 
121 | 122 |

Callbacks from C to JS

123 | 124 |
125 | const char *handle = NULL; /* handle to stowed away js function */
126 | 
127 | static void set_callback(js_State *J)
128 | {
129 | 	if (handle)
130 | 		js_unref(J, handle); /* delete old function */
131 | 	js_copy(J, 1);
132 | 	handle = js_ref(J); /* stow the js function in the registry */
133 | }
134 | 
135 | static void call_callback(js_State *J, int arg1, int arg2)
136 | {
137 | 	js_getregistry(J, handle); /* retrieve the js function from the registry */
138 | 	js_pushnull(J);
139 | 	js_pushnumber(J, arg1);
140 | 	js_pushnumber(J, arg2);
141 | 	js_pcall(J, 2);
142 | 	js_pop(J, 1);
143 | }
144 | 
145 | 146 |

Complete userdata example

147 | 148 |
149 | #include <stdio.h>
150 | #include <mujs.h>
151 | 
152 | #define TAG "File"
153 | 
154 | static void new_File(js_State *J)
155 | {
156 | 	FILE *file;
157 | 
158 | 	if (js_isundefined(J, 1)) {
159 | 		file = stdin;
160 | 	} else {
161 | 		const char *filename = js_tostring(J, 1);
162 | 		file = fopen(filename, "r");
163 | 		if (!file)
164 | 			js_error(J, "cannot open file: '%s'", filename);
165 | 	}
166 | 
167 | 	js_currentfunction(J);
168 | 	js_getproperty(J, -1, "prototype");
169 | 	js_newuserdata(J, TAG, file);
170 | }
171 | 
172 | static void File_prototype_readByte(js_State *J)
173 | {
174 | 	FILE *file = js_touserdata(J, 0, TAG);
175 | 	js_pushnumber(J, getc(file));
176 | }
177 | 
178 | static void File_prototype_readLine(js_State *J)
179 | {
180 | 	char line[256], *s;
181 | 	FILE *file = js_touserdata(J, 0, TAG);
182 | 	s = fgets(line, sizeof line, file);
183 | 	if (s)
184 | 		js_pushstring(J, line);
185 | 	else
186 | 		js_pushnull(J);
187 | }
188 | 
189 | static void File_prototype_close(js_State *J)
190 | {
191 | 	FILE *file = js_touserdata(J, 0, TAG);
192 | 	fclose(file);
193 | 	js_pushundefined(J);
194 | }
195 | 
196 | void initfile(js_State *J)
197 | {
198 | 	js_getglobal(J, "Object");
199 | 	js_getproperty(J, -1, "prototype");	// File.prototype.[[Prototype]] = Object.prototype
200 | 	js_newuserdata(J, TAG, stdin);		// File.prototype.[[Userdata]] = stdin
201 | 	{
202 | 		js_newcfunction(J, File_prototype_readByte, "File.prototype.readByte", 0);
203 | 		js_defproperty(J, -2, "readByte", JS_DONTENUM);
204 | 
205 | 		js_newcfunction(J, File_prototype_readLine, "File.prototype.readLine", 0);
206 | 		js_defproperty(J, -2, "readLine", JS_DONTENUM);
207 | 
208 | 		js_newcfunction(J, File_prototype_close, "File.prototype.close", 0);
209 | 		js_defproperty(J, -2, "close", JS_DONTENUM);
210 | 	}
211 | 	js_newcconstructor(J, new_File, new_File, "File", 1);
212 | 	js_defglobal(J, "File", JS_DONTENUM);
213 | }
214 | 
215 | 216 |
217 | 218 |
219 | 220 | Copyright © 2013-2017 Artifex Software Inc. 221 |
222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MuJS 6 | 7 | 8 | 9 | 10 |
11 |

MuJS

12 |
13 | 14 | 22 | 23 |
24 | 25 |

26 | MuJS is a lightweight Javascript interpreter designed for embedding in other 27 | software to extend them with scripting capabilities. 28 | 29 |

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

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

42 | The primary meeting place for the MuJS community is the 43 | #mupdf 44 | IRC channel on freenode. 45 | 46 |

47 | MuJS is free open source software distributed under the 48 | ISC license. 49 | 50 |

51 | 52 |
53 | 54 | Copyright © 2013-2017 Artifex Software Inc. 55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MuJS Introduction 6 | 7 | 8 | 9 | 10 |
11 |

MuJS Introduction

12 |
13 | 14 | 22 | 23 |
24 | 25 |

Why choose MuJS?

26 | 27 |

Javascript is a proven scripting language

28 | 29 |

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

36 | With MuJS you can bring this power to your application as well! 37 | 38 |

MuJS is standards compliant

39 | 40 |

41 | MuJS implements ES5. 42 | There are no non-standard extensions, so you can remain confident that 43 | Javascript code that runs on MuJS will also run on any other standards 44 | compliant Javascript implementation. 45 | 46 |

MuJS is portable

47 | 48 |

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

MuJS is embeddable

55 | 56 |

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

MuJS is small

64 | 65 |

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

MuJS is reasonably fast and secure

77 | 78 |

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

MuJS is free software

87 | 88 |

89 | MuJS is free open source software distributed under the 90 | ISC license. 91 | 92 |

MuJS is developed by a stable company

93 | 94 |

95 | Artifex Software has long experience in 96 | interpreters and page description languages, and has a history with open source 97 | that goes back to 1993 when it was created to facilitate licensing Ghostscript 98 | to OEMs. 99 | 100 |

101 | 102 |
103 | 104 | Copyright © 2013-2017 Artifex Software Inc. 105 |
106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/license.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MuJS License 6 | 7 | 8 | 9 | 10 |
11 |

MuJS License

12 |
13 | 14 | 22 | 23 |
24 | 25 |

26 | MuJS is Copyright © 2013-2017 Artifex Software, Inc. 27 | 28 |

29 | Permission to use, copy, modify, and/or distribute this software for any 30 | purpose with or without fee is hereby granted, provided that the above 31 | copyright notice and this permission notice appear in all copies. 32 | 33 |

34 | The software is provided "as is" and the author disclaims all warranties with 35 | regard to this software including all implied warranties of merchantability and 36 | fitness. In no event shall the author be liable for any special, direct, 37 | indirect, or consequential damages or any damages whatsoever resulting from 38 | loss of use, data or profits, whether in an action of contract, negligence or 39 | other tortious action, arising out of or in connection with the use or 40 | performance of this software. 41 | 42 |

43 | 44 |
45 | 46 | Copyright © 2013-2017 Artifex Software Inc. 47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/logo.ps: -------------------------------------------------------------------------------- 1 | %! 2 | <>setpagedevice 3 | 4 | % #323330 = 50 51 48 5 | % #F0DB4F = 240 219 79 6 | % #4386b5 = 67 134 181 7 | 8 | /cG { 50 255 div 51 255 div 48 255 div setrgbcolor } def 9 | /cY { 240 255 div 219 255 div 79 255 div setrgbcolor } def 10 | /cB { 67 255 div 134 255 div 181 255 div setrgbcolor } def 11 | 12 | % fill background with yellow 13 | cY 14 | 0 0 moveto 512 0 lineto 512 512 lineto 0 512 lineto closepath fill 15 | 16 | % move logo to lower right corner 17 | 512 0.2 mul 0 translate 18 | 0.8 0.8 scale 19 | 20 | % center logo 21 | 0.875 0.875 scale 22 | 32 32 translate 23 | 24 | % draw electrons and nucleus 25 | cG 26 | gsave 27 | 256 256 translate 28 | 29 | 16 setlinewidth 30 | gsave 0 rotate .5 1 scale 0 0 232 0 360 arc stroke grestore 31 | gsave 60 rotate .5 1 scale 0 0 232 0 360 arc stroke grestore 32 | gsave 120 rotate .5 1 scale 0 0 232 0 360 arc stroke grestore 33 | 34 | 0 0 96 0 360 arc fill 35 | grestore 36 | 37 | % draw yellow 'JS' text in center of nucleus 38 | cY 39 | gsave 40 | /SourceSansPro-Bold findfont 128 scalefont setfont 41 | 256 256 moveto 42 | (JS) 43 | dup stringwidth pop -2 div -44 rmoveto 44 | show 45 | grestore 46 | 47 | showpage 48 | -------------------------------------------------------------------------------- /docs/mujs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtifexSoftware/mujs/94ec2f2d7c0a48200dcc433e98fc172454c8d683/docs/mujs-logo.png -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | h1, nav, footer { font-family: sans-serif; } 2 | 3 | a { text-decoration: none; } 4 | a:hover { text-decoration: underline; } 5 | h2 { font-size: 1.25rem; } 6 | h3 { font-size: 1.12rem; } 7 | ul li { list-style-type: circle; } 8 | pre, table, ol, dl { margin-left: 2rem; } 9 | li { margin: 0; } 10 | th, td { text-align: left; vertical-align: top; } 11 | 12 | body { margin: 0; } 13 | h1 { 14 | font-weight: normal; 15 | margin: 0; 16 | padding: 1rem 2rem; 17 | } 18 | header{ 19 | color: white; 20 | background: no-repeat; 21 | background-color: #36648b; 22 | background-image: url("mujs-logo.png"); 23 | background-position: top right; 24 | min-height: 72px; 25 | } 26 | nav { 27 | padding: 0.75rem 2rem; 28 | background-color: #ddd; 29 | no-text-transform: uppercase; 30 | } 31 | nav a { color: #303030; padding-right: 2rem; } 32 | article { 33 | max-width: 50rem; 34 | margin: 2rem; 35 | } 36 | footer { 37 | background-color: #ddd; 38 | color: #303030; 39 | padding: 1rem 2rem; 40 | } 41 | -------------------------------------------------------------------------------- /genucd.py: -------------------------------------------------------------------------------- 1 | # Create utfdata.h from UnicodeData.txt and SpecialCasing.txt 2 | 3 | import sys 4 | 5 | tolower = [] 6 | toupper = [] 7 | tolower_full = [] 8 | toupper_full = [] 9 | isalpha = [] 10 | 11 | for line in open(sys.argv[1]).readlines(): 12 | line = line.split(";") 13 | code = int(line[0],16) 14 | # if code > 65535: continue # skip non-BMP codepoints 15 | if line[2][0] == 'L': 16 | isalpha.append(code) 17 | if line[12]: 18 | toupper.append((code,int(line[12],16))) 19 | if line[13]: 20 | tolower.append((code,int(line[13],16))) 21 | 22 | for line in open(sys.argv[2]).readlines(): 23 | # SpecialCasing.txt -- code; lower; title; upper; (condition;)? # comment 24 | line = line.strip() 25 | if len(line) == 0: 26 | continue 27 | if line[0] == "#": 28 | continue 29 | line = line.split(";") 30 | code = int(line[0],16) 31 | lower = line[1].strip() 32 | upper = line[3].strip() 33 | if len(lower) == 0 or len(upper) == 0: 34 | continue 35 | condition = line[4].split("#")[0].strip() 36 | if len(condition) > 0: 37 | continue 38 | lower = list(map(lambda x: int(x,16), lower.split(" "))) 39 | upper = list(map(lambda x: int(x,16), upper.split(" "))) 40 | if lower[0] != code: 41 | tolower_full.append([code] + lower) 42 | if upper[0] != code: 43 | toupper_full.append([code] + upper) 44 | 45 | tolower_full.sort() 46 | toupper_full.sort() 47 | 48 | def dumpalpha(): 49 | table = [] 50 | prev = 0 51 | start = 0 52 | for code in isalpha: 53 | if code != prev+1: 54 | if start: 55 | table.append((start,prev)) 56 | start = code 57 | prev = code 58 | table.append((start,prev)) 59 | 60 | print("") 61 | print("static const Rune ucd_alpha2[] = {") 62 | for a, b in table: 63 | if b - a > 0: 64 | print(hex(a)+","+hex(b)+",") 65 | print("};"); 66 | 67 | print("") 68 | print("static const Rune ucd_alpha1[] = {") 69 | for a, b in table: 70 | if b - a == 0: 71 | print(hex(a)+",") 72 | print("};"); 73 | 74 | def dumpmap(name, input): 75 | table = [] 76 | prev_a = 0 77 | prev_b = 0 78 | start_a = 0 79 | start_b = 0 80 | for a, b in input: 81 | if a != prev_a+1 or b != prev_b+1: 82 | if start_a: 83 | table.append((start_a,prev_a,start_b)) 84 | start_a = a 85 | start_b = b 86 | prev_a = a 87 | prev_b = b 88 | table.append((start_a,prev_a,start_b)) 89 | 90 | print("") 91 | print("static const Rune " + name + "2[] = {") 92 | for a, b, n in table: 93 | if b - a > 0: 94 | print(hex(a)+","+hex(b)+","+str(n-a)+",") 95 | print("};"); 96 | 97 | print("") 98 | print("static const Rune " + name + "1[] = {") 99 | for a, b, n in table: 100 | if b - a == 0: 101 | print(hex(a)+","+str(n-a)+",") 102 | print("};"); 103 | 104 | def dumpmultimap(name, table, w): 105 | print("") 106 | print("static const Rune " + name + "[] = {") 107 | for list in table: 108 | list += [0] * (w - len(list)) 109 | print(",".join(map(hex, list)) + ",") 110 | print("};") 111 | 112 | print("/* This file was automatically created from " + sys.argv[1] + " */") 113 | dumpalpha() 114 | dumpmap("ucd_tolower", tolower) 115 | dumpmap("ucd_toupper", toupper) 116 | dumpmultimap("ucd_tolower_full", tolower_full, 4) 117 | dumpmultimap("ucd_toupper_full", toupper_full, 5) 118 | -------------------------------------------------------------------------------- /jsarray.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | #ifndef JS_HEAPSORT 4 | #define JS_HEAPSORT 0 5 | #endif 6 | 7 | int js_getlength(js_State *J, int idx) 8 | { 9 | int len; 10 | js_getproperty(J, idx, "length"); 11 | len = js_tointeger(J, -1); 12 | js_pop(J, 1); 13 | return len; 14 | } 15 | 16 | void js_setlength(js_State *J, int idx, int len) 17 | { 18 | js_pushnumber(J, len); 19 | js_setproperty(J, idx < 0 ? idx - 1 : idx, "length"); 20 | } 21 | 22 | static void jsB_new_Array(js_State *J) 23 | { 24 | int i, top = js_gettop(J); 25 | 26 | js_newarray(J); 27 | 28 | if (top == 2) { 29 | if (js_isnumber(J, 1)) { 30 | js_copy(J, 1); 31 | js_setproperty(J, -2, "length"); 32 | } else { 33 | js_copy(J, 1); 34 | js_setindex(J, -2, 0); 35 | } 36 | } else { 37 | for (i = 1; i < top; ++i) { 38 | js_copy(J, i); 39 | js_setindex(J, -2, i - 1); 40 | } 41 | } 42 | } 43 | 44 | static void Ap_concat(js_State *J) 45 | { 46 | int i, top = js_gettop(J); 47 | int n, k, len; 48 | 49 | js_newarray(J); 50 | n = 0; 51 | 52 | for (i = 0; i < top; ++i) { 53 | js_copy(J, i); 54 | if (js_isarray(J, -1)) { 55 | len = js_getlength(J, -1); 56 | for (k = 0; k < len; ++k) 57 | if (js_hasindex(J, -1, k)) 58 | js_setindex(J, -3, n++); 59 | js_pop(J, 1); 60 | } else { 61 | js_setindex(J, -2, n++); 62 | } 63 | } 64 | } 65 | 66 | static void Ap_join(js_State *J) 67 | { 68 | char * volatile out = NULL; 69 | const char * volatile r = NULL; 70 | const char *sep; 71 | int seplen; 72 | int k, n, len, rlen; 73 | 74 | len = js_getlength(J, 0); 75 | 76 | if (js_isdefined(J, 1)) { 77 | sep = js_tostring(J, 1); 78 | seplen = strlen(sep); 79 | } else { 80 | sep = ","; 81 | seplen = 1; 82 | } 83 | 84 | if (len <= 0) { 85 | js_pushliteral(J, ""); 86 | return; 87 | } 88 | 89 | if (js_try(J)) { 90 | js_free(J, out); 91 | js_throw(J); 92 | } 93 | 94 | n = 0; 95 | for (k = 0; k < len; ++k) { 96 | js_getindex(J, 0, k); 97 | if (js_iscoercible(J, -1)) { 98 | r = js_tostring(J, -1); 99 | rlen = strlen(r); 100 | } else { 101 | rlen = 0; 102 | } 103 | 104 | if (k == 0) { 105 | out = js_malloc(J, rlen + 1); 106 | if (rlen > 0) { 107 | memcpy(out, r, rlen); 108 | n += rlen; 109 | } 110 | } else { 111 | if (n + seplen + rlen > JS_STRLIMIT) 112 | js_rangeerror(J, "invalid string length"); 113 | out = js_realloc(J, out, n + seplen + rlen + 1); 114 | if (seplen > 0) { 115 | memcpy(out + n, sep, seplen); 116 | n += seplen; 117 | } 118 | if (rlen > 0) { 119 | memcpy(out + n, r, rlen); 120 | n += rlen; 121 | } 122 | } 123 | 124 | js_pop(J, 1); 125 | } 126 | 127 | js_pushlstring(J, out, n); 128 | js_endtry(J); 129 | js_free(J, out); 130 | } 131 | 132 | static void Ap_pop(js_State *J) 133 | { 134 | int n; 135 | 136 | n = js_getlength(J, 0); 137 | 138 | if (n > 0) { 139 | js_getindex(J, 0, n - 1); 140 | js_delindex(J, 0, n - 1); 141 | js_setlength(J, 0, n - 1); 142 | } else { 143 | js_setlength(J, 0, 0); 144 | js_pushundefined(J); 145 | } 146 | } 147 | 148 | static void Ap_push(js_State *J) 149 | { 150 | int i, top = js_gettop(J); 151 | int n; 152 | 153 | n = js_getlength(J, 0); 154 | 155 | for (i = 1; i < top; ++i, ++n) { 156 | js_copy(J, i); 157 | js_setindex(J, 0, n); 158 | } 159 | 160 | js_setlength(J, 0, n); 161 | 162 | js_pushnumber(J, n); 163 | } 164 | 165 | static void Ap_reverse(js_State *J) 166 | { 167 | int len, middle, lower; 168 | 169 | len = js_getlength(J, 0); 170 | middle = len / 2; 171 | lower = 0; 172 | 173 | while (lower != middle) { 174 | int upper = len - lower - 1; 175 | int haslower = js_hasindex(J, 0, lower); 176 | int hasupper = js_hasindex(J, 0, upper); 177 | if (haslower && hasupper) { 178 | js_setindex(J, 0, lower); 179 | js_setindex(J, 0, upper); 180 | } else if (hasupper) { 181 | js_setindex(J, 0, lower); 182 | js_delindex(J, 0, upper); 183 | } else if (haslower) { 184 | js_setindex(J, 0, upper); 185 | js_delindex(J, 0, lower); 186 | } 187 | ++lower; 188 | } 189 | 190 | js_copy(J, 0); 191 | } 192 | 193 | static void Ap_shift(js_State *J) 194 | { 195 | int k, len; 196 | 197 | len = js_getlength(J, 0); 198 | 199 | if (len == 0) { 200 | js_setlength(J, 0, 0); 201 | js_pushundefined(J); 202 | return; 203 | } 204 | 205 | js_getindex(J, 0, 0); 206 | 207 | for (k = 1; k < len; ++k) { 208 | if (js_hasindex(J, 0, k)) 209 | js_setindex(J, 0, k - 1); 210 | else 211 | js_delindex(J, 0, k - 1); 212 | } 213 | 214 | js_delindex(J, 0, len - 1); 215 | js_setlength(J, 0, len - 1); 216 | } 217 | 218 | static void Ap_slice(js_State *J) 219 | { 220 | int len, s, e, n; 221 | double sv, ev; 222 | 223 | js_newarray(J); 224 | 225 | len = js_getlength(J, 0); 226 | sv = js_tointeger(J, 1); 227 | ev = js_isdefined(J, 2) ? js_tointeger(J, 2) : len; 228 | 229 | if (sv < 0) sv = sv + len; 230 | if (ev < 0) ev = ev + len; 231 | 232 | s = sv < 0 ? 0 : sv > len ? len : sv; 233 | e = ev < 0 ? 0 : ev > len ? len : ev; 234 | 235 | for (n = 0; s < e; ++s, ++n) 236 | if (js_hasindex(J, 0, s)) 237 | js_setindex(J, -2, n); 238 | } 239 | 240 | static int Ap_sort_cmp(js_State *J, int idx_a, int idx_b) 241 | { 242 | js_Object *obj = js_tovalue(J, 0)->u.object; 243 | if (obj->u.a.simple) { 244 | js_Value *val_a = &obj->u.a.array[idx_a]; 245 | js_Value *val_b = &obj->u.a.array[idx_b]; 246 | int und_a = val_a->t.type == JS_TUNDEFINED; 247 | int und_b = val_b->t.type == JS_TUNDEFINED; 248 | if (und_a) return und_b; 249 | if (und_b) return -1; 250 | if (js_iscallable(J, 1)) { 251 | double v; 252 | js_copy(J, 1); /* copy function */ 253 | js_pushundefined(J); /* no 'this' binding */ 254 | js_pushvalue(J, *val_a); 255 | js_pushvalue(J, *val_b); 256 | js_call(J, 2); 257 | v = js_tonumber(J, -1); 258 | js_pop(J, 1); 259 | if (isnan(v)) 260 | return 0; 261 | if (v == 0) 262 | return 0; 263 | return v < 0 ? -1 : 1; 264 | } else { 265 | const char *str_a, *str_b; 266 | int c; 267 | js_pushvalue(J, *val_a); 268 | js_pushvalue(J, *val_b); 269 | str_a = js_tostring(J, -2); 270 | str_b = js_tostring(J, -1); 271 | c = strcmp(str_a, str_b); 272 | js_pop(J, 2); 273 | return c; 274 | } 275 | } else { 276 | int und_a, und_b; 277 | int has_a = js_hasindex(J, 0, idx_a); 278 | int has_b = js_hasindex(J, 0, idx_b); 279 | if (!has_a && !has_b) { 280 | return 0; 281 | } 282 | if (has_a && !has_b) { 283 | js_pop(J, 1); 284 | return -1; 285 | } 286 | if (!has_a && has_b) { 287 | js_pop(J, 1); 288 | return 1; 289 | } 290 | 291 | und_a = js_isundefined(J, -2); 292 | und_b = js_isundefined(J, -1); 293 | if (und_a) { 294 | js_pop(J, 2); 295 | return und_b; 296 | } 297 | if (und_b) { 298 | js_pop(J, 2); 299 | return -1; 300 | } 301 | 302 | if (js_iscallable(J, 1)) { 303 | double v; 304 | js_copy(J, 1); /* copy function */ 305 | js_pushundefined(J); /* no 'this' binding */ 306 | js_copy(J, -4); 307 | js_copy(J, -4); 308 | js_call(J, 2); 309 | v = js_tonumber(J, -1); 310 | js_pop(J, 3); 311 | if (isnan(v)) 312 | return 0; 313 | if (v == 0) 314 | return 0; 315 | return v < 0 ? -1 : 1; 316 | } else { 317 | const char *str_a = js_tostring(J, -2); 318 | const char *str_b = js_tostring(J, -1); 319 | int c = strcmp(str_a, str_b); 320 | js_pop(J, 2); 321 | return c; 322 | } 323 | } 324 | } 325 | 326 | static void Ap_sort_swap(js_State *J, int idx_a, int idx_b) 327 | { 328 | js_Object *obj = js_tovalue(J, 0)->u.object; 329 | if (obj->u.a.simple) { 330 | js_Value tmp = obj->u.a.array[idx_a]; 331 | obj->u.a.array[idx_a] = obj->u.a.array[idx_b]; 332 | obj->u.a.array[idx_b] = tmp; 333 | } else { 334 | int has_a = js_hasindex(J, 0, idx_a); 335 | int has_b = js_hasindex(J, 0, idx_b); 336 | if (has_a && has_b) { 337 | js_setindex(J, 0, idx_a); 338 | js_setindex(J, 0, idx_b); 339 | } else if (has_a && !has_b) { 340 | js_delindex(J, 0, idx_a); 341 | js_setindex(J, 0, idx_b); 342 | } else if (!has_a && has_b) { 343 | js_delindex(J, 0, idx_b); 344 | js_setindex(J, 0, idx_a); 345 | } 346 | } 347 | } 348 | 349 | /* A bottom-up/bouncing heapsort implementation */ 350 | 351 | static int Ap_sort_leaf(js_State *J, int i, int end) 352 | { 353 | int j = i; 354 | int lc = (j << 1) + 1; /* left child */ 355 | int rc = (j << 1) + 2; /* right child */ 356 | while (rc < end) { 357 | if (Ap_sort_cmp(J, rc, lc) > 0) 358 | j = rc; 359 | else 360 | j = lc; 361 | lc = (j << 1) + 1; 362 | rc = (j << 1) + 2; 363 | } 364 | if (lc < end) 365 | j = lc; 366 | return j; 367 | } 368 | 369 | static void Ap_sort_sift(js_State *J, int i, int end) 370 | { 371 | int j = Ap_sort_leaf(J, i, end); 372 | while (Ap_sort_cmp(J, i, j) > 0) 373 | j = (j - 1) >> 1; /* parent */ 374 | while (j > i) { 375 | Ap_sort_swap(J, i, j); 376 | j = (j - 1) >> 1; /* parent */ 377 | } 378 | } 379 | 380 | static void Ap_sort_heapsort(js_State *J, int n) 381 | { 382 | int i; 383 | for (i = n / 2 - 1; i >= 0; --i) 384 | Ap_sort_sift(J, i, n); 385 | for (i = n - 1; i > 0; --i) { 386 | Ap_sort_swap(J, 0, i); 387 | Ap_sort_sift(J, 0, i); 388 | } 389 | } 390 | 391 | static void Ap_sort(js_State *J) 392 | { 393 | int len; 394 | 395 | len = js_getlength(J, 0); 396 | if (len <= 1) { 397 | js_copy(J, 0); 398 | return; 399 | } 400 | 401 | if (!js_iscallable(J, 1) && !js_isundefined(J, 1)) 402 | js_typeerror(J, "comparison function must be a function or undefined"); 403 | 404 | if (len >= INT_MAX) 405 | js_rangeerror(J, "array is too large to sort"); 406 | 407 | Ap_sort_heapsort(J, len); 408 | 409 | js_copy(J, 0); 410 | } 411 | 412 | static void Ap_splice(js_State *J) 413 | { 414 | int top = js_gettop(J); 415 | int len, start, del, add, k; 416 | 417 | len = js_getlength(J, 0); 418 | start = js_tointeger(J, 1); 419 | if (start < 0) 420 | start = (len + start) > 0 ? len + start : 0; 421 | else if (start > len) 422 | start = len; 423 | 424 | if (js_isdefined(J, 2)) 425 | del = js_tointeger(J, 2); 426 | else 427 | del = len - start; 428 | if (del > len - start) 429 | del = len - start; 430 | if (del < 0) 431 | del = 0; 432 | 433 | js_newarray(J); 434 | 435 | /* copy deleted items to return array */ 436 | for (k = 0; k < del; ++k) 437 | if (js_hasindex(J, 0, start + k)) 438 | js_setindex(J, -2, k); 439 | js_setlength(J, -1, del); 440 | 441 | /* shift the tail to resize the hole left by deleted items */ 442 | add = top - 3; 443 | if (add < del) { 444 | for (k = start; k < len - del; ++k) { 445 | if (js_hasindex(J, 0, k + del)) 446 | js_setindex(J, 0, k + add); 447 | else 448 | js_delindex(J, 0, k + add); 449 | } 450 | for (k = len; k > len - del + add; --k) 451 | js_delindex(J, 0, k - 1); 452 | } else if (add > del) { 453 | for (k = len - del; k > start; --k) { 454 | if (js_hasindex(J, 0, k + del - 1)) 455 | js_setindex(J, 0, k + add - 1); 456 | else 457 | js_delindex(J, 0, k + add - 1); 458 | } 459 | } 460 | 461 | /* copy new items into the hole */ 462 | for (k = 0; k < add; ++k) { 463 | js_copy(J, 3 + k); 464 | js_setindex(J, 0, start + k); 465 | } 466 | 467 | js_setlength(J, 0, len - del + add); 468 | } 469 | 470 | static void Ap_unshift(js_State *J) 471 | { 472 | int i, top = js_gettop(J); 473 | int k, len; 474 | 475 | len = js_getlength(J, 0); 476 | 477 | for (k = len; k > 0; --k) { 478 | int from = k - 1; 479 | int to = k + top - 2; 480 | if (js_hasindex(J, 0, from)) 481 | js_setindex(J, 0, to); 482 | else 483 | js_delindex(J, 0, to); 484 | } 485 | 486 | for (i = 1; i < top; ++i) { 487 | js_copy(J, i); 488 | js_setindex(J, 0, i - 1); 489 | } 490 | 491 | js_setlength(J, 0, len + top - 1); 492 | 493 | js_pushnumber(J, len + top - 1); 494 | } 495 | 496 | static void Ap_toString(js_State *J) 497 | { 498 | if (!js_iscoercible(J, 0)) 499 | js_typeerror(J, "'this' is not an object"); 500 | js_getproperty(J, 0, "join"); 501 | if (!js_iscallable(J, -1)) { 502 | js_pop(J, 1); 503 | /* TODO: call Object.prototype.toString implementation directly */ 504 | js_getglobal(J, "Object"); 505 | js_getproperty(J, -1, "prototype"); 506 | js_rot2pop1(J); 507 | js_getproperty(J, -1, "toString"); 508 | js_rot2pop1(J); 509 | } 510 | js_copy(J, 0); 511 | js_call(J, 0); 512 | } 513 | 514 | static void Ap_indexOf(js_State *J) 515 | { 516 | int k, len, from; 517 | 518 | len = js_getlength(J, 0); 519 | from = js_isdefined(J, 2) ? js_tointeger(J, 2) : 0; 520 | if (from < 0) from = len + from; 521 | if (from < 0) from = 0; 522 | 523 | js_copy(J, 1); 524 | for (k = from; k < len; ++k) { 525 | if (js_hasindex(J, 0, k)) { 526 | if (js_strictequal(J)) { 527 | js_pushnumber(J, k); 528 | return; 529 | } 530 | js_pop(J, 1); 531 | } 532 | } 533 | 534 | js_pushnumber(J, -1); 535 | } 536 | 537 | static void Ap_lastIndexOf(js_State *J) 538 | { 539 | int k, len, from; 540 | 541 | len = js_getlength(J, 0); 542 | from = js_isdefined(J, 2) ? js_tointeger(J, 2) : len - 1; 543 | if (from > len - 1) from = len - 1; 544 | if (from < 0) from = len + from; 545 | 546 | js_copy(J, 1); 547 | for (k = from; k >= 0; --k) { 548 | if (js_hasindex(J, 0, k)) { 549 | if (js_strictequal(J)) { 550 | js_pushnumber(J, k); 551 | return; 552 | } 553 | js_pop(J, 1); 554 | } 555 | } 556 | 557 | js_pushnumber(J, -1); 558 | } 559 | 560 | static void Ap_every(js_State *J) 561 | { 562 | int hasthis = js_gettop(J) >= 3; 563 | int k, len; 564 | 565 | if (!js_iscallable(J, 1)) 566 | js_typeerror(J, "callback is not a function"); 567 | 568 | len = js_getlength(J, 0); 569 | for (k = 0; k < len; ++k) { 570 | if (js_hasindex(J, 0, k)) { 571 | js_copy(J, 1); 572 | if (hasthis) 573 | js_copy(J, 2); 574 | else 575 | js_pushundefined(J); 576 | js_copy(J, -3); 577 | js_pushnumber(J, k); 578 | js_copy(J, 0); 579 | js_call(J, 3); 580 | if (!js_toboolean(J, -1)) 581 | return; 582 | js_pop(J, 2); 583 | } 584 | } 585 | 586 | js_pushboolean(J, 1); 587 | } 588 | 589 | static void Ap_some(js_State *J) 590 | { 591 | int hasthis = js_gettop(J) >= 3; 592 | int k, len; 593 | 594 | if (!js_iscallable(J, 1)) 595 | js_typeerror(J, "callback is not a function"); 596 | 597 | len = js_getlength(J, 0); 598 | for (k = 0; k < len; ++k) { 599 | if (js_hasindex(J, 0, k)) { 600 | js_copy(J, 1); 601 | if (hasthis) 602 | js_copy(J, 2); 603 | else 604 | js_pushundefined(J); 605 | js_copy(J, -3); 606 | js_pushnumber(J, k); 607 | js_copy(J, 0); 608 | js_call(J, 3); 609 | if (js_toboolean(J, -1)) 610 | return; 611 | js_pop(J, 2); 612 | } 613 | } 614 | 615 | js_pushboolean(J, 0); 616 | } 617 | 618 | static void Ap_forEach(js_State *J) 619 | { 620 | int hasthis = js_gettop(J) >= 3; 621 | int k, len; 622 | 623 | if (!js_iscallable(J, 1)) 624 | js_typeerror(J, "callback is not a function"); 625 | 626 | len = js_getlength(J, 0); 627 | for (k = 0; k < len; ++k) { 628 | if (js_hasindex(J, 0, k)) { 629 | js_copy(J, 1); 630 | if (hasthis) 631 | js_copy(J, 2); 632 | else 633 | js_pushundefined(J); 634 | js_copy(J, -3); 635 | js_pushnumber(J, k); 636 | js_copy(J, 0); 637 | js_call(J, 3); 638 | js_pop(J, 2); 639 | } 640 | } 641 | 642 | js_pushundefined(J); 643 | } 644 | 645 | static void Ap_map(js_State *J) 646 | { 647 | int hasthis = js_gettop(J) >= 3; 648 | int k, len; 649 | 650 | if (!js_iscallable(J, 1)) 651 | js_typeerror(J, "callback is not a function"); 652 | 653 | js_newarray(J); 654 | 655 | len = js_getlength(J, 0); 656 | for (k = 0; k < len; ++k) { 657 | if (js_hasindex(J, 0, k)) { 658 | js_copy(J, 1); 659 | if (hasthis) 660 | js_copy(J, 2); 661 | else 662 | js_pushundefined(J); 663 | js_copy(J, -3); 664 | js_pushnumber(J, k); 665 | js_copy(J, 0); 666 | js_call(J, 3); 667 | js_setindex(J, -3, k); 668 | js_pop(J, 1); 669 | } 670 | } 671 | js_setlength(J, -1, len); 672 | } 673 | 674 | static void Ap_filter(js_State *J) 675 | { 676 | int hasthis = js_gettop(J) >= 3; 677 | int k, to, len; 678 | 679 | if (!js_iscallable(J, 1)) 680 | js_typeerror(J, "callback is not a function"); 681 | 682 | js_newarray(J); 683 | to = 0; 684 | 685 | len = js_getlength(J, 0); 686 | for (k = 0; k < len; ++k) { 687 | if (js_hasindex(J, 0, k)) { 688 | js_copy(J, 1); 689 | if (hasthis) 690 | js_copy(J, 2); 691 | else 692 | js_pushundefined(J); 693 | js_copy(J, -3); 694 | js_pushnumber(J, k); 695 | js_copy(J, 0); 696 | js_call(J, 3); 697 | if (js_toboolean(J, -1)) { 698 | js_pop(J, 1); 699 | js_setindex(J, -2, to++); 700 | } else { 701 | js_pop(J, 2); 702 | } 703 | } 704 | } 705 | } 706 | 707 | static void Ap_reduce(js_State *J) 708 | { 709 | int hasinitial = js_gettop(J) >= 3; 710 | int k, len; 711 | 712 | if (!js_iscallable(J, 1)) 713 | js_typeerror(J, "callback is not a function"); 714 | 715 | len = js_getlength(J, 0); 716 | k = 0; 717 | 718 | if (len == 0 && !hasinitial) 719 | js_typeerror(J, "no initial value"); 720 | 721 | /* initial value of accumulator */ 722 | if (hasinitial) 723 | js_copy(J, 2); 724 | else { 725 | while (k < len) 726 | if (js_hasindex(J, 0, k++)) 727 | break; 728 | if (k == len) 729 | js_typeerror(J, "no initial value"); 730 | } 731 | 732 | while (k < len) { 733 | if (js_hasindex(J, 0, k)) { 734 | js_copy(J, 1); 735 | js_pushundefined(J); 736 | js_rot(J, 4); /* accumulator on top */ 737 | js_rot(J, 4); /* property on top */ 738 | js_pushnumber(J, k); 739 | js_copy(J, 0); 740 | js_call(J, 4); /* calculate new accumulator */ 741 | } 742 | ++k; 743 | } 744 | 745 | /* return accumulator */ 746 | } 747 | 748 | static void Ap_reduceRight(js_State *J) 749 | { 750 | int hasinitial = js_gettop(J) >= 3; 751 | int k, len; 752 | 753 | if (!js_iscallable(J, 1)) 754 | js_typeerror(J, "callback is not a function"); 755 | 756 | len = js_getlength(J, 0); 757 | k = len - 1; 758 | 759 | if (len == 0 && !hasinitial) 760 | js_typeerror(J, "no initial value"); 761 | 762 | /* initial value of accumulator */ 763 | if (hasinitial) 764 | js_copy(J, 2); 765 | else { 766 | while (k >= 0) 767 | if (js_hasindex(J, 0, k--)) 768 | break; 769 | if (k < 0) 770 | js_typeerror(J, "no initial value"); 771 | } 772 | 773 | while (k >= 0) { 774 | if (js_hasindex(J, 0, k)) { 775 | js_copy(J, 1); 776 | js_pushundefined(J); 777 | js_rot(J, 4); /* accumulator on top */ 778 | js_rot(J, 4); /* property on top */ 779 | js_pushnumber(J, k); 780 | js_copy(J, 0); 781 | js_call(J, 4); /* calculate new accumulator */ 782 | } 783 | --k; 784 | } 785 | 786 | /* return accumulator */ 787 | } 788 | 789 | static void A_isArray(js_State *J) 790 | { 791 | if (js_isobject(J, 1)) { 792 | js_Object *T = js_toobject(J, 1); 793 | js_pushboolean(J, T->type == JS_CARRAY); 794 | } else { 795 | js_pushboolean(J, 0); 796 | } 797 | } 798 | 799 | void jsB_initarray(js_State *J) 800 | { 801 | js_pushobject(J, J->Array_prototype); 802 | { 803 | jsB_propf(J, "Array.prototype.toString", Ap_toString, 0); 804 | jsB_propf(J, "Array.prototype.concat", Ap_concat, 0); /* 1 */ 805 | jsB_propf(J, "Array.prototype.join", Ap_join, 1); 806 | jsB_propf(J, "Array.prototype.pop", Ap_pop, 0); 807 | jsB_propf(J, "Array.prototype.push", Ap_push, 0); /* 1 */ 808 | jsB_propf(J, "Array.prototype.reverse", Ap_reverse, 0); 809 | jsB_propf(J, "Array.prototype.shift", Ap_shift, 0); 810 | jsB_propf(J, "Array.prototype.slice", Ap_slice, 2); 811 | jsB_propf(J, "Array.prototype.sort", Ap_sort, 1); 812 | jsB_propf(J, "Array.prototype.splice", Ap_splice, 2); 813 | jsB_propf(J, "Array.prototype.unshift", Ap_unshift, 0); /* 1 */ 814 | 815 | /* ES5 */ 816 | jsB_propf(J, "Array.prototype.indexOf", Ap_indexOf, 1); 817 | jsB_propf(J, "Array.prototype.lastIndexOf", Ap_lastIndexOf, 1); 818 | jsB_propf(J, "Array.prototype.every", Ap_every, 1); 819 | jsB_propf(J, "Array.prototype.some", Ap_some, 1); 820 | jsB_propf(J, "Array.prototype.forEach", Ap_forEach, 1); 821 | jsB_propf(J, "Array.prototype.map", Ap_map, 1); 822 | jsB_propf(J, "Array.prototype.filter", Ap_filter, 1); 823 | jsB_propf(J, "Array.prototype.reduce", Ap_reduce, 1); 824 | jsB_propf(J, "Array.prototype.reduceRight", Ap_reduceRight, 1); 825 | } 826 | js_newcconstructor(J, jsB_new_Array, jsB_new_Array, "Array", 0); /* 1 */ 827 | { 828 | /* ES5 */ 829 | jsB_propf(J, "Array.isArray", A_isArray, 1); 830 | } 831 | js_defglobal(J, "Array", JS_DONTENUM); 832 | } 833 | -------------------------------------------------------------------------------- /jsboolean.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | static void jsB_new_Boolean(js_State *J) 4 | { 5 | js_newboolean(J, js_toboolean(J, 1)); 6 | } 7 | 8 | static void jsB_Boolean(js_State *J) 9 | { 10 | js_pushboolean(J, js_toboolean(J, 1)); 11 | } 12 | 13 | static void Bp_toString(js_State *J) 14 | { 15 | js_Object *self = js_toobject(J, 0); 16 | if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean"); 17 | js_pushliteral(J, self->u.boolean ? "true" : "false"); 18 | } 19 | 20 | static void Bp_valueOf(js_State *J) 21 | { 22 | js_Object *self = js_toobject(J, 0); 23 | if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean"); 24 | js_pushboolean(J, self->u.boolean); 25 | } 26 | 27 | void jsB_initboolean(js_State *J) 28 | { 29 | J->Boolean_prototype->u.boolean = 0; 30 | 31 | js_pushobject(J, J->Boolean_prototype); 32 | { 33 | jsB_propf(J, "Boolean.prototype.toString", Bp_toString, 0); 34 | jsB_propf(J, "Boolean.prototype.valueOf", Bp_valueOf, 0); 35 | } 36 | js_newcconstructor(J, jsB_Boolean, jsB_new_Boolean, "Boolean", 1); 37 | js_defglobal(J, "Boolean", JS_DONTENUM); 38 | } 39 | -------------------------------------------------------------------------------- /jsbuiltin.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | #include "regexp.h" 3 | 4 | static void jsB_globalf(js_State *J, const char *name, js_CFunction cfun, int n) 5 | { 6 | js_newcfunction(J, cfun, name, n); 7 | js_defglobal(J, name, JS_DONTENUM); 8 | } 9 | 10 | void jsB_propf(js_State *J, const char *name, js_CFunction cfun, int n) 11 | { 12 | const char *pname = strrchr(name, '.'); 13 | pname = pname ? pname + 1 : name; 14 | js_newcfunction(J, cfun, name, n); 15 | js_defproperty(J, -2, pname, JS_DONTENUM); 16 | } 17 | 18 | void jsB_propn(js_State *J, const char *name, double number) 19 | { 20 | js_pushnumber(J, number); 21 | js_defproperty(J, -2, name, JS_READONLY | JS_DONTENUM | JS_DONTCONF); 22 | } 23 | 24 | void jsB_props(js_State *J, const char *name, const char *string) 25 | { 26 | js_pushliteral(J, string); 27 | js_defproperty(J, -2, name, JS_DONTENUM); 28 | } 29 | 30 | static void jsB_parseInt(js_State *J) 31 | { 32 | const char *s = js_tostring(J, 1); 33 | int radix = js_isdefined(J, 2) ? js_tointeger(J, 2) : 0; 34 | double sign = 1; 35 | double n; 36 | char *e; 37 | 38 | while (jsY_iswhite(*s) || jsY_isnewline(*s)) 39 | ++s; 40 | if (*s == '-') { 41 | ++s; 42 | sign = -1; 43 | } else if (*s == '+') { 44 | ++s; 45 | } 46 | if (radix == 0) { 47 | radix = 10; 48 | if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { 49 | s += 2; 50 | radix = 16; 51 | } 52 | } else if (radix < 2 || radix > 36) { 53 | js_pushnumber(J, NAN); 54 | return; 55 | } 56 | n = js_strtol(s, &e, radix); 57 | if (s == e) 58 | js_pushnumber(J, NAN); 59 | else 60 | js_pushnumber(J, n * sign); 61 | } 62 | 63 | static void jsB_parseFloat(js_State *J) 64 | { 65 | const char *s = js_tostring(J, 1); 66 | char *e; 67 | double n; 68 | 69 | while (jsY_iswhite(*s) || jsY_isnewline(*s)) ++s; 70 | if (!strncmp(s, "Infinity", 8)) 71 | js_pushnumber(J, INFINITY); 72 | else if (!strncmp(s, "+Infinity", 9)) 73 | js_pushnumber(J, INFINITY); 74 | else if (!strncmp(s, "-Infinity", 9)) 75 | js_pushnumber(J, -INFINITY); 76 | else { 77 | n = js_stringtofloat(s, &e); 78 | if (e == s) 79 | js_pushnumber(J, NAN); 80 | else 81 | js_pushnumber(J, n); 82 | } 83 | } 84 | 85 | static void jsB_isNaN(js_State *J) 86 | { 87 | double n = js_tonumber(J, 1); 88 | js_pushboolean(J, isnan(n)); 89 | } 90 | 91 | static void jsB_isFinite(js_State *J) 92 | { 93 | double n = js_tonumber(J, 1); 94 | js_pushboolean(J, isfinite(n)); 95 | } 96 | 97 | static void Encode(js_State *J, const char *str_, const char *unescaped) 98 | { 99 | /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */ 100 | const char * volatile str = str_; 101 | js_Buffer *sb = NULL; 102 | 103 | static const char *HEX = "0123456789ABCDEF"; 104 | 105 | if (js_try(J)) { 106 | js_free(J, sb); 107 | js_throw(J); 108 | } 109 | 110 | while (*str) { 111 | int c = (unsigned char) *str++; 112 | if (strchr(unescaped, c)) 113 | js_putc(J, &sb, c); 114 | else { 115 | js_putc(J, &sb, '%'); 116 | js_putc(J, &sb, HEX[(c >> 4) & 0xf]); 117 | js_putc(J, &sb, HEX[c & 0xf]); 118 | } 119 | } 120 | js_putc(J, &sb, 0); 121 | 122 | js_pushstring(J, sb ? sb->s : ""); 123 | js_endtry(J); 124 | js_free(J, sb); 125 | } 126 | 127 | static void Decode(js_State *J, const char *str_, const char *reserved) 128 | { 129 | /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */ 130 | const char * volatile str = str_; 131 | js_Buffer *sb = NULL; 132 | int a, b; 133 | 134 | if (js_try(J)) { 135 | js_free(J, sb); 136 | js_throw(J); 137 | } 138 | 139 | while (*str) { 140 | int c = (unsigned char) *str++; 141 | if (c != '%') 142 | js_putc(J, &sb, c); 143 | else { 144 | if (!str[0] || !str[1]) 145 | js_urierror(J, "truncated escape sequence"); 146 | a = *str++; 147 | b = *str++; 148 | if (!jsY_ishex(a) || !jsY_ishex(b)) 149 | js_urierror(J, "invalid escape sequence"); 150 | c = jsY_tohex(a) << 4 | jsY_tohex(b); 151 | if (!strchr(reserved, c)) 152 | js_putc(J, &sb, c); 153 | else { 154 | js_putc(J, &sb, '%'); 155 | js_putc(J, &sb, a); 156 | js_putc(J, &sb, b); 157 | } 158 | } 159 | } 160 | js_putc(J, &sb, 0); 161 | 162 | js_pushstring(J, sb ? sb->s : ""); 163 | js_endtry(J); 164 | js_free(J, sb); 165 | } 166 | 167 | #define URIRESERVED ";/?:@&=+$," 168 | #define URIALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 169 | #define URIDIGIT "0123456789" 170 | #define URIMARK "-_.!~*'()" 171 | #define URIUNESCAPED URIALPHA URIDIGIT URIMARK 172 | 173 | static void jsB_decodeURI(js_State *J) 174 | { 175 | Decode(J, js_tostring(J, 1), URIRESERVED "#"); 176 | } 177 | 178 | static void jsB_decodeURIComponent(js_State *J) 179 | { 180 | Decode(J, js_tostring(J, 1), ""); 181 | } 182 | 183 | static void jsB_encodeURI(js_State *J) 184 | { 185 | Encode(J, js_tostring(J, 1), URIUNESCAPED URIRESERVED "#"); 186 | } 187 | 188 | static void jsB_encodeURIComponent(js_State *J) 189 | { 190 | Encode(J, js_tostring(J, 1), URIUNESCAPED); 191 | } 192 | 193 | void jsB_init(js_State *J) 194 | { 195 | /* Create the prototype objects here, before the constructors */ 196 | J->Object_prototype = jsV_newobject(J, JS_COBJECT, NULL); 197 | J->Array_prototype = jsV_newobject(J, JS_CARRAY, J->Object_prototype); 198 | J->Function_prototype = jsV_newobject(J, JS_CCFUNCTION, J->Object_prototype); 199 | J->Boolean_prototype = jsV_newobject(J, JS_CBOOLEAN, J->Object_prototype); 200 | J->Number_prototype = jsV_newobject(J, JS_CNUMBER, J->Object_prototype); 201 | J->String_prototype = jsV_newobject(J, JS_CSTRING, J->Object_prototype); 202 | J->Date_prototype = jsV_newobject(J, JS_CDATE, J->Object_prototype); 203 | 204 | J->RegExp_prototype = jsV_newobject(J, JS_CREGEXP, J->Object_prototype); 205 | J->RegExp_prototype->u.r.prog = js_regcompx(J->alloc, J->actx, "(?:)", 0, NULL); 206 | J->RegExp_prototype->u.r.source = js_strdup(J, "(?:)"); 207 | 208 | /* All the native error types */ 209 | J->Error_prototype = jsV_newobject(J, JS_CERROR, J->Object_prototype); 210 | J->EvalError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); 211 | J->RangeError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); 212 | J->ReferenceError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); 213 | J->SyntaxError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); 214 | J->TypeError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); 215 | J->URIError_prototype = jsV_newobject(J, JS_CERROR, J->Error_prototype); 216 | 217 | /* Create the constructors and fill out the prototype objects */ 218 | jsB_initobject(J); 219 | jsB_initarray(J); 220 | jsB_initfunction(J); 221 | jsB_initboolean(J); 222 | jsB_initnumber(J); 223 | jsB_initstring(J); 224 | jsB_initregexp(J); 225 | jsB_initdate(J); 226 | jsB_initerror(J); 227 | jsB_initmath(J); 228 | jsB_initjson(J); 229 | 230 | /* Initialize the global object */ 231 | js_pushnumber(J, NAN); 232 | js_defglobal(J, "NaN", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 233 | 234 | js_pushnumber(J, INFINITY); 235 | js_defglobal(J, "Infinity", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 236 | 237 | js_pushundefined(J); 238 | js_defglobal(J, "undefined", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 239 | 240 | jsB_globalf(J, "parseInt", jsB_parseInt, 1); 241 | jsB_globalf(J, "parseFloat", jsB_parseFloat, 1); 242 | jsB_globalf(J, "isNaN", jsB_isNaN, 1); 243 | jsB_globalf(J, "isFinite", jsB_isFinite, 1); 244 | 245 | jsB_globalf(J, "decodeURI", jsB_decodeURI, 1); 246 | jsB_globalf(J, "decodeURIComponent", jsB_decodeURIComponent, 1); 247 | jsB_globalf(J, "encodeURI", jsB_encodeURI, 1); 248 | jsB_globalf(J, "encodeURIComponent", jsB_encodeURIComponent, 1); 249 | } 250 | -------------------------------------------------------------------------------- /jserror.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | #define QQ(X) #X 4 | #define Q(X) QQ(X) 5 | 6 | static int jsB_stacktrace(js_State *J, int skip) 7 | { 8 | char buf[256]; 9 | int n = J->tracetop - skip; 10 | if (n <= 0) 11 | return 0; 12 | for (; n > 0; --n) { 13 | const char *name = J->trace[n].name; 14 | const char *file = J->trace[n].file; 15 | int line = J->trace[n].line; 16 | if (line > 0) { 17 | if (name[0]) 18 | snprintf(buf, sizeof buf, "\n\tat %s (%s:%d)", name, file, line); 19 | else 20 | snprintf(buf, sizeof buf, "\n\tat %s:%d", file, line); 21 | } else 22 | snprintf(buf, sizeof buf, "\n\tat %s (%s)", name, file); 23 | js_pushstring(J, buf); 24 | if (n < J->tracetop - skip) 25 | js_concat(J); 26 | } 27 | return 1; 28 | } 29 | 30 | static void Ep_toString(js_State *J) 31 | { 32 | const char *name = "Error"; 33 | const char *message = ""; 34 | 35 | if (!js_isobject(J, -1)) 36 | js_typeerror(J, "not an object"); 37 | 38 | if (js_hasproperty(J, 0, "name")) 39 | name = js_tostring(J, -1); 40 | if (js_hasproperty(J, 0, "message")) 41 | message = js_tostring(J, -1); 42 | 43 | if (name[0] == 0) 44 | js_pushstring(J, message); 45 | else if (message[0] == 0) 46 | js_pushstring(J, name); 47 | else { 48 | js_pushstring(J, name); 49 | js_pushstring(J, ": "); 50 | js_concat(J); 51 | js_pushstring(J, message); 52 | js_concat(J); 53 | } 54 | } 55 | 56 | static int jsB_ErrorX(js_State *J, js_Object *prototype) 57 | { 58 | js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype)); 59 | if (js_isdefined(J, 1)) { 60 | js_pushstring(J, js_tostring(J, 1)); 61 | js_defproperty(J, -2, "message", JS_DONTENUM); 62 | } 63 | if (jsB_stacktrace(J, 1)) 64 | js_defproperty(J, -2, "stackTrace", JS_DONTENUM); 65 | return 1; 66 | } 67 | 68 | static void js_newerrorx(js_State *J, const char *message, js_Object *prototype) 69 | { 70 | js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype)); 71 | js_pushstring(J, message); 72 | js_setproperty(J, -2, "message"); 73 | if (jsB_stacktrace(J, 0)) 74 | js_setproperty(J, -2, "stackTrace"); 75 | } 76 | 77 | #define DERROR(name, Name) \ 78 | static void jsB_##Name(js_State *J) { \ 79 | jsB_ErrorX(J, J->Name##_prototype); \ 80 | } \ 81 | void js_new##name(js_State *J, const char *s) { \ 82 | js_newerrorx(J, s, J->Name##_prototype); \ 83 | } \ 84 | void js_##name(js_State *J, const char *fmt, ...) { \ 85 | va_list ap; \ 86 | char buf[256]; \ 87 | va_start(ap, fmt); \ 88 | vsnprintf(buf, sizeof buf, fmt, ap); \ 89 | va_end(ap); \ 90 | js_newerrorx(J, buf, J->Name##_prototype); \ 91 | js_throw(J); \ 92 | } 93 | 94 | DERROR(error, Error) 95 | DERROR(evalerror, EvalError) 96 | DERROR(rangeerror, RangeError) 97 | DERROR(referenceerror, ReferenceError) 98 | DERROR(syntaxerror, SyntaxError) 99 | DERROR(typeerror, TypeError) 100 | DERROR(urierror, URIError) 101 | 102 | #undef DERROR 103 | 104 | void jsB_initerror(js_State *J) 105 | { 106 | js_pushobject(J, J->Error_prototype); 107 | { 108 | jsB_props(J, "name", "Error"); 109 | jsB_propf(J, "Error.prototype.toString", Ep_toString, 0); 110 | } 111 | js_newcconstructor(J, jsB_Error, jsB_Error, "Error", 1); 112 | js_defglobal(J, "Error", JS_DONTENUM); 113 | 114 | #define IERROR(NAME) \ 115 | js_pushobject(J, J->NAME##_prototype); \ 116 | jsB_props(J, "name", Q(NAME)); \ 117 | js_newcconstructor(J, jsB_##NAME, jsB_##NAME, Q(NAME), 1); \ 118 | js_defglobal(J, Q(NAME), JS_DONTENUM); 119 | 120 | IERROR(EvalError); 121 | IERROR(RangeError); 122 | IERROR(ReferenceError); 123 | IERROR(SyntaxError); 124 | IERROR(TypeError); 125 | IERROR(URIError); 126 | 127 | #undef IERROR 128 | } 129 | -------------------------------------------------------------------------------- /jsfunction.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | static void jsB_Function(js_State *J) 4 | { 5 | int i, top = js_gettop(J); 6 | js_Buffer *sb = NULL; 7 | const char *body; 8 | js_Ast *parse; 9 | js_Function *fun; 10 | 11 | if (js_try(J)) { 12 | js_free(J, sb); 13 | jsP_freeparse(J); 14 | js_throw(J); 15 | } 16 | 17 | /* p1, p2, ..., pn */ 18 | if (top > 2) { 19 | for (i = 1; i < top - 1; ++i) { 20 | if (i > 1) 21 | js_putc(J, &sb, ','); 22 | js_puts(J, &sb, js_tostring(J, i)); 23 | } 24 | js_putc(J, &sb, ')'); 25 | js_putc(J, &sb, 0); 26 | } 27 | 28 | /* body */ 29 | body = js_isdefined(J, top - 1) ? js_tostring(J, top - 1) : ""; 30 | 31 | parse = jsP_parsefunction(J, "[string]", sb ? sb->s : NULL, body); 32 | fun = jsC_compilefunction(J, parse); 33 | 34 | js_endtry(J); 35 | js_free(J, sb); 36 | jsP_freeparse(J); 37 | 38 | js_newfunction(J, fun, J->GE); 39 | } 40 | 41 | static void jsB_Function_prototype(js_State *J) 42 | { 43 | js_pushundefined(J); 44 | } 45 | 46 | static void Fp_toString(js_State *J) 47 | { 48 | js_Object *self = js_toobject(J, 0); 49 | js_Buffer *sb = NULL; 50 | int i; 51 | 52 | if (!js_iscallable(J, 0)) 53 | js_typeerror(J, "not a function"); 54 | 55 | if (self->type == JS_CFUNCTION || self->type == JS_CSCRIPT) { 56 | js_Function *F = self->u.f.function; 57 | 58 | if (js_try(J)) { 59 | js_free(J, sb); 60 | js_throw(J); 61 | } 62 | 63 | js_puts(J, &sb, "function "); 64 | js_puts(J, &sb, F->name); 65 | js_putc(J, &sb, '('); 66 | for (i = 0; i < F->numparams; ++i) { 67 | if (i > 0) js_putc(J, &sb, ','); 68 | js_puts(J, &sb, F->vartab[i]); 69 | } 70 | js_puts(J, &sb, ") { [byte code] }"); 71 | js_putc(J, &sb, 0); 72 | 73 | js_pushstring(J, sb->s); 74 | js_endtry(J); 75 | js_free(J, sb); 76 | } else if (self->type == JS_CCFUNCTION) { 77 | if (js_try(J)) { 78 | js_free(J, sb); 79 | js_throw(J); 80 | } 81 | 82 | js_puts(J, &sb, "function "); 83 | js_puts(J, &sb, self->u.c.name); 84 | js_puts(J, &sb, "() { [native code] }"); 85 | js_putc(J, &sb, 0); 86 | 87 | js_pushstring(J, sb->s); 88 | js_endtry(J); 89 | js_free(J, sb); 90 | } else { 91 | js_pushliteral(J, "function () { }"); 92 | } 93 | } 94 | 95 | static void Fp_apply(js_State *J) 96 | { 97 | int i, n; 98 | 99 | if (!js_iscallable(J, 0)) 100 | js_typeerror(J, "not a function"); 101 | 102 | js_copy(J, 0); 103 | js_copy(J, 1); 104 | 105 | if (js_isnull(J, 2) || js_isundefined(J, 2)) { 106 | n = 0; 107 | } else { 108 | n = js_getlength(J, 2); 109 | if (n < 0) 110 | n = 0; 111 | for (i = 0; i < n; ++i) 112 | js_getindex(J, 2, i); 113 | } 114 | 115 | js_call(J, n); 116 | } 117 | 118 | static void Fp_call(js_State *J) 119 | { 120 | int i, top = js_gettop(J); 121 | 122 | if (!js_iscallable(J, 0)) 123 | js_typeerror(J, "not a function"); 124 | 125 | for (i = 0; i < top; ++i) 126 | js_copy(J, i); 127 | 128 | js_call(J, top - 2); 129 | } 130 | 131 | static void callbound(js_State *J) 132 | { 133 | int top = js_gettop(J); 134 | int i, fun, args, n; 135 | 136 | fun = js_gettop(J); 137 | js_currentfunction(J); 138 | js_getproperty(J, fun, "__TargetFunction__"); 139 | js_getproperty(J, fun, "__BoundThis__"); 140 | 141 | args = js_gettop(J); 142 | js_getproperty(J, fun, "__BoundArguments__"); 143 | n = js_getlength(J, args); 144 | if (n < 0) 145 | n = 0; 146 | for (i = 0; i < n; ++i) 147 | js_getindex(J, args, i); 148 | js_remove(J, args); 149 | 150 | for (i = 1; i < top; ++i) 151 | js_copy(J, i); 152 | 153 | js_call(J, n + top - 1); 154 | } 155 | 156 | static void constructbound(js_State *J) 157 | { 158 | int top = js_gettop(J); 159 | int i, fun, args, n; 160 | 161 | fun = js_gettop(J); 162 | js_currentfunction(J); 163 | js_getproperty(J, fun, "__TargetFunction__"); 164 | 165 | args = js_gettop(J); 166 | js_getproperty(J, fun, "__BoundArguments__"); 167 | n = js_getlength(J, args); 168 | if (n < 0) 169 | n = 0; 170 | for (i = 0; i < n; ++i) 171 | js_getindex(J, args, i); 172 | js_remove(J, args); 173 | 174 | for (i = 1; i < top; ++i) 175 | js_copy(J, i); 176 | 177 | js_construct(J, n + top - 1); 178 | } 179 | 180 | static void Fp_bind(js_State *J) 181 | { 182 | int i, top = js_gettop(J); 183 | int n; 184 | 185 | if (!js_iscallable(J, 0)) 186 | js_typeerror(J, "not a function"); 187 | 188 | n = js_getlength(J, 0); 189 | if (n > top - 2) 190 | n -= top - 2; 191 | else 192 | n = 0; 193 | 194 | /* Reuse target function's prototype for HasInstance check. */ 195 | js_getproperty(J, 0, "prototype"); 196 | js_newcconstructor(J, callbound, constructbound, "[bind]", n); 197 | 198 | /* target function */ 199 | js_copy(J, 0); 200 | js_defproperty(J, -2, "__TargetFunction__", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 201 | 202 | /* bound this */ 203 | js_copy(J, 1); 204 | js_defproperty(J, -2, "__BoundThis__", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 205 | 206 | /* bound arguments */ 207 | js_newarray(J); 208 | for (i = 2; i < top; ++i) { 209 | js_copy(J, i); 210 | js_setindex(J, -2, i - 2); 211 | } 212 | js_defproperty(J, -2, "__BoundArguments__", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 213 | } 214 | 215 | void jsB_initfunction(js_State *J) 216 | { 217 | J->Function_prototype->u.c.name = "Function.prototype"; 218 | J->Function_prototype->u.c.function = jsB_Function_prototype; 219 | J->Function_prototype->u.c.constructor = NULL; 220 | J->Function_prototype->u.c.length = 0; 221 | 222 | js_pushobject(J, J->Function_prototype); 223 | { 224 | jsB_propf(J, "Function.prototype.toString", Fp_toString, 2); 225 | jsB_propf(J, "Function.prototype.apply", Fp_apply, 2); 226 | jsB_propf(J, "Function.prototype.call", Fp_call, 1); 227 | jsB_propf(J, "Function.prototype.bind", Fp_bind, 1); 228 | } 229 | js_newcconstructor(J, jsB_Function, jsB_Function, "Function", 1); 230 | js_defglobal(J, "Function", JS_DONTENUM); 231 | } 232 | -------------------------------------------------------------------------------- /jsgc.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | #include "regexp.h" 3 | 4 | static void jsG_freeenvironment(js_State *J, js_Environment *env) 5 | { 6 | js_free(J, env); 7 | } 8 | 9 | static void jsG_freefunction(js_State *J, js_Function *fun) 10 | { 11 | js_free(J, fun->funtab); 12 | js_free(J, fun->vartab); 13 | js_free(J, fun->code); 14 | js_free(J, fun); 15 | } 16 | 17 | static void jsG_freeproperty(js_State *J, js_Property *node) 18 | { 19 | if (node->left->level) jsG_freeproperty(J, node->left); 20 | if (node->right->level) jsG_freeproperty(J, node->right); 21 | js_free(J, node); 22 | } 23 | 24 | static void jsG_freeiterator(js_State *J, js_Iterator *node) 25 | { 26 | while (node) { 27 | js_Iterator *next = node->next; 28 | js_free(J, node); 29 | node = next; 30 | } 31 | } 32 | 33 | static void jsG_freeobject(js_State *J, js_Object *obj) 34 | { 35 | if (obj->properties->level) 36 | jsG_freeproperty(J, obj->properties); 37 | if (obj->type == JS_CREGEXP) { 38 | js_free(J, obj->u.r.source); 39 | js_regfreex(J->alloc, J->actx, obj->u.r.prog); 40 | } 41 | if (obj->type == JS_CSTRING) { 42 | if (obj->u.s.string != obj->u.s.shrstr) 43 | js_free(J, obj->u.s.string); 44 | } 45 | if (obj->type == JS_CARRAY && obj->u.a.simple) 46 | js_free(J, obj->u.a.array); 47 | if (obj->type == JS_CITERATOR) 48 | jsG_freeiterator(J, obj->u.iter.head); 49 | if (obj->type == JS_CUSERDATA && obj->u.user.finalize) 50 | obj->u.user.finalize(J, obj->u.user.data); 51 | if (obj->type == JS_CCFUNCTION && obj->u.c.finalize) 52 | obj->u.c.finalize(J, obj->u.c.data); 53 | js_free(J, obj); 54 | } 55 | 56 | /* Mark and add object to scan queue */ 57 | static void jsG_markobject(js_State *J, int mark, js_Object *obj) 58 | { 59 | obj->gcmark = mark; 60 | obj->gcroot = J->gcroot; 61 | J->gcroot = obj; 62 | } 63 | 64 | static void jsG_markfunction(js_State *J, int mark, js_Function *fun) 65 | { 66 | int i; 67 | fun->gcmark = mark; 68 | for (i = 0; i < fun->funlen; ++i) 69 | if (fun->funtab[i]->gcmark != mark) 70 | jsG_markfunction(J, mark, fun->funtab[i]); 71 | } 72 | 73 | static void jsG_markenvironment(js_State *J, int mark, js_Environment *env) 74 | { 75 | do { 76 | env->gcmark = mark; 77 | if (env->variables->gcmark != mark) 78 | jsG_markobject(J, mark, env->variables); 79 | env = env->outer; 80 | } while (env && env->gcmark != mark); 81 | } 82 | 83 | static void jsG_markproperty(js_State *J, int mark, js_Property *node) 84 | { 85 | if (node->left->level) jsG_markproperty(J, mark, node->left); 86 | if (node->right->level) jsG_markproperty(J, mark, node->right); 87 | 88 | if (node->value.t.type == JS_TMEMSTR && node->value.u.memstr->gcmark != mark) 89 | node->value.u.memstr->gcmark = mark; 90 | if (node->value.t.type == JS_TOBJECT && node->value.u.object->gcmark != mark) 91 | jsG_markobject(J, mark, node->value.u.object); 92 | if (node->getter && node->getter->gcmark != mark) 93 | jsG_markobject(J, mark, node->getter); 94 | if (node->setter && node->setter->gcmark != mark) 95 | jsG_markobject(J, mark, node->setter); 96 | } 97 | 98 | /* Mark everything the object can reach. */ 99 | static void jsG_scanobject(js_State *J, int mark, js_Object *obj) 100 | { 101 | if (obj->properties->level) 102 | jsG_markproperty(J, mark, obj->properties); 103 | if (obj->prototype && obj->prototype->gcmark != mark) 104 | jsG_markobject(J, mark, obj->prototype); 105 | if (obj->type == JS_CARRAY && obj->u.a.simple) { 106 | int i; 107 | for (i = 0; i < obj->u.a.flat_length; ++i) { 108 | js_Value *v = &obj->u.a.array[i]; 109 | if (v->t.type == JS_TMEMSTR && v->u.memstr->gcmark != mark) 110 | v->u.memstr->gcmark = mark; 111 | if (v->t.type == JS_TOBJECT && v->u.object->gcmark != mark) 112 | jsG_markobject(J, mark, v->u.object); 113 | } 114 | } 115 | if (obj->type == JS_CITERATOR && obj->u.iter.target->gcmark != mark) { 116 | jsG_markobject(J, mark, obj->u.iter.target); 117 | } 118 | if (obj->type == JS_CFUNCTION || obj->type == JS_CSCRIPT) { 119 | if (obj->u.f.scope && obj->u.f.scope->gcmark != mark) 120 | jsG_markenvironment(J, mark, obj->u.f.scope); 121 | if (obj->u.f.function && obj->u.f.function->gcmark != mark) 122 | jsG_markfunction(J, mark, obj->u.f.function); 123 | } 124 | } 125 | 126 | static void jsG_markstack(js_State *J, int mark) 127 | { 128 | js_Value *v = J->stack; 129 | int n = J->top; 130 | while (n--) { 131 | if (v->t.type == JS_TMEMSTR && v->u.memstr->gcmark != mark) 132 | v->u.memstr->gcmark = mark; 133 | if (v->t.type == JS_TOBJECT && v->u.object->gcmark != mark) 134 | jsG_markobject(J, mark, v->u.object); 135 | ++v; 136 | } 137 | } 138 | 139 | void js_gc(js_State *J, int report) 140 | { 141 | js_Function *fun, *nextfun, **prevnextfun; 142 | js_Object *obj, *nextobj, **prevnextobj; 143 | js_String *str, *nextstr, **prevnextstr; 144 | js_Environment *env, *nextenv, **prevnextenv; 145 | unsigned int nenv = 0, nfun = 0, nobj = 0, nstr = 0, nprop = 0; 146 | unsigned int genv = 0, gfun = 0, gobj = 0, gstr = 0, gprop = 0; 147 | int mark; 148 | int i; 149 | 150 | mark = J->gcmark = J->gcmark == 1 ? 2 : 1; 151 | 152 | /* Add initial roots. */ 153 | 154 | jsG_markobject(J, mark, J->Object_prototype); 155 | jsG_markobject(J, mark, J->Array_prototype); 156 | jsG_markobject(J, mark, J->Function_prototype); 157 | jsG_markobject(J, mark, J->Boolean_prototype); 158 | jsG_markobject(J, mark, J->Number_prototype); 159 | jsG_markobject(J, mark, J->String_prototype); 160 | jsG_markobject(J, mark, J->RegExp_prototype); 161 | jsG_markobject(J, mark, J->Date_prototype); 162 | 163 | jsG_markobject(J, mark, J->Error_prototype); 164 | jsG_markobject(J, mark, J->EvalError_prototype); 165 | jsG_markobject(J, mark, J->RangeError_prototype); 166 | jsG_markobject(J, mark, J->ReferenceError_prototype); 167 | jsG_markobject(J, mark, J->SyntaxError_prototype); 168 | jsG_markobject(J, mark, J->TypeError_prototype); 169 | jsG_markobject(J, mark, J->URIError_prototype); 170 | 171 | jsG_markobject(J, mark, J->R); 172 | jsG_markobject(J, mark, J->G); 173 | 174 | jsG_markstack(J, mark); 175 | 176 | jsG_markenvironment(J, mark, J->E); 177 | jsG_markenvironment(J, mark, J->GE); 178 | for (i = 0; i < J->envtop; ++i) 179 | jsG_markenvironment(J, mark, J->envstack[i]); 180 | 181 | /* Scan objects until none remain. */ 182 | 183 | while ((obj = J->gcroot) != NULL) { 184 | J->gcroot = obj->gcroot; 185 | obj->gcroot = NULL; 186 | jsG_scanobject(J, mark, obj); 187 | } 188 | 189 | /* Free everything not marked. */ 190 | 191 | prevnextenv = &J->gcenv; 192 | for (env = J->gcenv; env; env = nextenv) { 193 | nextenv = env->gcnext; 194 | if (env->gcmark != mark) { 195 | *prevnextenv = nextenv; 196 | jsG_freeenvironment(J, env); 197 | ++genv; 198 | } else { 199 | prevnextenv = &env->gcnext; 200 | } 201 | ++nenv; 202 | } 203 | 204 | prevnextfun = &J->gcfun; 205 | for (fun = J->gcfun; fun; fun = nextfun) { 206 | nextfun = fun->gcnext; 207 | if (fun->gcmark != mark) { 208 | *prevnextfun = nextfun; 209 | jsG_freefunction(J, fun); 210 | ++gfun; 211 | } else { 212 | prevnextfun = &fun->gcnext; 213 | } 214 | ++nfun; 215 | } 216 | 217 | prevnextobj = &J->gcobj; 218 | for (obj = J->gcobj; obj; obj = nextobj) { 219 | nprop += obj->count; 220 | nextobj = obj->gcnext; 221 | if (obj->gcmark != mark) { 222 | gprop += obj->count; 223 | *prevnextobj = nextobj; 224 | jsG_freeobject(J, obj); 225 | ++gobj; 226 | } else { 227 | prevnextobj = &obj->gcnext; 228 | } 229 | ++nobj; 230 | } 231 | 232 | prevnextstr = &J->gcstr; 233 | for (str = J->gcstr; str; str = nextstr) { 234 | nextstr = str->gcnext; 235 | if (str->gcmark != mark) { 236 | *prevnextstr = nextstr; 237 | js_free(J, str); 238 | ++gstr; 239 | } else { 240 | prevnextstr = &str->gcnext; 241 | } 242 | ++nstr; 243 | } 244 | 245 | unsigned int ntot = nenv + nfun + nobj + nstr + nprop; 246 | unsigned int gtot = genv + gfun + gobj + gstr + gprop; 247 | unsigned int remaining = ntot - gtot; 248 | 249 | J->gccounter = remaining; 250 | J->gcthresh = remaining * JS_GCFACTOR; 251 | 252 | if (report) { 253 | char buf[256]; 254 | snprintf(buf, sizeof buf, "garbage collected (%d%%): %d/%d envs, %d/%d funs, %d/%d objs, %d/%d props, %d/%d strs", 255 | 100*gtot/ntot, genv, nenv, gfun, nfun, gobj, nobj, gprop, nprop, gstr, nstr); 256 | js_report(J, buf); 257 | } 258 | } 259 | 260 | void js_freestate(js_State *J) 261 | { 262 | js_Function *fun, *nextfun; 263 | js_Object *obj, *nextobj; 264 | js_Environment *env, *nextenv; 265 | js_String *str, *nextstr; 266 | 267 | if (!J) 268 | return; 269 | 270 | for (env = J->gcenv; env; env = nextenv) 271 | nextenv = env->gcnext, jsG_freeenvironment(J, env); 272 | for (fun = J->gcfun; fun; fun = nextfun) 273 | nextfun = fun->gcnext, jsG_freefunction(J, fun); 274 | for (obj = J->gcobj; obj; obj = nextobj) 275 | nextobj = obj->gcnext, jsG_freeobject(J, obj); 276 | for (str = J->gcstr; str; str = nextstr) 277 | nextstr = str->gcnext, js_free(J, str); 278 | 279 | jsS_freestrings(J); 280 | 281 | js_free(J, J->lexbuf.text); 282 | J->alloc(J->actx, J->stack, 0); 283 | J->alloc(J->actx, J, 0); 284 | } 285 | -------------------------------------------------------------------------------- /jsintern.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | /* Dynamically grown string buffer */ 4 | 5 | void js_putc(js_State *J, js_Buffer **sbp, int c) 6 | { 7 | js_Buffer *sb = *sbp; 8 | if (!sb) { 9 | sb = js_malloc(J, sizeof *sb); 10 | sb->n = 0; 11 | sb->m = sizeof sb->s; 12 | *sbp = sb; 13 | } else if (sb->n == sb->m) { 14 | sb = js_realloc(J, sb, (sb->m *= 2) + soffsetof(js_Buffer, s)); 15 | *sbp = sb; 16 | } 17 | sb->s[sb->n++] = c; 18 | } 19 | 20 | void js_puts(js_State *J, js_Buffer **sb, const char *s) 21 | { 22 | while (*s) 23 | js_putc(J, sb, *s++); 24 | } 25 | 26 | void js_putm(js_State *J, js_Buffer **sb, const char *s, const char *e) 27 | { 28 | while (s < e) 29 | js_putc(J, sb, *s++); 30 | } 31 | 32 | /* Use an AA-tree to quickly look up interned strings. */ 33 | 34 | struct js_StringNode 35 | { 36 | js_StringNode *left, *right; 37 | int level; 38 | char string[1]; 39 | }; 40 | 41 | static js_StringNode jsS_sentinel = { &jsS_sentinel, &jsS_sentinel, 0, ""}; 42 | 43 | static js_StringNode *jsS_newstringnode(js_State *J, const char *string, const char **result) 44 | { 45 | size_t n = strlen(string); 46 | if (n > JS_STRLIMIT) 47 | js_rangeerror(J, "invalid string length"); 48 | js_StringNode *node = js_malloc(J, soffsetof(js_StringNode, string) + n + 1); 49 | node->left = node->right = &jsS_sentinel; 50 | node->level = 1; 51 | memcpy(node->string, string, n + 1); 52 | return *result = node->string, node; 53 | } 54 | 55 | static js_StringNode *jsS_skew(js_StringNode *node) 56 | { 57 | if (node->left->level == node->level) { 58 | js_StringNode *temp = node; 59 | node = node->left; 60 | temp->left = node->right; 61 | node->right = temp; 62 | } 63 | return node; 64 | } 65 | 66 | static js_StringNode *jsS_split(js_StringNode *node) 67 | { 68 | if (node->right->right->level == node->level) { 69 | js_StringNode *temp = node; 70 | node = node->right; 71 | temp->right = node->left; 72 | node->left = temp; 73 | ++node->level; 74 | } 75 | return node; 76 | } 77 | 78 | static js_StringNode *jsS_insert(js_State *J, js_StringNode *node, const char *string, const char **result) 79 | { 80 | if (node != &jsS_sentinel) { 81 | int c = strcmp(string, node->string); 82 | if (c < 0) 83 | node->left = jsS_insert(J, node->left, string, result); 84 | else if (c > 0) 85 | node->right = jsS_insert(J, node->right, string, result); 86 | else 87 | return *result = node->string, node; 88 | node = jsS_skew(node); 89 | node = jsS_split(node); 90 | return node; 91 | } 92 | return jsS_newstringnode(J, string, result); 93 | } 94 | 95 | static void dumpstringnode(js_StringNode *node, int level) 96 | { 97 | int i; 98 | if (node->left != &jsS_sentinel) 99 | dumpstringnode(node->left, level + 1); 100 | printf("%d: ", node->level); 101 | for (i = 0; i < level; ++i) 102 | putchar('\t'); 103 | printf("'%s'\n", node->string); 104 | if (node->right != &jsS_sentinel) 105 | dumpstringnode(node->right, level + 1); 106 | } 107 | 108 | void jsS_dumpstrings(js_State *J) 109 | { 110 | js_StringNode *root = J->strings; 111 | printf("interned strings {\n"); 112 | if (root && root != &jsS_sentinel) 113 | dumpstringnode(root, 1); 114 | printf("}\n"); 115 | } 116 | 117 | static void jsS_freestringnode(js_State *J, js_StringNode *node) 118 | { 119 | if (node->left != &jsS_sentinel) jsS_freestringnode(J, node->left); 120 | if (node->right != &jsS_sentinel) jsS_freestringnode(J, node->right); 121 | js_free(J, node); 122 | } 123 | 124 | void jsS_freestrings(js_State *J) 125 | { 126 | if (J->strings && J->strings != &jsS_sentinel) 127 | jsS_freestringnode(J, J->strings); 128 | } 129 | 130 | const char *js_intern(js_State *J, const char *s) 131 | { 132 | const char *result; 133 | if (!J->strings) 134 | J->strings = &jsS_sentinel; 135 | J->strings = jsS_insert(J, J->strings, s, &result); 136 | return result; 137 | } 138 | -------------------------------------------------------------------------------- /jsmath.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | #if defined(_MSC_VER) && (_MSC_VER < 1700) /* VS2012 has stdint.h */ 4 | typedef unsigned int uint32_t; 5 | typedef unsigned __int64 uint64_t; 6 | #else 7 | #include 8 | #endif 9 | 10 | #include 11 | 12 | static double jsM_round(double x) 13 | { 14 | if (isnan(x)) return x; 15 | if (isinf(x)) return x; 16 | if (x == 0) return x; 17 | if (x > 0 && x < 0.5) return 0; 18 | if (x < 0 && x >= -0.5) return -0; 19 | return floor(x + 0.5); 20 | } 21 | 22 | static void Math_abs(js_State *J) 23 | { 24 | js_pushnumber(J, fabs(js_tonumber(J, 1))); 25 | } 26 | 27 | static void Math_acos(js_State *J) 28 | { 29 | js_pushnumber(J, acos(js_tonumber(J, 1))); 30 | } 31 | 32 | static void Math_asin(js_State *J) 33 | { 34 | js_pushnumber(J, asin(js_tonumber(J, 1))); 35 | } 36 | 37 | static void Math_atan(js_State *J) 38 | { 39 | js_pushnumber(J, atan(js_tonumber(J, 1))); 40 | } 41 | 42 | static void Math_atan2(js_State *J) 43 | { 44 | double y = js_tonumber(J, 1); 45 | double x = js_tonumber(J, 2); 46 | js_pushnumber(J, atan2(y, x)); 47 | } 48 | 49 | static void Math_ceil(js_State *J) 50 | { 51 | js_pushnumber(J, ceil(js_tonumber(J, 1))); 52 | } 53 | 54 | static void Math_cos(js_State *J) 55 | { 56 | js_pushnumber(J, cos(js_tonumber(J, 1))); 57 | } 58 | 59 | static void Math_exp(js_State *J) 60 | { 61 | js_pushnumber(J, exp(js_tonumber(J, 1))); 62 | } 63 | 64 | static void Math_floor(js_State *J) 65 | { 66 | js_pushnumber(J, floor(js_tonumber(J, 1))); 67 | } 68 | 69 | static void Math_log(js_State *J) 70 | { 71 | js_pushnumber(J, log(js_tonumber(J, 1))); 72 | } 73 | 74 | static void Math_pow(js_State *J) 75 | { 76 | double x = js_tonumber(J, 1); 77 | double y = js_tonumber(J, 2); 78 | if (!isfinite(y) && fabs(x) == 1) 79 | js_pushnumber(J, NAN); 80 | else 81 | js_pushnumber(J, pow(x,y)); 82 | } 83 | 84 | static void Math_random(js_State *J) 85 | { 86 | /* Lehmer generator with a=48271 and m=2^31-1 */ 87 | /* Park & Miller (1988). Random Number Generators: Good ones are hard to find. */ 88 | J->seed = (uint64_t) J->seed * 48271 % 0x7fffffff; 89 | js_pushnumber(J, (double) J->seed / 0x7fffffff); 90 | } 91 | 92 | static void Math_init_random(js_State *J) 93 | { 94 | /* Pick initial seed by scrambling current time with Xorshift. */ 95 | /* Marsaglia (2003). Xorshift RNGs. */ 96 | J->seed = time(0) + 123; 97 | J->seed ^= J->seed << 13; 98 | J->seed ^= J->seed >> 17; 99 | J->seed ^= J->seed << 5; 100 | J->seed %= 0x7fffffff; 101 | } 102 | 103 | static void Math_round(js_State *J) 104 | { 105 | double x = js_tonumber(J, 1); 106 | js_pushnumber(J, jsM_round(x)); 107 | } 108 | 109 | static void Math_sin(js_State *J) 110 | { 111 | js_pushnumber(J, sin(js_tonumber(J, 1))); 112 | } 113 | 114 | static void Math_sqrt(js_State *J) 115 | { 116 | js_pushnumber(J, sqrt(js_tonumber(J, 1))); 117 | } 118 | 119 | static void Math_tan(js_State *J) 120 | { 121 | js_pushnumber(J, tan(js_tonumber(J, 1))); 122 | } 123 | 124 | static void Math_max(js_State *J) 125 | { 126 | int i, n = js_gettop(J); 127 | double x = -INFINITY; 128 | for (i = 1; i < n; ++i) { 129 | double y = js_tonumber(J, i); 130 | if (isnan(y)) { 131 | x = y; 132 | break; 133 | } 134 | if (signbit(x) == signbit(y)) 135 | x = x > y ? x : y; 136 | else if (signbit(x)) 137 | x = y; 138 | } 139 | js_pushnumber(J, x); 140 | } 141 | 142 | static void Math_min(js_State *J) 143 | { 144 | int i, n = js_gettop(J); 145 | double x = INFINITY; 146 | for (i = 1; i < n; ++i) { 147 | double y = js_tonumber(J, i); 148 | if (isnan(y)) { 149 | x = y; 150 | break; 151 | } 152 | if (signbit(x) == signbit(y)) 153 | x = x < y ? x : y; 154 | else if (signbit(y)) 155 | x = y; 156 | } 157 | js_pushnumber(J, x); 158 | } 159 | 160 | void jsB_initmath(js_State *J) 161 | { 162 | Math_init_random(J); 163 | js_pushobject(J, jsV_newobject(J, JS_CMATH, J->Object_prototype)); 164 | { 165 | jsB_propn(J, "E", 2.7182818284590452354); 166 | jsB_propn(J, "LN10", 2.302585092994046); 167 | jsB_propn(J, "LN2", 0.6931471805599453); 168 | jsB_propn(J, "LOG2E", 1.4426950408889634); 169 | jsB_propn(J, "LOG10E", 0.4342944819032518); 170 | jsB_propn(J, "PI", 3.1415926535897932); 171 | jsB_propn(J, "SQRT1_2", 0.7071067811865476); 172 | jsB_propn(J, "SQRT2", 1.4142135623730951); 173 | 174 | jsB_propf(J, "Math.abs", Math_abs, 1); 175 | jsB_propf(J, "Math.acos", Math_acos, 1); 176 | jsB_propf(J, "Math.asin", Math_asin, 1); 177 | jsB_propf(J, "Math.atan", Math_atan, 1); 178 | jsB_propf(J, "Math.atan2", Math_atan2, 2); 179 | jsB_propf(J, "Math.ceil", Math_ceil, 1); 180 | jsB_propf(J, "Math.cos", Math_cos, 1); 181 | jsB_propf(J, "Math.exp", Math_exp, 1); 182 | jsB_propf(J, "Math.floor", Math_floor, 1); 183 | jsB_propf(J, "Math.log", Math_log, 1); 184 | jsB_propf(J, "Math.max", Math_max, 0); /* 2 */ 185 | jsB_propf(J, "Math.min", Math_min, 0); /* 2 */ 186 | jsB_propf(J, "Math.pow", Math_pow, 2); 187 | jsB_propf(J, "Math.random", Math_random, 0); 188 | jsB_propf(J, "Math.round", Math_round, 1); 189 | jsB_propf(J, "Math.sin", Math_sin, 1); 190 | jsB_propf(J, "Math.sqrt", Math_sqrt, 1); 191 | jsB_propf(J, "Math.tan", Math_tan, 1); 192 | } 193 | js_defglobal(J, "Math", JS_DONTENUM); 194 | } 195 | -------------------------------------------------------------------------------- /jsnumber.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | #if defined(_MSC_VER) && (_MSC_VER < 1700) /* VS2012 has stdint.h */ 4 | typedef unsigned __int64 uint64_t; 5 | #else 6 | #include 7 | #endif 8 | 9 | static void jsB_new_Number(js_State *J) 10 | { 11 | js_newnumber(J, js_gettop(J) > 1 ? js_tonumber(J, 1) : 0); 12 | } 13 | 14 | static void jsB_Number(js_State *J) 15 | { 16 | js_pushnumber(J, js_gettop(J) > 1 ? js_tonumber(J, 1) : 0); 17 | } 18 | 19 | static void Np_valueOf(js_State *J) 20 | { 21 | js_Object *self = js_toobject(J, 0); 22 | if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); 23 | js_pushnumber(J, self->u.number); 24 | } 25 | 26 | static void Np_toString(js_State *J) 27 | { 28 | char buf[100]; 29 | js_Object *self = js_toobject(J, 0); 30 | int radix = js_isundefined(J, 1) ? 10 : js_tointeger(J, 1); 31 | double x = 0; 32 | if (self->type != JS_CNUMBER) 33 | js_typeerror(J, "not a number"); 34 | x = self->u.number; 35 | if (radix == 10) { 36 | js_pushstring(J, jsV_numbertostring(J, buf, x)); 37 | return; 38 | } 39 | if (radix < 2 || radix > 36) 40 | js_rangeerror(J, "invalid radix"); 41 | 42 | /* lame number to string conversion for any radix from 2 to 36 */ 43 | { 44 | static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 45 | double number = x; 46 | int sign = x < 0; 47 | js_Buffer *sb = NULL; 48 | uint64_t u, limit = ((uint64_t)1<<52); 49 | 50 | int ndigits, exp, point; 51 | 52 | if (number == 0) { js_pushstring(J, "0"); return; } 53 | if (isnan(number)) { js_pushstring(J, "NaN"); return; } 54 | if (isinf(number)) { js_pushstring(J, sign ? "-Infinity" : "Infinity"); return; } 55 | 56 | if (sign) 57 | number = -number; 58 | 59 | /* fit as many digits as we want in an int */ 60 | exp = 0; 61 | while (number * pow(radix, exp) > limit) 62 | --exp; 63 | while (number * pow(radix, exp+1) < limit) 64 | ++exp; 65 | u = number * pow(radix, exp) + 0.5; 66 | 67 | /* trim trailing zeros */ 68 | while (u > 0 && (u % radix) == 0) { 69 | u /= radix; 70 | --exp; 71 | } 72 | 73 | /* serialize digits */ 74 | ndigits = 0; 75 | while (u > 0) { 76 | buf[ndigits++] = digits[u % radix]; 77 | u /= radix; 78 | } 79 | point = ndigits - exp; 80 | 81 | if (js_try(J)) { 82 | js_free(J, sb); 83 | js_throw(J); 84 | } 85 | 86 | if (sign) 87 | js_putc(J, &sb, '-'); 88 | 89 | if (point <= 0) { 90 | js_putc(J, &sb, '0'); 91 | js_putc(J, &sb, '.'); 92 | while (point++ < 0) 93 | js_putc(J, &sb, '0'); 94 | while (ndigits-- > 0) 95 | js_putc(J, &sb, buf[ndigits]); 96 | } else { 97 | while (ndigits-- > 0) { 98 | js_putc(J, &sb, buf[ndigits]); 99 | if (--point == 0 && ndigits > 0) 100 | js_putc(J, &sb, '.'); 101 | } 102 | while (point-- > 0) 103 | js_putc(J, &sb, '0'); 104 | } 105 | 106 | js_putc(J, &sb, 0); 107 | js_pushstring(J, sb->s); 108 | 109 | js_endtry(J); 110 | js_free(J, sb); 111 | } 112 | } 113 | 114 | /* Customized ToString() on a number */ 115 | static void numtostr(js_State *J, const char *fmt, int w, double n) 116 | { 117 | /* buf needs to fit printf("%.20f", 1e20) */ 118 | char buf[50], *e; 119 | sprintf(buf, fmt, w, n); 120 | e = strchr(buf, 'e'); 121 | if (e) { 122 | int exp = atoi(e+1); 123 | sprintf(e, "e%+d", exp); 124 | } 125 | js_pushstring(J, buf); 126 | } 127 | 128 | static void Np_toFixed(js_State *J) 129 | { 130 | js_Object *self = js_toobject(J, 0); 131 | int width = js_tointeger(J, 1); 132 | char buf[32]; 133 | double x; 134 | if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); 135 | if (width < 0) js_rangeerror(J, "precision %d out of range", width); 136 | if (width > 20) js_rangeerror(J, "precision %d out of range", width); 137 | x = self->u.number; 138 | if (isnan(x) || isinf(x) || x <= -1e21 || x >= 1e21) 139 | js_pushstring(J, jsV_numbertostring(J, buf, x)); 140 | else 141 | numtostr(J, "%.*f", width, x); 142 | } 143 | 144 | static void Np_toExponential(js_State *J) 145 | { 146 | js_Object *self = js_toobject(J, 0); 147 | int width = js_tointeger(J, 1); 148 | char buf[32]; 149 | double x; 150 | if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); 151 | if (width < 0) js_rangeerror(J, "precision %d out of range", width); 152 | if (width > 20) js_rangeerror(J, "precision %d out of range", width); 153 | x = self->u.number; 154 | if (isnan(x) || isinf(x)) 155 | js_pushstring(J, jsV_numbertostring(J, buf, x)); 156 | else 157 | numtostr(J, "%.*e", width, x); 158 | } 159 | 160 | static void Np_toPrecision(js_State *J) 161 | { 162 | js_Object *self = js_toobject(J, 0); 163 | int width = js_tointeger(J, 1); 164 | char buf[32]; 165 | double x; 166 | if (self->type != JS_CNUMBER) js_typeerror(J, "not a number"); 167 | if (width < 1) js_rangeerror(J, "precision %d out of range", width); 168 | if (width > 21) js_rangeerror(J, "precision %d out of range", width); 169 | x = self->u.number; 170 | if (isnan(x) || isinf(x)) 171 | js_pushstring(J, jsV_numbertostring(J, buf, x)); 172 | else 173 | numtostr(J, "%.*g", width, x); 174 | } 175 | 176 | void jsB_initnumber(js_State *J) 177 | { 178 | J->Number_prototype->u.number = 0; 179 | 180 | js_pushobject(J, J->Number_prototype); 181 | { 182 | jsB_propf(J, "Number.prototype.valueOf", Np_valueOf, 0); 183 | jsB_propf(J, "Number.prototype.toString", Np_toString, 1); 184 | jsB_propf(J, "Number.prototype.toLocaleString", Np_toString, 0); 185 | jsB_propf(J, "Number.prototype.toFixed", Np_toFixed, 1); 186 | jsB_propf(J, "Number.prototype.toExponential", Np_toExponential, 1); 187 | jsB_propf(J, "Number.prototype.toPrecision", Np_toPrecision, 1); 188 | } 189 | js_newcconstructor(J, jsB_Number, jsB_new_Number, "Number", 0); /* 1 */ 190 | { 191 | jsB_propn(J, "MAX_VALUE", 1.7976931348623157e+308); 192 | jsB_propn(J, "MIN_VALUE", 5e-324); 193 | jsB_propn(J, "NaN", NAN); 194 | jsB_propn(J, "NEGATIVE_INFINITY", -INFINITY); 195 | jsB_propn(J, "POSITIVE_INFINITY", INFINITY); 196 | } 197 | js_defglobal(J, "Number", JS_DONTENUM); 198 | } 199 | -------------------------------------------------------------------------------- /jsobject.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | static void jsB_new_Object(js_State *J) 4 | { 5 | if (js_isundefined(J, 1) || js_isnull(J, 1)) 6 | js_newobject(J); 7 | else 8 | js_pushobject(J, js_toobject(J, 1)); 9 | } 10 | 11 | static void jsB_Object(js_State *J) 12 | { 13 | if (js_isundefined(J, 1) || js_isnull(J, 1)) 14 | js_newobject(J); 15 | else 16 | js_pushobject(J, js_toobject(J, 1)); 17 | } 18 | 19 | static void Op_toString(js_State *J) 20 | { 21 | if (js_isundefined(J, 0)) 22 | js_pushliteral(J, "[object Undefined]"); 23 | else if (js_isnull(J, 0)) 24 | js_pushliteral(J, "[object Null]"); 25 | else { 26 | js_Object *self = js_toobject(J, 0); 27 | switch (self->type) { 28 | case JS_COBJECT: js_pushliteral(J, "[object Object]"); break; 29 | case JS_CARRAY: js_pushliteral(J, "[object Array]"); break; 30 | case JS_CFUNCTION: js_pushliteral(J, "[object Function]"); break; 31 | case JS_CSCRIPT: js_pushliteral(J, "[object Function]"); break; 32 | case JS_CCFUNCTION: js_pushliteral(J, "[object Function]"); break; 33 | case JS_CERROR: js_pushliteral(J, "[object Error]"); break; 34 | case JS_CBOOLEAN: js_pushliteral(J, "[object Boolean]"); break; 35 | case JS_CNUMBER: js_pushliteral(J, "[object Number]"); break; 36 | case JS_CSTRING: js_pushliteral(J, "[object String]"); break; 37 | case JS_CREGEXP: js_pushliteral(J, "[object RegExp]"); break; 38 | case JS_CDATE: js_pushliteral(J, "[object Date]"); break; 39 | case JS_CMATH: js_pushliteral(J, "[object Math]"); break; 40 | case JS_CJSON: js_pushliteral(J, "[object JSON]"); break; 41 | case JS_CARGUMENTS: js_pushliteral(J, "[object Arguments]"); break; 42 | case JS_CITERATOR: js_pushliteral(J, "[object Iterator]"); break; 43 | case JS_CUSERDATA: 44 | js_pushliteral(J, "[object "); 45 | js_pushliteral(J, self->u.user.tag); 46 | js_concat(J); 47 | js_pushliteral(J, "]"); 48 | js_concat(J); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | static void Op_valueOf(js_State *J) 55 | { 56 | js_copy(J, 0); 57 | } 58 | 59 | static void Op_hasOwnProperty(js_State *J) 60 | { 61 | js_Object *self = js_toobject(J, 0); 62 | const char *name = js_tostring(J, 1); 63 | js_Property *ref; 64 | int k; 65 | 66 | if (self->type == JS_CSTRING) { 67 | if (js_isarrayindex(J, name, &k) && k >= 0 && k < self->u.s.length) { 68 | js_pushboolean(J, 1); 69 | return; 70 | } 71 | } 72 | 73 | if (self->type == JS_CARRAY && self->u.a.simple) { 74 | if (js_isarrayindex(J, name, &k) && k >= 0 && k < self->u.a.flat_length) { 75 | js_pushboolean(J, 1); 76 | return; 77 | } 78 | } 79 | 80 | ref = jsV_getownproperty(J, self, name); 81 | js_pushboolean(J, ref != NULL); 82 | } 83 | 84 | static void Op_isPrototypeOf(js_State *J) 85 | { 86 | js_Object *self = js_toobject(J, 0); 87 | if (js_isobject(J, 1)) { 88 | js_Object *V = js_toobject(J, 1); 89 | do { 90 | V = V->prototype; 91 | if (V == self) { 92 | js_pushboolean(J, 1); 93 | return; 94 | } 95 | } while (V); 96 | } 97 | js_pushboolean(J, 0); 98 | } 99 | 100 | static void Op_propertyIsEnumerable(js_State *J) 101 | { 102 | js_Object *self = js_toobject(J, 0); 103 | const char *name = js_tostring(J, 1); 104 | js_Property *ref = jsV_getownproperty(J, self, name); 105 | js_pushboolean(J, ref && !(ref->atts & JS_DONTENUM)); 106 | } 107 | 108 | static void O_getPrototypeOf(js_State *J) 109 | { 110 | js_Object *obj; 111 | if (!js_isobject(J, 1)) 112 | js_typeerror(J, "not an object"); 113 | obj = js_toobject(J, 1); 114 | if (obj->prototype) 115 | js_pushobject(J, obj->prototype); 116 | else 117 | js_pushnull(J); 118 | } 119 | 120 | static void O_getOwnPropertyDescriptor(js_State *J) 121 | { 122 | js_Object *obj; 123 | js_Property *ref; 124 | if (!js_isobject(J, 1)) 125 | js_typeerror(J, "not an object"); 126 | obj = js_toobject(J, 1); 127 | ref = jsV_getproperty(J, obj, js_tostring(J, 2)); 128 | if (!ref) { 129 | /* TODO: builtin properties (string and array index and length, regexp flags, etc) */ 130 | js_pushundefined(J); 131 | } else { 132 | js_newobject(J); 133 | if (!ref->getter && !ref->setter) { 134 | js_pushvalue(J, ref->value); 135 | js_defproperty(J, -2, "value", 0); 136 | js_pushboolean(J, !(ref->atts & JS_READONLY)); 137 | js_defproperty(J, -2, "writable", 0); 138 | } else { 139 | if (ref->getter) 140 | js_pushobject(J, ref->getter); 141 | else 142 | js_pushundefined(J); 143 | js_defproperty(J, -2, "get", 0); 144 | if (ref->setter) 145 | js_pushobject(J, ref->setter); 146 | else 147 | js_pushundefined(J); 148 | js_defproperty(J, -2, "set", 0); 149 | } 150 | js_pushboolean(J, !(ref->atts & JS_DONTENUM)); 151 | js_defproperty(J, -2, "enumerable", 0); 152 | js_pushboolean(J, !(ref->atts & JS_DONTCONF)); 153 | js_defproperty(J, -2, "configurable", 0); 154 | } 155 | } 156 | 157 | static int O_getOwnPropertyNames_walk(js_State *J, js_Property *ref, int i) 158 | { 159 | if (ref->left->level) 160 | i = O_getOwnPropertyNames_walk(J, ref->left, i); 161 | js_pushstring(J, ref->name); 162 | js_setindex(J, -2, i++); 163 | if (ref->right->level) 164 | i = O_getOwnPropertyNames_walk(J, ref->right, i); 165 | return i; 166 | } 167 | 168 | static void O_getOwnPropertyNames(js_State *J) 169 | { 170 | js_Object *obj; 171 | char name[32]; 172 | int k; 173 | int i; 174 | 175 | if (!js_isobject(J, 1)) 176 | js_typeerror(J, "not an object"); 177 | obj = js_toobject(J, 1); 178 | 179 | js_newarray(J); 180 | 181 | if (obj->properties->level) 182 | i = O_getOwnPropertyNames_walk(J, obj->properties, 0); 183 | else 184 | i = 0; 185 | 186 | if (obj->type == JS_CARRAY) { 187 | js_pushliteral(J, "length"); 188 | js_setindex(J, -2, i++); 189 | if (obj->u.a.simple) { 190 | for (k = 0; k < obj->u.a.flat_length; ++k) { 191 | js_itoa(name, k); 192 | js_pushstring(J, name); 193 | js_setindex(J, -2, i++); 194 | } 195 | } 196 | } 197 | 198 | if (obj->type == JS_CSTRING) { 199 | js_pushliteral(J, "length"); 200 | js_setindex(J, -2, i++); 201 | for (k = 0; k < obj->u.s.length; ++k) { 202 | js_itoa(name, k); 203 | js_pushstring(J, name); 204 | js_setindex(J, -2, i++); 205 | } 206 | } 207 | 208 | if (obj->type == JS_CREGEXP) { 209 | js_pushliteral(J, "source"); 210 | js_setindex(J, -2, i++); 211 | js_pushliteral(J, "global"); 212 | js_setindex(J, -2, i++); 213 | js_pushliteral(J, "ignoreCase"); 214 | js_setindex(J, -2, i++); 215 | js_pushliteral(J, "multiline"); 216 | js_setindex(J, -2, i++); 217 | js_pushliteral(J, "lastIndex"); 218 | js_setindex(J, -2, i++); 219 | } 220 | } 221 | 222 | static void ToPropertyDescriptor(js_State *J, js_Object *obj, const char *name, js_Object *desc) 223 | { 224 | int haswritable = 0; 225 | int hasvalue = 0; 226 | int enumerable = 0; 227 | int configurable = 0; 228 | int writable = 0; 229 | int atts = 0; 230 | 231 | js_pushobject(J, obj); 232 | js_pushobject(J, desc); 233 | 234 | if (js_hasproperty(J, -1, "writable")) { 235 | haswritable = 1; 236 | writable = js_toboolean(J, -1); 237 | js_pop(J, 1); 238 | } 239 | if (js_hasproperty(J, -1, "enumerable")) { 240 | enumerable = js_toboolean(J, -1); 241 | js_pop(J, 1); 242 | } 243 | if (js_hasproperty(J, -1, "configurable")) { 244 | configurable = js_toboolean(J, -1); 245 | js_pop(J, 1); 246 | } 247 | if (js_hasproperty(J, -1, "value")) { 248 | hasvalue = 1; 249 | js_defproperty(J, -3, name, 0); 250 | } 251 | 252 | if (!writable) atts |= JS_READONLY; 253 | if (!enumerable) atts |= JS_DONTENUM; 254 | if (!configurable) atts |= JS_DONTCONF; 255 | 256 | if (js_hasproperty(J, -1, "get")) { 257 | if (haswritable || hasvalue) 258 | js_typeerror(J, "value/writable and get/set attributes are exclusive"); 259 | } else { 260 | js_pushundefined(J); 261 | } 262 | 263 | if (js_hasproperty(J, -2, "set")) { 264 | if (haswritable || hasvalue) 265 | js_typeerror(J, "value/writable and get/set attributes are exclusive"); 266 | } else { 267 | js_pushundefined(J); 268 | } 269 | 270 | js_defaccessor(J, -4, name, atts); 271 | 272 | js_pop(J, 2); 273 | } 274 | 275 | static void O_defineProperty(js_State *J) 276 | { 277 | if (!js_isobject(J, 1)) js_typeerror(J, "not an object"); 278 | if (!js_isobject(J, 3)) js_typeerror(J, "not an object"); 279 | ToPropertyDescriptor(J, js_toobject(J, 1), js_tostring(J, 2), js_toobject(J, 3)); 280 | js_copy(J, 1); 281 | } 282 | 283 | static void O_defineProperties_walk(js_State *J, js_Property *ref) 284 | { 285 | if (ref->left->level) 286 | O_defineProperties_walk(J, ref->left); 287 | if (!(ref->atts & JS_DONTENUM)) { 288 | js_pushvalue(J, ref->value); 289 | ToPropertyDescriptor(J, js_toobject(J, 1), ref->name, js_toobject(J, -1)); 290 | js_pop(J, 1); 291 | } 292 | if (ref->right->level) 293 | O_defineProperties_walk(J, ref->right); 294 | } 295 | 296 | static void O_defineProperties(js_State *J) 297 | { 298 | js_Object *props; 299 | 300 | if (!js_isobject(J, 1)) js_typeerror(J, "not an object"); 301 | if (!js_isobject(J, 2)) js_typeerror(J, "not an object"); 302 | 303 | props = js_toobject(J, 2); 304 | if (props->properties->level) 305 | O_defineProperties_walk(J, props->properties); 306 | 307 | js_copy(J, 1); 308 | } 309 | 310 | static void O_create_walk(js_State *J, js_Object *obj, js_Property *ref) 311 | { 312 | if (ref->left->level) 313 | O_create_walk(J, obj, ref->left); 314 | if (!(ref->atts & JS_DONTENUM)) { 315 | if (ref->value.t.type != JS_TOBJECT) 316 | js_typeerror(J, "not an object"); 317 | ToPropertyDescriptor(J, obj, ref->name, ref->value.u.object); 318 | } 319 | if (ref->right->level) 320 | O_create_walk(J, obj, ref->right); 321 | } 322 | 323 | static void O_create(js_State *J) 324 | { 325 | js_Object *obj; 326 | js_Object *proto; 327 | js_Object *props; 328 | 329 | if (js_isobject(J, 1)) 330 | proto = js_toobject(J, 1); 331 | else if (js_isnull(J, 1)) 332 | proto = NULL; 333 | else 334 | js_typeerror(J, "not an object or null"); 335 | 336 | obj = jsV_newobject(J, JS_COBJECT, proto); 337 | js_pushobject(J, obj); 338 | 339 | if (js_isdefined(J, 2)) { 340 | if (!js_isobject(J, 2)) 341 | js_typeerror(J, "not an object"); 342 | props = js_toobject(J, 2); 343 | if (props->properties->level) 344 | O_create_walk(J, obj, props->properties); 345 | } 346 | } 347 | 348 | static int O_keys_walk(js_State *J, js_Property *ref, int i) 349 | { 350 | if (ref->left->level) 351 | i = O_keys_walk(J, ref->left, i); 352 | if (!(ref->atts & JS_DONTENUM)) { 353 | js_pushstring(J, ref->name); 354 | js_setindex(J, -2, i++); 355 | } 356 | if (ref->right->level) 357 | i = O_keys_walk(J, ref->right, i); 358 | return i; 359 | } 360 | 361 | static void O_keys(js_State *J) 362 | { 363 | js_Object *obj; 364 | char name[32]; 365 | int i, k; 366 | 367 | if (!js_isobject(J, 1)) 368 | js_typeerror(J, "not an object"); 369 | obj = js_toobject(J, 1); 370 | 371 | js_newarray(J); 372 | 373 | if (obj->properties->level) 374 | i = O_keys_walk(J, obj->properties, 0); 375 | else 376 | i = 0; 377 | 378 | if (obj->type == JS_CSTRING) { 379 | for (k = 0; k < obj->u.s.length; ++k) { 380 | js_itoa(name, k); 381 | js_pushstring(J, name); 382 | js_setindex(J, -2, i++); 383 | } 384 | } 385 | 386 | if (obj->type == JS_CARRAY && obj->u.a.simple) { 387 | for (k = 0; k < obj->u.a.flat_length; ++k) { 388 | js_itoa(name, k); 389 | js_pushstring(J, name); 390 | js_setindex(J, -2, i++); 391 | } 392 | } 393 | } 394 | 395 | static void O_preventExtensions(js_State *J) 396 | { 397 | js_Object *obj; 398 | if (!js_isobject(J, 1)) 399 | js_typeerror(J, "not an object"); 400 | obj = js_toobject(J, 1); 401 | jsR_unflattenarray(J, obj); 402 | obj->extensible = 0; 403 | js_copy(J, 1); 404 | } 405 | 406 | static void O_isExtensible(js_State *J) 407 | { 408 | if (!js_isobject(J, 1)) 409 | js_typeerror(J, "not an object"); 410 | js_pushboolean(J, js_toobject(J, 1)->extensible); 411 | } 412 | 413 | static void O_seal_walk(js_State *J, js_Property *ref) 414 | { 415 | if (ref->left->level) 416 | O_seal_walk(J, ref->left); 417 | ref->atts |= JS_DONTCONF; 418 | if (ref->right->level) 419 | O_seal_walk(J, ref->right); 420 | } 421 | 422 | static void O_seal(js_State *J) 423 | { 424 | js_Object *obj; 425 | 426 | if (!js_isobject(J, 1)) 427 | js_typeerror(J, "not an object"); 428 | 429 | obj = js_toobject(J, 1); 430 | jsR_unflattenarray(J, obj); 431 | obj->extensible = 0; 432 | 433 | if (obj->properties->level) 434 | O_seal_walk(J, obj->properties); 435 | 436 | js_copy(J, 1); 437 | } 438 | 439 | static int O_isSealed_walk(js_State *J, js_Property *ref) 440 | { 441 | if (ref->left->level) 442 | if (!O_isSealed_walk(J, ref->left)) 443 | return 0; 444 | if (!(ref->atts & JS_DONTCONF)) 445 | return 0; 446 | if (ref->right->level) 447 | if (!O_isSealed_walk(J, ref->right)) 448 | return 0; 449 | return 1; 450 | } 451 | 452 | static void O_isSealed(js_State *J) 453 | { 454 | js_Object *obj; 455 | 456 | if (!js_isobject(J, 1)) 457 | js_typeerror(J, "not an object"); 458 | 459 | obj = js_toobject(J, 1); 460 | if (obj->extensible) { 461 | js_pushboolean(J, 0); 462 | return; 463 | } 464 | 465 | if (obj->properties->level) 466 | js_pushboolean(J, O_isSealed_walk(J, obj->properties)); 467 | else 468 | js_pushboolean(J, 1); 469 | } 470 | 471 | static void O_freeze_walk(js_State *J, js_Property *ref) 472 | { 473 | if (ref->left->level) 474 | O_freeze_walk(J, ref->left); 475 | ref->atts |= JS_READONLY | JS_DONTCONF; 476 | if (ref->right->level) 477 | O_freeze_walk(J, ref->right); 478 | } 479 | 480 | static void O_freeze(js_State *J) 481 | { 482 | js_Object *obj; 483 | 484 | if (!js_isobject(J, 1)) 485 | js_typeerror(J, "not an object"); 486 | 487 | obj = js_toobject(J, 1); 488 | jsR_unflattenarray(J, obj); 489 | obj->extensible = 0; 490 | 491 | if (obj->properties->level) 492 | O_freeze_walk(J, obj->properties); 493 | 494 | js_copy(J, 1); 495 | } 496 | 497 | static int O_isFrozen_walk(js_State *J, js_Property *ref) 498 | { 499 | if (ref->left->level) 500 | if (!O_isFrozen_walk(J, ref->left)) 501 | return 0; 502 | if (!(ref->atts & JS_READONLY)) 503 | return 0; 504 | if (!(ref->atts & JS_DONTCONF)) 505 | return 0; 506 | if (ref->right->level) 507 | if (!O_isFrozen_walk(J, ref->right)) 508 | return 0; 509 | return 1; 510 | } 511 | 512 | static void O_isFrozen(js_State *J) 513 | { 514 | js_Object *obj; 515 | 516 | if (!js_isobject(J, 1)) 517 | js_typeerror(J, "not an object"); 518 | 519 | obj = js_toobject(J, 1); 520 | 521 | if (obj->properties->level) { 522 | if (!O_isFrozen_walk(J, obj->properties)) { 523 | js_pushboolean(J, 0); 524 | return; 525 | } 526 | } 527 | 528 | js_pushboolean(J, !obj->extensible); 529 | } 530 | 531 | void jsB_initobject(js_State *J) 532 | { 533 | js_pushobject(J, J->Object_prototype); 534 | { 535 | jsB_propf(J, "Object.prototype.toString", Op_toString, 0); 536 | jsB_propf(J, "Object.prototype.toLocaleString", Op_toString, 0); 537 | jsB_propf(J, "Object.prototype.valueOf", Op_valueOf, 0); 538 | jsB_propf(J, "Object.prototype.hasOwnProperty", Op_hasOwnProperty, 1); 539 | jsB_propf(J, "Object.prototype.isPrototypeOf", Op_isPrototypeOf, 1); 540 | jsB_propf(J, "Object.prototype.propertyIsEnumerable", Op_propertyIsEnumerable, 1); 541 | } 542 | js_newcconstructor(J, jsB_Object, jsB_new_Object, "Object", 1); 543 | { 544 | /* ES5 */ 545 | jsB_propf(J, "Object.getPrototypeOf", O_getPrototypeOf, 1); 546 | jsB_propf(J, "Object.getOwnPropertyDescriptor", O_getOwnPropertyDescriptor, 2); 547 | jsB_propf(J, "Object.getOwnPropertyNames", O_getOwnPropertyNames, 1); 548 | jsB_propf(J, "Object.create", O_create, 2); 549 | jsB_propf(J, "Object.defineProperty", O_defineProperty, 3); 550 | jsB_propf(J, "Object.defineProperties", O_defineProperties, 2); 551 | jsB_propf(J, "Object.seal", O_seal, 1); 552 | jsB_propf(J, "Object.freeze", O_freeze, 1); 553 | jsB_propf(J, "Object.preventExtensions", O_preventExtensions, 1); 554 | jsB_propf(J, "Object.isSealed", O_isSealed, 1); 555 | jsB_propf(J, "Object.isFrozen", O_isFrozen, 1); 556 | jsB_propf(J, "Object.isExtensible", O_isExtensible, 1); 557 | jsB_propf(J, "Object.keys", O_keys, 1); 558 | } 559 | js_defglobal(J, "Object", JS_DONTENUM); 560 | } 561 | -------------------------------------------------------------------------------- /json.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | #include "utf.h" 3 | 4 | int js_isnumberobject(js_State *J, int idx) 5 | { 6 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CNUMBER; 7 | } 8 | 9 | int js_isstringobject(js_State *J, int idx) 10 | { 11 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CSTRING; 12 | } 13 | 14 | int js_isbooleanobject(js_State *J, int idx) 15 | { 16 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CBOOLEAN; 17 | } 18 | 19 | int js_isdateobject(js_State *J, int idx) 20 | { 21 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CDATE; 22 | } 23 | 24 | static void jsonnext(js_State *J) 25 | { 26 | J->lookahead = jsY_lexjson(J); 27 | } 28 | 29 | static int jsonaccept(js_State *J, int t) 30 | { 31 | if (J->lookahead == t) { 32 | jsonnext(J); 33 | return 1; 34 | } 35 | return 0; 36 | } 37 | 38 | static void jsonexpect(js_State *J, int t) 39 | { 40 | if (!jsonaccept(J, t)) 41 | js_syntaxerror(J, "JSON: unexpected token: %s (expected %s)", 42 | jsY_tokenstring(J->lookahead), jsY_tokenstring(t)); 43 | } 44 | 45 | static void jsonvalue(js_State *J) 46 | { 47 | int i; 48 | const char *name; 49 | 50 | switch (J->lookahead) { 51 | case TK_STRING: 52 | js_pushstring(J, J->text); 53 | jsonnext(J); 54 | break; 55 | 56 | case TK_NUMBER: 57 | js_pushnumber(J, J->number); 58 | jsonnext(J); 59 | break; 60 | 61 | case '{': 62 | js_newobject(J); 63 | jsonnext(J); 64 | if (jsonaccept(J, '}')) 65 | return; 66 | do { 67 | if (J->lookahead != TK_STRING) 68 | js_syntaxerror(J, "JSON: unexpected token: %s (expected string)", jsY_tokenstring(J->lookahead)); 69 | name = J->text; 70 | jsonnext(J); 71 | jsonexpect(J, ':'); 72 | jsonvalue(J); 73 | js_setproperty(J, -2, name); 74 | } while (jsonaccept(J, ',')); 75 | jsonexpect(J, '}'); 76 | break; 77 | 78 | case '[': 79 | js_newarray(J); 80 | jsonnext(J); 81 | i = 0; 82 | if (jsonaccept(J, ']')) 83 | return; 84 | do { 85 | jsonvalue(J); 86 | js_setindex(J, -2, i++); 87 | } while (jsonaccept(J, ',')); 88 | jsonexpect(J, ']'); 89 | break; 90 | 91 | case TK_TRUE: 92 | js_pushboolean(J, 1); 93 | jsonnext(J); 94 | break; 95 | 96 | case TK_FALSE: 97 | js_pushboolean(J, 0); 98 | jsonnext(J); 99 | break; 100 | 101 | case TK_NULL: 102 | js_pushnull(J); 103 | jsonnext(J); 104 | break; 105 | 106 | default: 107 | js_syntaxerror(J, "JSON: unexpected token: %s", jsY_tokenstring(J->lookahead)); 108 | } 109 | } 110 | 111 | static void jsonrevive(js_State *J, const char *name) 112 | { 113 | const char *key; 114 | char buf[32]; 115 | 116 | /* revive is in 2 */ 117 | /* holder is in -1 */ 118 | 119 | js_getproperty(J, -1, name); /* get value from holder */ 120 | 121 | if (js_isobject(J, -1)) { 122 | if (js_isarray(J, -1)) { 123 | int i = 0; 124 | int n = js_getlength(J, -1); 125 | for (i = 0; i < n; ++i) { 126 | jsonrevive(J, js_itoa(buf, i)); 127 | if (js_isundefined(J, -1)) { 128 | js_pop(J, 1); 129 | js_delproperty(J, -1, buf); 130 | } else { 131 | js_setproperty(J, -2, buf); 132 | } 133 | } 134 | } else { 135 | js_pushiterator(J, -1, 1); 136 | while ((key = js_nextiterator(J, -1))) { 137 | js_rot2(J); 138 | jsonrevive(J, key); 139 | if (js_isundefined(J, -1)) { 140 | js_pop(J, 1); 141 | js_delproperty(J, -1, key); 142 | } else { 143 | js_setproperty(J, -2, key); 144 | } 145 | js_rot2(J); 146 | } 147 | js_pop(J, 1); 148 | } 149 | } 150 | 151 | js_copy(J, 2); /* reviver function */ 152 | js_copy(J, -3); /* holder as this */ 153 | js_pushstring(J, name); /* name */ 154 | js_copy(J, -4); /* value */ 155 | js_call(J, 2); 156 | js_rot2pop1(J); /* pop old value, leave new value on stack */ 157 | } 158 | 159 | static void JSON_parse(js_State *J) 160 | { 161 | const char *source = js_tostring(J, 1); 162 | jsY_initlex(J, "JSON", source); 163 | jsonnext(J); 164 | 165 | if (js_iscallable(J, 2)) { 166 | js_newobject(J); 167 | jsonvalue(J); 168 | js_defproperty(J, -2, "", 0); 169 | jsonrevive(J, ""); 170 | } else { 171 | jsonvalue(J); 172 | } 173 | } 174 | 175 | static void fmtnum(js_State *J, js_Buffer **sb, double n) 176 | { 177 | if (isnan(n)) js_puts(J, sb, "null"); 178 | else if (isinf(n)) js_puts(J, sb, "null"); 179 | else if (n == 0) js_puts(J, sb, "0"); 180 | else { 181 | char buf[40]; 182 | js_puts(J, sb, jsV_numbertostring(J, buf, n)); 183 | } 184 | } 185 | 186 | static void fmtstr(js_State *J, js_Buffer **sb, const char *s) 187 | { 188 | static const char *HEX = "0123456789abcdef"; 189 | int i, n; 190 | Rune c; 191 | js_putc(J, sb, '"'); 192 | while (*s) { 193 | n = chartorune(&c, s); 194 | switch (c) { 195 | case '"': js_puts(J, sb, "\\\""); break; 196 | case '\\': js_puts(J, sb, "\\\\"); break; 197 | case '\b': js_puts(J, sb, "\\b"); break; 198 | case '\f': js_puts(J, sb, "\\f"); break; 199 | case '\n': js_puts(J, sb, "\\n"); break; 200 | case '\r': js_puts(J, sb, "\\r"); break; 201 | case '\t': js_puts(J, sb, "\\t"); break; 202 | default: 203 | if (c < ' ' || (c >= 0xd800 && c <= 0xdfff)) { 204 | js_putc(J, sb, '\\'); 205 | js_putc(J, sb, 'u'); 206 | js_putc(J, sb, HEX[(c>>12)&15]); 207 | js_putc(J, sb, HEX[(c>>8)&15]); 208 | js_putc(J, sb, HEX[(c>>4)&15]); 209 | js_putc(J, sb, HEX[c&15]); 210 | } else if (c < 128) { 211 | js_putc(J, sb, c); 212 | } else { 213 | for (i = 0; i < n; ++i) 214 | js_putc(J, sb, s[i]); 215 | } 216 | break; 217 | } 218 | s += n; 219 | } 220 | js_putc(J, sb, '"'); 221 | } 222 | 223 | static void fmtindent(js_State *J, js_Buffer **sb, const char *gap, int level) 224 | { 225 | js_putc(J, sb, '\n'); 226 | while (level--) 227 | js_puts(J, sb, gap); 228 | } 229 | 230 | static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level); 231 | 232 | static int filterprop(js_State *J, const char *key) 233 | { 234 | int i, n, found; 235 | /* replacer/property-list is in stack slot 2 */ 236 | if (js_isarray(J, 2)) { 237 | found = 0; 238 | n = js_getlength(J, 2); 239 | for (i = 0; i < n && !found; ++i) { 240 | js_getindex(J, 2, i); 241 | if (js_isstring(J, -1) || js_isnumber(J, -1) || 242 | js_isstringobject(J, -1) || js_isnumberobject(J, -1)) 243 | found = !strcmp(key, js_tostring(J, -1)); 244 | js_pop(J, 1); 245 | } 246 | return found; 247 | } 248 | return 1; 249 | } 250 | 251 | static void fmtobject(js_State *J, js_Buffer **sb, js_Object *obj, const char *gap, int level) 252 | { 253 | const char *key; 254 | int save; 255 | int i, n; 256 | 257 | n = js_gettop(J) - 1; 258 | for (i = 4; i < n; ++i) 259 | if (js_isobject(J, i)) 260 | if (js_toobject(J, i) == js_toobject(J, -1)) 261 | js_typeerror(J, "cyclic object value"); 262 | 263 | n = 0; 264 | js_putc(J, sb, '{'); 265 | js_pushiterator(J, -1, 1); 266 | while ((key = js_nextiterator(J, -1))) { 267 | if (filterprop(J, key)) { 268 | save = (*sb)->n; 269 | if (n) js_putc(J, sb, ','); 270 | if (gap) fmtindent(J, sb, gap, level + 1); 271 | fmtstr(J, sb, key); 272 | js_putc(J, sb, ':'); 273 | if (gap) 274 | js_putc(J, sb, ' '); 275 | js_rot2(J); 276 | if (!fmtvalue(J, sb, key, gap, level + 1)) 277 | (*sb)->n = save; 278 | else 279 | ++n; 280 | js_rot2(J); 281 | } 282 | } 283 | js_pop(J, 1); 284 | if (gap && n) fmtindent(J, sb, gap, level); 285 | js_putc(J, sb, '}'); 286 | } 287 | 288 | static void fmtarray(js_State *J, js_Buffer **sb, const char *gap, int level) 289 | { 290 | int n, i; 291 | char buf[32]; 292 | 293 | n = js_gettop(J) - 1; 294 | for (i = 4; i < n; ++i) 295 | if (js_isobject(J, i)) 296 | if (js_toobject(J, i) == js_toobject(J, -1)) 297 | js_typeerror(J, "cyclic object value"); 298 | 299 | js_putc(J, sb, '['); 300 | n = js_getlength(J, -1); 301 | for (i = 0; i < n; ++i) { 302 | if (i) js_putc(J, sb, ','); 303 | if (gap) fmtindent(J, sb, gap, level + 1); 304 | if (!fmtvalue(J, sb, js_itoa(buf, i), gap, level + 1)) 305 | js_puts(J, sb, "null"); 306 | } 307 | if (gap && n) fmtindent(J, sb, gap, level); 308 | js_putc(J, sb, ']'); 309 | } 310 | 311 | static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level) 312 | { 313 | /* replacer/property-list is in 2 */ 314 | /* holder is in -1 */ 315 | 316 | js_getproperty(J, -1, key); 317 | 318 | if (js_isobject(J, -1)) { 319 | if (js_hasproperty(J, -1, "toJSON")) { 320 | if (js_iscallable(J, -1)) { 321 | js_copy(J, -2); 322 | js_pushstring(J, key); 323 | js_call(J, 1); 324 | js_rot2pop1(J); 325 | } else { 326 | js_pop(J, 1); 327 | } 328 | } 329 | } 330 | 331 | if (js_iscallable(J, 2)) { 332 | js_copy(J, 2); /* replacer function */ 333 | js_copy(J, -3); /* holder as this */ 334 | js_pushstring(J, key); /* name */ 335 | js_copy(J, -4); /* old value */ 336 | js_call(J, 2); 337 | js_rot2pop1(J); /* pop old value, leave new value on stack */ 338 | } 339 | 340 | if (js_isobject(J, -1) && !js_iscallable(J, -1)) { 341 | js_Object *obj = js_toobject(J, -1); 342 | switch (obj->type) { 343 | case JS_CNUMBER: fmtnum(J, sb, obj->u.number); break; 344 | case JS_CSTRING: fmtstr(J, sb, obj->u.s.string); break; 345 | case JS_CBOOLEAN: js_puts(J, sb, obj->u.boolean ? "true" : "false"); break; 346 | case JS_CARRAY: fmtarray(J, sb, gap, level); break; 347 | default: fmtobject(J, sb, obj, gap, level); break; 348 | } 349 | } 350 | else if (js_isboolean(J, -1)) 351 | js_puts(J, sb, js_toboolean(J, -1) ? "true" : "false"); 352 | else if (js_isnumber(J, -1)) 353 | fmtnum(J, sb, js_tonumber(J, -1)); 354 | else if (js_isstring(J, -1)) 355 | fmtstr(J, sb, js_tostring(J, -1)); 356 | else if (js_isnull(J, -1)) 357 | js_puts(J, sb, "null"); 358 | else { 359 | js_pop(J, 1); 360 | return 0; 361 | } 362 | 363 | js_pop(J, 1); 364 | return 1; 365 | } 366 | 367 | static void JSON_stringify(js_State *J) 368 | { 369 | js_Buffer *sb = NULL; 370 | char buf[12]; 371 | /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */ 372 | const char * volatile gap; 373 | const char *s; 374 | int n; 375 | 376 | gap = NULL; 377 | 378 | if (js_isnumber(J, 3) || js_isnumberobject(J, 3)) { 379 | n = js_tointeger(J, 3); 380 | if (n < 0) n = 0; 381 | if (n > 10) n = 10; 382 | memset(buf, ' ', n); 383 | buf[n] = 0; 384 | if (n > 0) gap = buf; 385 | } else if (js_isstring(J, 3) || js_isstringobject(J, 3)) { 386 | s = js_tostring(J, 3); 387 | n = strlen(s); 388 | if (n > 10) n = 10; 389 | memcpy(buf, s, n); 390 | buf[n] = 0; 391 | if (n > 0) gap = buf; 392 | } 393 | 394 | if (js_try(J)) { 395 | js_free(J, sb); 396 | js_throw(J); 397 | } 398 | 399 | js_newobject(J); /* wrapper */ 400 | js_copy(J, 1); 401 | js_defproperty(J, -2, "", 0); 402 | if (!fmtvalue(J, &sb, "", gap, 0)) { 403 | js_pushundefined(J); 404 | } else { 405 | js_putc(J, &sb, 0); 406 | js_pushstring(J, sb ? sb->s : ""); 407 | js_rot2pop1(J); 408 | } 409 | 410 | js_endtry(J); 411 | js_free(J, sb); 412 | } 413 | 414 | void jsB_initjson(js_State *J) 415 | { 416 | js_pushobject(J, jsV_newobject(J, JS_CJSON, J->Object_prototype)); 417 | { 418 | jsB_propf(J, "JSON.parse", JSON_parse, 2); 419 | jsB_propf(J, "JSON.stringify", JSON_stringify, 3); 420 | } 421 | js_defglobal(J, "JSON", JS_DONTENUM); 422 | } 423 | -------------------------------------------------------------------------------- /jsproperty.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | #include 4 | 5 | /* 6 | Use an AA-tree to quickly look up properties in objects: 7 | 8 | The level of every leaf node is one. 9 | The level of every left child is one less than its parent. 10 | The level of every right child is equal or one less than its parent. 11 | The level of every right grandchild is less than its grandparent. 12 | Every node of level greater than one has two children. 13 | 14 | A link where the child's level is equal to that of its parent is called a horizontal link. 15 | Individual right horizontal links are allowed, but consecutive ones are forbidden. 16 | Left horizontal links are forbidden. 17 | 18 | skew() fixes left horizontal links. 19 | split() fixes consecutive right horizontal links. 20 | */ 21 | 22 | static js_Property sentinel = { 23 | &sentinel, &sentinel, 24 | 0, 0, 25 | { { {0}, JS_TUNDEFINED } }, 26 | NULL, NULL, "" 27 | }; 28 | 29 | static js_Property *newproperty(js_State *J, js_Object *obj, const char *name) 30 | { 31 | int n = strlen(name) + 1; 32 | js_Property *node = js_malloc(J, offsetof(js_Property, name) + n); 33 | node->left = node->right = &sentinel; 34 | node->level = 1; 35 | node->atts = 0; 36 | node->value.t.type = JS_TUNDEFINED; 37 | node->value.u.number = 0; 38 | node->getter = NULL; 39 | node->setter = NULL; 40 | memcpy(node->name, name, n); 41 | ++obj->count; 42 | ++J->gccounter; 43 | return node; 44 | } 45 | 46 | static js_Property *lookup(js_Property *node, const char *name) 47 | { 48 | while (node != &sentinel) { 49 | int c = strcmp(name, node->name); 50 | if (c == 0) 51 | return node; 52 | else if (c < 0) 53 | node = node->left; 54 | else 55 | node = node->right; 56 | } 57 | return NULL; 58 | } 59 | 60 | static js_Property *skew(js_Property *node) 61 | { 62 | if (node->left->level == node->level) { 63 | js_Property *temp = node; 64 | node = node->left; 65 | temp->left = node->right; 66 | node->right = temp; 67 | } 68 | return node; 69 | } 70 | 71 | static js_Property *split(js_Property *node) 72 | { 73 | if (node->right->right->level == node->level) { 74 | js_Property *temp = node; 75 | node = node->right; 76 | temp->right = node->left; 77 | node->left = temp; 78 | ++node->level; 79 | } 80 | return node; 81 | } 82 | 83 | static js_Property *insert(js_State *J, js_Object *obj, js_Property *node, const char *name, js_Property **result) 84 | { 85 | if (node != &sentinel) { 86 | int c = strcmp(name, node->name); 87 | if (c < 0) 88 | node->left = insert(J, obj, node->left, name, result); 89 | else if (c > 0) 90 | node->right = insert(J, obj, node->right, name, result); 91 | else 92 | return *result = node; 93 | node = skew(node); 94 | node = split(node); 95 | return node; 96 | } 97 | return *result = newproperty(J, obj, name); 98 | } 99 | 100 | static void freeproperty(js_State *J, js_Object *obj, js_Property *node) 101 | { 102 | js_free(J, node); 103 | --obj->count; 104 | } 105 | 106 | static js_Property *unlinkproperty(js_Property *node, const char *name, js_Property **garbage) 107 | { 108 | js_Property *temp, *a, *b; 109 | if (node != &sentinel) { 110 | int c = strcmp(name, node->name); 111 | if (c < 0) { 112 | node->left = unlinkproperty(node->left, name, garbage); 113 | } else if (c > 0) { 114 | node->right = unlinkproperty(node->right, name, garbage); 115 | } else { 116 | *garbage = node; 117 | if (node->left == &sentinel && node->right == &sentinel) { 118 | return &sentinel; 119 | } 120 | else if (node->left == &sentinel) { 121 | a = node->right; 122 | while (a->left != &sentinel) 123 | a = a->left; 124 | b = unlinkproperty(node->right, a->name, &temp); 125 | temp->level = node->level; 126 | temp->left = node->left; 127 | temp->right = b; 128 | node = temp; 129 | } 130 | else { 131 | a = node->left; 132 | while (a->right != &sentinel) 133 | a = a->right; 134 | b = unlinkproperty(node->left, a->name, &temp); 135 | temp->level = node->level; 136 | temp->left = b; 137 | temp->right = node->right; 138 | node = temp; 139 | } 140 | } 141 | 142 | if (node->left->level < node->level - 1 || node->right->level < node->level - 1) 143 | { 144 | if (node->right->level > --node->level) 145 | node->right->level = node->level; 146 | node = skew(node); 147 | node->right = skew(node->right); 148 | node->right->right = skew(node->right->right); 149 | node = split(node); 150 | node->right = split(node->right); 151 | } 152 | } 153 | return node; 154 | } 155 | 156 | static js_Property *deleteproperty(js_State *J, js_Object *obj, js_Property *tree, const char *name) 157 | { 158 | js_Property *garbage = &sentinel; 159 | tree = unlinkproperty(tree, name, &garbage); 160 | if (garbage != &sentinel) 161 | freeproperty(J, obj, garbage); 162 | return tree; 163 | } 164 | 165 | js_Object *jsV_newobject(js_State *J, enum js_Class type, js_Object *prototype) 166 | { 167 | js_Object *obj = js_malloc(J, sizeof *obj); 168 | memset(obj, 0, sizeof *obj); 169 | obj->gcmark = 0; 170 | obj->gcnext = J->gcobj; 171 | J->gcobj = obj; 172 | ++J->gccounter; 173 | 174 | obj->type = type; 175 | obj->properties = &sentinel; 176 | obj->prototype = prototype; 177 | obj->extensible = 1; 178 | return obj; 179 | } 180 | 181 | js_Property *jsV_getownproperty(js_State *J, js_Object *obj, const char *name) 182 | { 183 | return lookup(obj->properties, name); 184 | } 185 | 186 | js_Property *jsV_getpropertyx(js_State *J, js_Object *obj, const char *name, int *own) 187 | { 188 | *own = 1; 189 | do { 190 | js_Property *ref = lookup(obj->properties, name); 191 | if (ref) 192 | return ref; 193 | obj = obj->prototype; 194 | *own = 0; 195 | } while (obj); 196 | return NULL; 197 | } 198 | 199 | js_Property *jsV_getproperty(js_State *J, js_Object *obj, const char *name) 200 | { 201 | do { 202 | js_Property *ref = lookup(obj->properties, name); 203 | if (ref) 204 | return ref; 205 | obj = obj->prototype; 206 | } while (obj); 207 | return NULL; 208 | } 209 | 210 | static js_Property *jsV_getenumproperty(js_State *J, js_Object *obj, const char *name) 211 | { 212 | do { 213 | js_Property *ref = lookup(obj->properties, name); 214 | if (ref && !(ref->atts & JS_DONTENUM)) 215 | return ref; 216 | obj = obj->prototype; 217 | } while (obj); 218 | return NULL; 219 | } 220 | 221 | js_Property *jsV_setproperty(js_State *J, js_Object *obj, const char *name) 222 | { 223 | js_Property *result; 224 | 225 | if (!obj->extensible) { 226 | result = lookup(obj->properties, name); 227 | if (J->strict && !result) 228 | js_typeerror(J, "object is non-extensible"); 229 | return result; 230 | } 231 | 232 | obj->properties = insert(J, obj, obj->properties, name, &result); 233 | 234 | return result; 235 | } 236 | 237 | void jsV_delproperty(js_State *J, js_Object *obj, const char *name) 238 | { 239 | obj->properties = deleteproperty(J, obj, obj->properties, name); 240 | } 241 | 242 | /* Flatten hierarchy of enumerable properties into an iterator object */ 243 | 244 | static js_Iterator *itnewnode(js_State *J, const char *name, js_Iterator *next) { 245 | int n = strlen(name) + 1; 246 | js_Iterator *node = js_malloc(J, offsetof(js_Iterator, name) + n); 247 | node->next = next; 248 | memcpy(node->name, name, n); 249 | return node; 250 | } 251 | 252 | static js_Iterator *itwalk(js_State *J, js_Iterator *iter, js_Property *prop, js_Object *seen) 253 | { 254 | if (prop->right != &sentinel) 255 | iter = itwalk(J, iter, prop->right, seen); 256 | if (!(prop->atts & JS_DONTENUM)) { 257 | if (!seen || !jsV_getenumproperty(J, seen, prop->name)) { 258 | iter = itnewnode(J, prop->name, iter); 259 | } 260 | } 261 | if (prop->left != &sentinel) 262 | iter = itwalk(J, iter, prop->left, seen); 263 | return iter; 264 | } 265 | 266 | static js_Iterator *itflatten(js_State *J, js_Object *obj) 267 | { 268 | js_Iterator *iter = NULL; 269 | if (obj->prototype) 270 | iter = itflatten(J, obj->prototype); 271 | if (obj->properties != &sentinel) 272 | iter = itwalk(J, iter, obj->properties, obj->prototype); 273 | return iter; 274 | } 275 | 276 | js_Object *jsV_newiterator(js_State *J, js_Object *obj, int own) 277 | { 278 | js_Object *io = jsV_newobject(J, JS_CITERATOR, NULL); 279 | io->u.iter.target = obj; 280 | io->u.iter.i = 0; 281 | io->u.iter.n = 0; 282 | if (own) { 283 | io->u.iter.head = NULL; 284 | if (obj->properties != &sentinel) 285 | io->u.iter.head = itwalk(J, io->u.iter.head, obj->properties, NULL); 286 | } else { 287 | io->u.iter.head = itflatten(J, obj); 288 | } 289 | io->u.iter.current = io->u.iter.head; 290 | 291 | if (obj->type == JS_CSTRING) 292 | io->u.iter.n = obj->u.s.length; 293 | 294 | if (obj->type == JS_CARRAY && obj->u.a.simple) 295 | io->u.iter.n = obj->u.a.flat_length; 296 | 297 | return io; 298 | } 299 | 300 | const char *jsV_nextiterator(js_State *J, js_Object *io) 301 | { 302 | if (io->type != JS_CITERATOR) 303 | js_typeerror(J, "not an iterator"); 304 | if (io->u.iter.i < io->u.iter.n) { 305 | js_itoa(J->scratch, io->u.iter.i); 306 | io->u.iter.i++; 307 | return J->scratch; 308 | } 309 | while (io->u.iter.current) { 310 | const char *name = io->u.iter.current->name; 311 | io->u.iter.current = io->u.iter.current->next; 312 | if (jsV_getproperty(J, io->u.iter.target, name)) 313 | return name; 314 | } 315 | return NULL; 316 | } 317 | 318 | /* Walk all the properties and delete them one by one for arrays */ 319 | 320 | void jsV_resizearray(js_State *J, js_Object *obj, int newlen) 321 | { 322 | char buf[32]; 323 | const char *s; 324 | int k; 325 | assert(!obj->u.a.simple); 326 | if (newlen < obj->u.a.length) { 327 | if (obj->u.a.length > obj->count * 2) { 328 | js_Object *it = jsV_newiterator(J, obj, 1); 329 | while ((s = jsV_nextiterator(J, it))) { 330 | k = jsV_numbertointeger(jsV_stringtonumber(J, s)); 331 | if (k >= newlen && !strcmp(s, jsV_numbertostring(J, buf, k))) 332 | jsV_delproperty(J, obj, s); 333 | } 334 | } else { 335 | for (k = newlen; k < obj->u.a.length; ++k) { 336 | jsV_delproperty(J, obj, js_itoa(buf, k)); 337 | } 338 | } 339 | } 340 | obj->u.a.length = newlen; 341 | } 342 | -------------------------------------------------------------------------------- /jsregexp.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | #include "regexp.h" 3 | 4 | static char *escaperegexp(js_State *J, const char *pattern) { 5 | char *copy, *p; 6 | const char *s; 7 | int n = 0; 8 | for (s = pattern; *s; ++s) { 9 | if (*s == '/') 10 | ++n; 11 | ++n; 12 | } 13 | copy = p = js_malloc(J, n+1); 14 | for (s = pattern; *s; ++s) { 15 | if (*s == '/') 16 | *p++ = '\\'; 17 | *p++ = *s; 18 | } 19 | *p = 0; 20 | return copy; 21 | } 22 | 23 | static void js_newregexpx(js_State *J, const char *pattern, int flags, int is_clone) 24 | { 25 | const char *error; 26 | js_Object *obj; 27 | Reprog *prog; 28 | int opts; 29 | 30 | obj = jsV_newobject(J, JS_CREGEXP, J->RegExp_prototype); 31 | 32 | opts = 0; 33 | if (flags & JS_REGEXP_I) opts |= REG_ICASE; 34 | if (flags & JS_REGEXP_M) opts |= REG_NEWLINE; 35 | 36 | prog = js_regcompx(J->alloc, J->actx, pattern, opts, &error); 37 | if (!prog) 38 | js_syntaxerror(J, "regular expression: %s", error); 39 | 40 | obj->u.r.prog = prog; 41 | obj->u.r.source = is_clone ? js_strdup(J, pattern) : escaperegexp(J, pattern); 42 | obj->u.r.flags = flags; 43 | obj->u.r.last = 0; 44 | js_pushobject(J, obj); 45 | } 46 | 47 | void js_newregexp(js_State *J, const char *pattern, int flags) 48 | { 49 | js_newregexpx(J, pattern, flags, 0); 50 | } 51 | 52 | void js_RegExp_prototype_exec(js_State *J, js_Regexp *re, const char *text) 53 | { 54 | const char *haystack; 55 | int result; 56 | int i; 57 | int opts; 58 | Resub m; 59 | 60 | haystack = text; 61 | opts = 0; 62 | if (re->flags & JS_REGEXP_G) { 63 | if (re->last > strlen(haystack)) { 64 | re->last = 0; 65 | js_pushnull(J); 66 | return; 67 | } 68 | if (re->last > 0) { 69 | haystack = text + re->last; 70 | opts |= REG_NOTBOL; 71 | } 72 | } 73 | 74 | result = js_regexec(re->prog, haystack, &m, opts); 75 | if (result < 0) 76 | js_error(J, "regexec failed"); 77 | if (result == 0) { 78 | js_newarray(J); 79 | js_pushstring(J, text); 80 | js_setproperty(J, -2, "input"); 81 | js_pushnumber(J, js_utfptrtoidx(text, m.sub[0].sp)); 82 | js_setproperty(J, -2, "index"); 83 | for (i = 0; i < m.nsub; ++i) { 84 | js_pushlstring(J, m.sub[i].sp, m.sub[i].ep - m.sub[i].sp); 85 | js_setindex(J, -2, i); 86 | } 87 | if (re->flags & JS_REGEXP_G) 88 | re->last = m.sub[0].ep - text; 89 | return; 90 | } 91 | 92 | if (re->flags & JS_REGEXP_G) 93 | re->last = 0; 94 | 95 | js_pushnull(J); 96 | } 97 | 98 | static void Rp_test(js_State *J) 99 | { 100 | js_Regexp *re; 101 | const char *text; 102 | int result; 103 | int opts; 104 | Resub m; 105 | 106 | re = js_toregexp(J, 0); 107 | text = js_tostring(J, 1); 108 | 109 | opts = 0; 110 | if (re->flags & JS_REGEXP_G) { 111 | if (re->last > strlen(text)) { 112 | re->last = 0; 113 | js_pushboolean(J, 0); 114 | return; 115 | } 116 | if (re->last > 0) { 117 | text += re->last; 118 | opts |= REG_NOTBOL; 119 | } 120 | } 121 | 122 | result = js_regexec(re->prog, text, &m, opts); 123 | if (result < 0) 124 | js_error(J, "regexec failed"); 125 | if (result == 0) { 126 | if (re->flags & JS_REGEXP_G) 127 | re->last = re->last + (m.sub[0].ep - text); 128 | js_pushboolean(J, 1); 129 | return; 130 | } 131 | 132 | if (re->flags & JS_REGEXP_G) 133 | re->last = 0; 134 | 135 | js_pushboolean(J, 0); 136 | } 137 | 138 | static void jsB_new_RegExp(js_State *J) 139 | { 140 | js_Regexp *old; 141 | const char *pattern; 142 | int flags; 143 | int is_clone = 0; 144 | 145 | if (js_isregexp(J, 1)) { 146 | if (js_isdefined(J, 2)) 147 | js_typeerror(J, "cannot supply flags when creating one RegExp from another"); 148 | old = js_toregexp(J, 1); 149 | pattern = old->source; 150 | flags = old->flags; 151 | is_clone = 1; 152 | } else if (js_isundefined(J, 1)) { 153 | pattern = "(?:)"; 154 | flags = 0; 155 | } else { 156 | pattern = js_tostring(J, 1); 157 | flags = 0; 158 | } 159 | 160 | if (strlen(pattern) == 0) 161 | pattern = "(?:)"; 162 | 163 | if (js_isdefined(J, 2)) { 164 | const char *s = js_tostring(J, 2); 165 | int g = 0, i = 0, m = 0; 166 | while (*s) { 167 | if (*s == 'g') ++g; 168 | else if (*s == 'i') ++i; 169 | else if (*s == 'm') ++m; 170 | else js_syntaxerror(J, "invalid regular expression flag: '%c'", *s); 171 | ++s; 172 | } 173 | if (g > 1) js_syntaxerror(J, "invalid regular expression flag: 'g'"); 174 | if (i > 1) js_syntaxerror(J, "invalid regular expression flag: 'i'"); 175 | if (m > 1) js_syntaxerror(J, "invalid regular expression flag: 'm'"); 176 | if (g) flags |= JS_REGEXP_G; 177 | if (i) flags |= JS_REGEXP_I; 178 | if (m) flags |= JS_REGEXP_M; 179 | } 180 | 181 | js_newregexpx(J, pattern, flags, is_clone); 182 | } 183 | 184 | static void jsB_RegExp(js_State *J) 185 | { 186 | if (js_isregexp(J, 1)) 187 | return; 188 | jsB_new_RegExp(J); 189 | } 190 | 191 | static void Rp_toString(js_State *J) 192 | { 193 | js_Regexp *re; 194 | char * volatile out = NULL; 195 | 196 | re = js_toregexp(J, 0); 197 | 198 | if (js_try(J)) { 199 | js_free(J, out); 200 | js_throw(J); 201 | } 202 | 203 | out = js_malloc(J, strlen(re->source) + 6); /* extra space for //gim */ 204 | strcpy(out, "/"); 205 | strcat(out, re->source); 206 | strcat(out, "/"); 207 | if (re->flags & JS_REGEXP_G) strcat(out, "g"); 208 | if (re->flags & JS_REGEXP_I) strcat(out, "i"); 209 | if (re->flags & JS_REGEXP_M) strcat(out, "m"); 210 | 211 | js_pop(J, 0); 212 | js_pushstring(J, out); 213 | js_endtry(J); 214 | js_free(J, out); 215 | } 216 | 217 | static void Rp_exec(js_State *J) 218 | { 219 | js_RegExp_prototype_exec(J, js_toregexp(J, 0), js_tostring(J, 1)); 220 | } 221 | 222 | void jsB_initregexp(js_State *J) 223 | { 224 | js_pushobject(J, J->RegExp_prototype); 225 | { 226 | jsB_propf(J, "RegExp.prototype.toString", Rp_toString, 0); 227 | jsB_propf(J, "RegExp.prototype.test", Rp_test, 0); 228 | jsB_propf(J, "RegExp.prototype.exec", Rp_exec, 0); 229 | } 230 | js_newcconstructor(J, jsB_RegExp, jsB_new_RegExp, "RegExp", 1); 231 | js_defglobal(J, "RegExp", JS_DONTENUM); 232 | } 233 | -------------------------------------------------------------------------------- /jsrepr.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | #include "utf.h" 3 | 4 | static void reprvalue(js_State *J, js_Buffer **sb); 5 | 6 | static void reprnum(js_State *J, js_Buffer **sb, double n) 7 | { 8 | char buf[40]; 9 | if (n == 0 && signbit(n)) 10 | js_puts(J, sb, "-0"); 11 | else 12 | js_puts(J, sb, jsV_numbertostring(J, buf, n)); 13 | } 14 | 15 | static void reprstr(js_State *J, js_Buffer **sb, const char *s) 16 | { 17 | static const char *HEX = "0123456789ABCDEF"; 18 | int i, n; 19 | Rune c; 20 | js_putc(J, sb, '"'); 21 | while (*s) { 22 | n = chartorune(&c, s); 23 | switch (c) { 24 | case '"': js_puts(J, sb, "\\\""); break; 25 | case '\\': js_puts(J, sb, "\\\\"); break; 26 | case '\b': js_puts(J, sb, "\\b"); break; 27 | case '\f': js_puts(J, sb, "\\f"); break; 28 | case '\n': js_puts(J, sb, "\\n"); break; 29 | case '\r': js_puts(J, sb, "\\r"); break; 30 | case '\t': js_puts(J, sb, "\\t"); break; 31 | default: 32 | if (c < ' ') { 33 | js_putc(J, sb, '\\'); 34 | js_putc(J, sb, 'x'); 35 | js_putc(J, sb, HEX[(c>>4)&15]); 36 | js_putc(J, sb, HEX[c&15]); 37 | } else if (c < 128) { 38 | js_putc(J, sb, c); 39 | } else if (c < 0x10000) { 40 | js_putc(J, sb, '\\'); 41 | js_putc(J, sb, 'u'); 42 | js_putc(J, sb, HEX[(c>>12)&15]); 43 | js_putc(J, sb, HEX[(c>>8)&15]); 44 | js_putc(J, sb, HEX[(c>>4)&15]); 45 | js_putc(J, sb, HEX[c&15]); 46 | } else { 47 | for (i = 0; i < n; ++i) 48 | js_putc(J, sb, s[i]); 49 | } 50 | break; 51 | } 52 | s += n; 53 | } 54 | js_putc(J, sb, '"'); 55 | } 56 | 57 | #ifndef isalpha 58 | #define isalpha(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) 59 | #endif 60 | #ifndef isdigit 61 | #define isdigit(c) (c >= '0' && c <= '9') 62 | #endif 63 | 64 | static void reprident(js_State *J, js_Buffer **sb, const char *name) 65 | { 66 | const char *p = name; 67 | if (isdigit(*p)) 68 | while (isdigit(*p)) 69 | ++p; 70 | else if (isalpha(*p) || *p == '_') 71 | while (isdigit(*p) || isalpha(*p) || *p == '_') 72 | ++p; 73 | if (p > name && *p == 0) 74 | js_puts(J, sb, name); 75 | else 76 | reprstr(J, sb, name); 77 | } 78 | 79 | static void reprobject(js_State *J, js_Buffer **sb) 80 | { 81 | const char *key; 82 | int i, n; 83 | 84 | n = js_gettop(J) - 1; 85 | for (i = 0; i < n; ++i) { 86 | if (js_isobject(J, i)) { 87 | if (js_toobject(J, i) == js_toobject(J, -1)) { 88 | js_puts(J, sb, "{}"); 89 | return; 90 | } 91 | } 92 | } 93 | 94 | n = 0; 95 | js_putc(J, sb, '{'); 96 | js_pushiterator(J, -1, 1); 97 | while ((key = js_nextiterator(J, -1))) { 98 | if (n++ > 0) 99 | js_puts(J, sb, ", "); 100 | reprident(J, sb, key); 101 | js_puts(J, sb, ": "); 102 | js_getproperty(J, -2, key); 103 | reprvalue(J, sb); 104 | js_pop(J, 1); 105 | } 106 | js_pop(J, 1); 107 | js_putc(J, sb, '}'); 108 | } 109 | 110 | static void reprarray(js_State *J, js_Buffer **sb) 111 | { 112 | int n, i; 113 | 114 | n = js_gettop(J) - 1; 115 | for (i = 0; i < n; ++i) { 116 | if (js_isobject(J, i)) { 117 | if (js_toobject(J, i) == js_toobject(J, -1)) { 118 | js_puts(J, sb, "[]"); 119 | return; 120 | } 121 | } 122 | } 123 | 124 | js_putc(J, sb, '['); 125 | n = js_getlength(J, -1); 126 | for (i = 0; i < n; ++i) { 127 | if (i > 0) 128 | js_puts(J, sb, ", "); 129 | if (js_hasindex(J, -1, i)) { 130 | reprvalue(J, sb); 131 | js_pop(J, 1); 132 | } 133 | } 134 | js_putc(J, sb, ']'); 135 | } 136 | 137 | static void reprfun(js_State *J, js_Buffer **sb, js_Function *fun) 138 | { 139 | int i; 140 | js_puts(J, sb, "function "); 141 | js_puts(J, sb, fun->name); 142 | js_putc(J, sb, '('); 143 | for (i = 0; i < fun->numparams; ++i) { 144 | if (i > 0) 145 | js_puts(J, sb, ", "); 146 | js_puts(J, sb, fun->vartab[i]); 147 | } 148 | js_puts(J, sb, ") { [byte code] }"); 149 | } 150 | 151 | static void reprvalue(js_State *J, js_Buffer **sb) 152 | { 153 | if (js_isundefined(J, -1)) 154 | js_puts(J, sb, "undefined"); 155 | else if (js_isnull(J, -1)) 156 | js_puts(J, sb, "null"); 157 | else if (js_isboolean(J, -1)) 158 | js_puts(J, sb, js_toboolean(J, -1) ? "true" : "false"); 159 | else if (js_isnumber(J, -1)) 160 | reprnum(J, sb, js_tonumber(J, -1)); 161 | else if (js_isstring(J, -1)) 162 | reprstr(J, sb, js_tostring(J, -1)); 163 | else if (js_isobject(J, -1)) { 164 | js_Object *obj = js_toobject(J, -1); 165 | switch (obj->type) { 166 | default: 167 | reprobject(J, sb); 168 | break; 169 | case JS_CARRAY: 170 | reprarray(J, sb); 171 | break; 172 | case JS_CFUNCTION: 173 | case JS_CSCRIPT: 174 | reprfun(J, sb, obj->u.f.function); 175 | break; 176 | case JS_CCFUNCTION: 177 | js_puts(J, sb, "function "); 178 | js_puts(J, sb, obj->u.c.name); 179 | js_puts(J, sb, "() { [native code] }"); 180 | break; 181 | case JS_CBOOLEAN: 182 | js_puts(J, sb, "(new Boolean("); 183 | js_puts(J, sb, obj->u.boolean ? "true" : "false"); 184 | js_puts(J, sb, "))"); 185 | break; 186 | case JS_CNUMBER: 187 | js_puts(J, sb, "(new Number("); 188 | reprnum(J, sb, obj->u.number); 189 | js_puts(J, sb, "))"); 190 | break; 191 | case JS_CSTRING: 192 | js_puts(J, sb, "(new String("); 193 | reprstr(J, sb, obj->u.s.string); 194 | js_puts(J, sb, "))"); 195 | break; 196 | case JS_CREGEXP: 197 | js_putc(J, sb, '/'); 198 | js_puts(J, sb, obj->u.r.source); 199 | js_putc(J, sb, '/'); 200 | if (obj->u.r.flags & JS_REGEXP_G) js_putc(J, sb, 'g'); 201 | if (obj->u.r.flags & JS_REGEXP_I) js_putc(J, sb, 'i'); 202 | if (obj->u.r.flags & JS_REGEXP_M) js_putc(J, sb, 'm'); 203 | break; 204 | case JS_CDATE: 205 | { 206 | char buf[40]; 207 | js_puts(J, sb, "(new Date("); 208 | js_puts(J, sb, jsV_numbertostring(J, buf, obj->u.number)); 209 | js_puts(J, sb, "))"); 210 | } 211 | break; 212 | case JS_CERROR: 213 | js_puts(J, sb, "(new "); 214 | js_getproperty(J, -1, "name"); 215 | js_puts(J, sb, js_tostring(J, -1)); 216 | js_pop(J, 1); 217 | js_putc(J, sb, '('); 218 | if (js_hasproperty(J, -1, "message")) { 219 | reprvalue(J, sb); 220 | js_pop(J, 1); 221 | } 222 | js_puts(J, sb, "))"); 223 | break; 224 | case JS_CMATH: 225 | js_puts(J, sb, "Math"); 226 | break; 227 | case JS_CJSON: 228 | js_puts(J, sb, "JSON"); 229 | break; 230 | case JS_CITERATOR: 231 | js_puts(J, sb, "[iterator "); 232 | break; 233 | case JS_CUSERDATA: 234 | js_puts(J, sb, "[userdata "); 235 | js_puts(J, sb, obj->u.user.tag); 236 | js_putc(J, sb, ']'); 237 | break; 238 | } 239 | } 240 | } 241 | 242 | void js_repr(js_State *J, int idx) 243 | { 244 | js_Buffer *sb = NULL; 245 | int savebot; 246 | 247 | if (js_try(J)) { 248 | js_free(J, sb); 249 | js_throw(J); 250 | } 251 | 252 | js_copy(J, idx); 253 | 254 | savebot = J->bot; 255 | J->bot = J->top - 1; 256 | reprvalue(J, &sb); 257 | J->bot = savebot; 258 | 259 | js_pop(J, 1); 260 | 261 | js_putc(J, &sb, 0); 262 | js_pushstring(J, sb ? sb->s : "undefined"); 263 | 264 | js_endtry(J); 265 | js_free(J, sb); 266 | } 267 | 268 | const char *js_torepr(js_State *J, int idx) 269 | { 270 | js_repr(J, idx); 271 | js_replace(J, idx < 0 ? idx-1 : idx); 272 | return js_tostring(J, idx); 273 | } 274 | 275 | const char *js_tryrepr(js_State *J, int idx, const char *error) 276 | { 277 | const char *s; 278 | if (js_try(J)) { 279 | js_pop(J, 1); 280 | return error; 281 | } 282 | s = js_torepr(J, idx); 283 | js_endtry(J); 284 | return s; 285 | } 286 | -------------------------------------------------------------------------------- /jsstate.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | 3 | #include 4 | #include 5 | 6 | static int js_ptry(js_State *J) { 7 | if (J->trytop == JS_TRYLIMIT) { 8 | J->stack[J->top].t.type = JS_TLITSTR; 9 | J->stack[J->top].u.litstr = "exception stack overflow"; 10 | ++J->top; 11 | return 1; 12 | } 13 | return 0; 14 | } 15 | 16 | static void *js_defaultalloc(void *actx, void *ptr, int size) 17 | { 18 | if (size == 0) { 19 | free(ptr); 20 | return NULL; 21 | } 22 | return realloc(ptr, (size_t)size); 23 | } 24 | 25 | static void js_defaultreport(js_State *J, const char *message) 26 | { 27 | fputs(message, stderr); 28 | fputc('\n', stderr); 29 | } 30 | 31 | static void js_defaultpanic(js_State *J) 32 | { 33 | js_report(J, "uncaught exception"); 34 | /* return to javascript to abort */ 35 | } 36 | 37 | int js_ploadstring(js_State *J, const char *filename, const char *source) 38 | { 39 | if (js_ptry(J)) 40 | return 1; 41 | if (js_try(J)) 42 | return 1; 43 | js_loadstring(J, filename, source); 44 | js_endtry(J); 45 | return 0; 46 | } 47 | 48 | int js_ploadfile(js_State *J, const char *filename) 49 | { 50 | if (js_ptry(J)) 51 | return 1; 52 | if (js_try(J)) 53 | return 1; 54 | js_loadfile(J, filename); 55 | js_endtry(J); 56 | return 0; 57 | } 58 | 59 | const char *js_trystring(js_State *J, int idx, const char *error) 60 | { 61 | const char *s; 62 | if (js_ptry(J)) { 63 | js_pop(J, 1); 64 | return error; 65 | } 66 | if (js_try(J)) { 67 | js_pop(J, 1); 68 | return error; 69 | } 70 | s = js_tostring(J, idx); 71 | js_endtry(J); 72 | return s; 73 | } 74 | 75 | double js_trynumber(js_State *J, int idx, double error) 76 | { 77 | double v; 78 | if (js_ptry(J)) { 79 | js_pop(J, 1); 80 | return error; 81 | } 82 | if (js_try(J)) { 83 | js_pop(J, 1); 84 | return error; 85 | } 86 | v = js_tonumber(J, idx); 87 | js_endtry(J); 88 | return v; 89 | } 90 | 91 | int js_tryinteger(js_State *J, int idx, int error) 92 | { 93 | int v; 94 | if (js_ptry(J)) { 95 | js_pop(J, 1); 96 | return error; 97 | } 98 | if (js_try(J)) { 99 | js_pop(J, 1); 100 | return error; 101 | } 102 | v = js_tointeger(J, idx); 103 | js_endtry(J); 104 | return v; 105 | } 106 | 107 | int js_tryboolean(js_State *J, int idx, int error) 108 | { 109 | int v; 110 | if (js_ptry(J)) { 111 | js_pop(J, 1); 112 | return error; 113 | } 114 | if (js_try(J)) { 115 | js_pop(J, 1); 116 | return error; 117 | } 118 | v = js_toboolean(J, idx); 119 | js_endtry(J); 120 | return v; 121 | } 122 | 123 | static void js_loadstringx(js_State *J, const char *filename, const char *source, int iseval) 124 | { 125 | js_Ast *P; 126 | js_Function *F; 127 | 128 | if (js_try(J)) { 129 | jsP_freeparse(J); 130 | js_throw(J); 131 | } 132 | 133 | P = jsP_parse(J, filename, source); 134 | F = jsC_compilescript(J, P, iseval ? J->strict : J->default_strict); 135 | jsP_freeparse(J); 136 | js_newscript(J, F, iseval ? (J->strict ? J->E : NULL) : J->GE); 137 | 138 | js_endtry(J); 139 | } 140 | 141 | void js_loadeval(js_State *J, const char *filename, const char *source) 142 | { 143 | js_loadstringx(J, filename, source, 1); 144 | } 145 | 146 | void js_loadstring(js_State *J, const char *filename, const char *source) 147 | { 148 | js_loadstringx(J, filename, source, 0); 149 | } 150 | 151 | void js_loadfile(js_State *J, const char *filename) 152 | { 153 | FILE *f; 154 | char *s, *p; 155 | int n, t; 156 | 157 | f = fopen(filename, "rb"); 158 | if (!f) { 159 | js_error(J, "cannot open file '%s': %s", filename, strerror(errno)); 160 | } 161 | 162 | if (fseek(f, 0, SEEK_END) < 0) { 163 | fclose(f); 164 | js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); 165 | } 166 | 167 | n = ftell(f); 168 | if (n < 0) { 169 | fclose(f); 170 | js_error(J, "cannot tell in file '%s': %s", filename, strerror(errno)); 171 | } 172 | 173 | if (fseek(f, 0, SEEK_SET) < 0) { 174 | fclose(f); 175 | js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); 176 | } 177 | 178 | if (js_try(J)) { 179 | fclose(f); 180 | js_throw(J); 181 | } 182 | s = js_malloc(J, n + 1); /* add space for string terminator */ 183 | js_endtry(J); 184 | 185 | t = fread(s, 1, (size_t)n, f); 186 | if (t != n) { 187 | js_free(J, s); 188 | fclose(f); 189 | js_error(J, "cannot read data from file '%s': %s", filename, strerror(errno)); 190 | } 191 | 192 | s[n] = 0; /* zero-terminate string containing file data */ 193 | 194 | if (js_try(J)) { 195 | js_free(J, s); 196 | fclose(f); 197 | js_throw(J); 198 | } 199 | 200 | /* skip first line if it starts with "#!" */ 201 | p = s; 202 | if (p[0] == '#' && p[1] == '!') { 203 | p += 2; 204 | while (*p && *p != '\n') 205 | ++p; 206 | } 207 | 208 | js_loadstring(J, filename, p); 209 | 210 | js_free(J, s); 211 | fclose(f); 212 | js_endtry(J); 213 | } 214 | 215 | int js_dostring(js_State *J, const char *source) 216 | { 217 | if (js_ptry(J)) { 218 | js_report(J, "exception stack overflow"); 219 | js_pop(J, 1); 220 | return 1; 221 | } 222 | if (js_try(J)) { 223 | js_report(J, js_trystring(J, -1, "Error")); 224 | js_pop(J, 1); 225 | return 1; 226 | } 227 | js_loadstring(J, "[string]", source); 228 | js_pushundefined(J); 229 | js_call(J, 0); 230 | js_pop(J, 1); 231 | js_endtry(J); 232 | return 0; 233 | } 234 | 235 | int js_dofile(js_State *J, const char *filename) 236 | { 237 | if (js_ptry(J)) { 238 | js_report(J, "exception stack overflow"); 239 | js_pop(J, 1); 240 | return 1; 241 | } 242 | if (js_try(J)) { 243 | js_report(J, js_trystring(J, -1, "Error")); 244 | js_pop(J, 1); 245 | return 1; 246 | } 247 | js_loadfile(J, filename); 248 | js_pushundefined(J); 249 | js_call(J, 0); 250 | js_pop(J, 1); 251 | js_endtry(J); 252 | return 0; 253 | } 254 | 255 | js_Panic js_atpanic(js_State *J, js_Panic panic) 256 | { 257 | js_Panic old = J->panic; 258 | J->panic = panic; 259 | return old; 260 | } 261 | 262 | void js_report(js_State *J, const char *message) 263 | { 264 | if (J->report) 265 | J->report(J, message); 266 | } 267 | 268 | void js_setreport(js_State *J, js_Report report) 269 | { 270 | J->report = report; 271 | } 272 | 273 | void js_setcontext(js_State *J, void *uctx) 274 | { 275 | J->uctx = uctx; 276 | } 277 | 278 | void *js_getcontext(js_State *J) 279 | { 280 | return J->uctx; 281 | } 282 | 283 | js_State *js_newstate(js_Alloc alloc, void *actx, int flags) 284 | { 285 | js_State *J; 286 | 287 | assert(sizeof(js_Value) == 16); 288 | assert(soffsetof(js_Value, t.type) == 15); 289 | 290 | if (!alloc) 291 | alloc = js_defaultalloc; 292 | 293 | J = alloc(actx, NULL, sizeof *J); 294 | if (!J) 295 | return NULL; 296 | memset(J, 0, sizeof(*J)); 297 | J->actx = actx; 298 | J->alloc = alloc; 299 | 300 | if (flags & JS_STRICT) 301 | J->strict = J->default_strict = 1; 302 | 303 | J->trace[0].name = "-top-"; 304 | J->trace[0].file = "native"; 305 | J->trace[0].line = 0; 306 | 307 | J->report = js_defaultreport; 308 | J->panic = js_defaultpanic; 309 | 310 | J->stack = alloc(actx, NULL, JS_STACKSIZE * sizeof *J->stack); 311 | if (!J->stack) { 312 | alloc(actx, J, 0); 313 | return NULL; 314 | } 315 | 316 | J->gcmark = 1; 317 | J->nextref = 0; 318 | J->gcthresh = 0; /* reaches stability within ~ 2-5 GC cycles */ 319 | 320 | if (js_try(J)) { 321 | js_freestate(J); 322 | return NULL; 323 | } 324 | 325 | J->R = jsV_newobject(J, JS_COBJECT, NULL); 326 | J->G = jsV_newobject(J, JS_COBJECT, NULL); 327 | J->E = jsR_newenvironment(J, J->G, NULL); 328 | J->GE = J->E; 329 | 330 | jsB_init(J); 331 | 332 | js_endtry(J); 333 | return J; 334 | } 335 | -------------------------------------------------------------------------------- /jsstring.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | #include "utf.h" 3 | #include "regexp.h" 4 | 5 | static int js_doregexec(js_State *J, Reprog *prog, const char *string, Resub *sub, int eflags) 6 | { 7 | int result = js_regexec(prog, string, sub, eflags); 8 | if (result < 0) 9 | js_error(J, "regexec failed"); 10 | return result; 11 | } 12 | 13 | static const char *checkstring(js_State *J, int idx) 14 | { 15 | if (!js_iscoercible(J, idx)) 16 | js_typeerror(J, "string function called on null or undefined"); 17 | return js_tostring(J, idx); 18 | } 19 | 20 | int js_runeat(js_State *J, const char *s, int i) 21 | { 22 | Rune rune = EOF; 23 | while (i >= 0) { 24 | rune = *(unsigned char*)s; 25 | if (rune < Runeself) { 26 | if (rune == 0) 27 | return EOF; 28 | ++s; 29 | --i; 30 | } else { 31 | s += chartorune(&rune, s); 32 | if (rune >= 0x10000) 33 | i -= 2; 34 | else 35 | --i; 36 | } 37 | } 38 | if (rune >= 0x10000) { 39 | /* high surrogate */ 40 | if (i == -2) 41 | return 0xd800 + ((rune - 0x10000) >> 10); 42 | /* low surrogate */ 43 | else 44 | return 0xdc00 + ((rune - 0x10000) & 0x3ff); 45 | } 46 | return rune; 47 | } 48 | 49 | int js_utflen(const char *s) 50 | { 51 | int c; 52 | int n; 53 | Rune rune; 54 | 55 | n = 0; 56 | for(;;) { 57 | c = *(unsigned char *)s; 58 | if (c < Runeself) { 59 | if (c == 0) 60 | return n; 61 | s++; 62 | n++; 63 | } else { 64 | s += chartorune(&rune, s); 65 | if (rune >= 0x10000) 66 | n += 2; 67 | else 68 | n++; 69 | } 70 | } 71 | } 72 | 73 | int js_utfptrtoidx(const char *s, const char *p) 74 | { 75 | Rune rune; 76 | int i = 0; 77 | while (s < p) { 78 | if (*(unsigned char *)s < Runeself) 79 | ++s; 80 | else 81 | s += chartorune(&rune, s); 82 | if (rune >= 0x10000) 83 | i += 2; 84 | else 85 | i += 1; 86 | } 87 | return i; 88 | } 89 | 90 | static void jsB_new_String(js_State *J) 91 | { 92 | js_newstring(J, js_gettop(J) > 1 ? js_tostring(J, 1) : ""); 93 | } 94 | 95 | static void jsB_String(js_State *J) 96 | { 97 | js_pushstring(J, js_gettop(J) > 1 ? js_tostring(J, 1) : ""); 98 | } 99 | 100 | static void Sp_toString(js_State *J) 101 | { 102 | js_Object *self = js_toobject(J, 0); 103 | if (self->type != JS_CSTRING) js_typeerror(J, "not a string"); 104 | js_pushstring(J, self->u.s.string); 105 | } 106 | 107 | static void Sp_valueOf(js_State *J) 108 | { 109 | js_Object *self = js_toobject(J, 0); 110 | if (self->type != JS_CSTRING) js_typeerror(J, "not a string"); 111 | js_pushstring(J, self->u.s.string); 112 | } 113 | 114 | static void Sp_charAt(js_State *J) 115 | { 116 | char buf[UTFmax + 1]; 117 | const char *s = checkstring(J, 0); 118 | int pos = js_tointeger(J, 1); 119 | Rune rune = js_runeat(J, s, pos); 120 | if (rune >= 0) { 121 | buf[runetochar(buf, &rune)] = 0; 122 | js_pushstring(J, buf); 123 | } else { 124 | js_pushliteral(J, ""); 125 | } 126 | } 127 | 128 | static void Sp_charCodeAt(js_State *J) 129 | { 130 | const char *s = checkstring(J, 0); 131 | int pos = js_tointeger(J, 1); 132 | Rune rune = js_runeat(J, s, pos); 133 | if (rune >= 0) 134 | js_pushnumber(J, rune); 135 | else 136 | js_pushnumber(J, NAN); 137 | } 138 | 139 | static void Sp_concat(js_State *J) 140 | { 141 | int i, top = js_gettop(J); 142 | int n; 143 | char * volatile out = NULL; 144 | const char *s; 145 | 146 | if (top == 1) 147 | return; 148 | 149 | s = checkstring(J, 0); 150 | n = 1 + strlen(s); 151 | 152 | if (js_try(J)) { 153 | js_free(J, out); 154 | js_throw(J); 155 | } 156 | 157 | if (n > JS_STRLIMIT) 158 | js_rangeerror(J, "invalid string length"); 159 | out = js_malloc(J, n); 160 | strcpy(out, s); 161 | 162 | for (i = 1; i < top; ++i) { 163 | s = js_tostring(J, i); 164 | n += strlen(s); 165 | if (n > JS_STRLIMIT) 166 | js_rangeerror(J, "invalid string length"); 167 | out = js_realloc(J, out, n); 168 | strcat(out, s); 169 | } 170 | 171 | js_pushstring(J, out); 172 | js_endtry(J); 173 | js_free(J, out); 174 | } 175 | 176 | static void Sp_indexOf(js_State *J) 177 | { 178 | const char *haystack = checkstring(J, 0); 179 | const char *needle = js_tostring(J, 1); 180 | int pos = js_tointeger(J, 2); 181 | int len = strlen(needle); 182 | int k = 0; 183 | Rune rune; 184 | while (*haystack) { 185 | if (k >= pos && !strncmp(haystack, needle, len)) { 186 | js_pushnumber(J, k); 187 | return; 188 | } 189 | haystack += chartorune(&rune, haystack); 190 | ++k; 191 | } 192 | js_pushnumber(J, -1); 193 | } 194 | 195 | static void Sp_lastIndexOf(js_State *J) 196 | { 197 | const char *haystack = checkstring(J, 0); 198 | const char *needle = js_tostring(J, 1); 199 | int pos = js_isdefined(J, 2) ? js_tointeger(J, 2) : (int)strlen(haystack); 200 | int len = strlen(needle); 201 | int k = 0, last = -1; 202 | Rune rune; 203 | while (*haystack && k <= pos) { 204 | if (!strncmp(haystack, needle, len)) 205 | last = k; 206 | haystack += chartorune(&rune, haystack); 207 | ++k; 208 | } 209 | js_pushnumber(J, last); 210 | } 211 | 212 | static void Sp_localeCompare(js_State *J) 213 | { 214 | const char *a = checkstring(J, 0); 215 | const char *b = js_tostring(J, 1); 216 | js_pushnumber(J, strcmp(a, b)); 217 | } 218 | 219 | static void Sp_substring_imp(js_State *J, const char *s, int a, int n) 220 | { 221 | Rune head_rune = 0, tail_rune = 0; 222 | const char *head, *tail; 223 | char *p; 224 | int i, k, head_len, tail_len; 225 | 226 | /* find start of substring */ 227 | head = s; 228 | for (i = 0; i < a; ++i) { 229 | head += chartorune(&head_rune, head); 230 | if (head_rune >= 0x10000) 231 | ++i; 232 | } 233 | 234 | /* find end of substring */ 235 | tail = head; 236 | for (k = i - a; k < n; ++k) { 237 | tail += chartorune(&tail_rune, tail); 238 | if (tail_rune >= 0x10000) 239 | ++k; 240 | } 241 | 242 | /* no surrogate pair splits! */ 243 | if (i == a && k == n) { 244 | js_pushlstring(J, head, tail - head); 245 | return; 246 | } 247 | 248 | if (js_try(J)) { 249 | js_free(J, p); 250 | js_throw(J); 251 | } 252 | 253 | p = js_malloc(J, UTFmax + (tail - head)); 254 | 255 | /* substring starts with low surrogate (head is just after character) */ 256 | if (i > a) { 257 | head_rune = 0xdc00 + ((head_rune - 0x10000) & 0x3ff); 258 | head_len = runetochar(p, &head_rune); 259 | memcpy(p + head_len, head, tail - head); 260 | js_pushlstring(J, p, head_len + (tail - head)); 261 | } 262 | 263 | /* substring ends with high surrogate (tail is just after character) */ 264 | if (k > n) { 265 | tail -= runelen(tail_rune); 266 | memcpy(p, head, tail - head); 267 | tail_rune = 0xd800 + ((tail_rune - 0x10000) >> 10); 268 | tail_len = runetochar(p + (tail - head), &tail_rune); 269 | js_pushlstring(J, p, (tail - head) + tail_len); 270 | } 271 | 272 | js_endtry(J); 273 | js_free(J, p); 274 | } 275 | 276 | static void Sp_slice(js_State *J) 277 | { 278 | const char *str = checkstring(J, 0); 279 | int len = js_utflen(str); 280 | int s = js_tointeger(J, 1); 281 | int e = js_isdefined(J, 2) ? js_tointeger(J, 2) : len; 282 | 283 | s = s < 0 ? s + len : s; 284 | e = e < 0 ? e + len : e; 285 | 286 | s = s < 0 ? 0 : s > len ? len : s; 287 | e = e < 0 ? 0 : e > len ? len : e; 288 | 289 | if (s < e) 290 | Sp_substring_imp(J, str, s, e - s); 291 | else 292 | Sp_substring_imp(J, str, e, s - e); 293 | } 294 | 295 | static void Sp_substring(js_State *J) 296 | { 297 | const char *str = checkstring(J, 0); 298 | int len = js_utflen(str); 299 | int s = js_tointeger(J, 1); 300 | int e = js_isdefined(J, 2) ? js_tointeger(J, 2) : len; 301 | 302 | s = s < 0 ? 0 : s > len ? len : s; 303 | e = e < 0 ? 0 : e > len ? len : e; 304 | 305 | if (s < e) 306 | Sp_substring_imp(J, str, s, e - s); 307 | else 308 | Sp_substring_imp(J, str, e, s - e); 309 | } 310 | 311 | static void Sp_toLowerCase(js_State *J) 312 | { 313 | const char *s, *s0 = checkstring(J, 0); 314 | char * volatile dst = NULL; 315 | char *d; 316 | Rune rune; 317 | const Rune *full; 318 | int n; 319 | 320 | n = 1; 321 | for (s = s0; *s;) { 322 | s += chartorune(&rune, s); 323 | full = tolowerrune_full(rune); 324 | if (full) { 325 | while (*full) { 326 | n += runelen(*full); 327 | ++full; 328 | } 329 | } else { 330 | rune = tolowerrune(rune); 331 | n += runelen(rune); 332 | } 333 | } 334 | 335 | if (js_try(J)) { 336 | js_free(J, dst); 337 | js_throw(J); 338 | } 339 | 340 | d = dst = js_malloc(J, n); 341 | for (s = s0; *s;) { 342 | s += chartorune(&rune, s); 343 | full = tolowerrune_full(rune); 344 | if (full) { 345 | while (*full) { 346 | d += runetochar(d, full); 347 | ++full; 348 | } 349 | } else { 350 | rune = tolowerrune(rune); 351 | d += runetochar(d, &rune); 352 | } 353 | } 354 | *d = 0; 355 | 356 | js_pushstring(J, dst); 357 | js_endtry(J); 358 | js_free(J, dst); 359 | } 360 | 361 | static void Sp_toUpperCase(js_State *J) 362 | { 363 | const char *s, *s0 = checkstring(J, 0); 364 | char * volatile dst = NULL; 365 | char *d; 366 | const Rune *full; 367 | Rune rune; 368 | int n; 369 | 370 | n = 1; 371 | for (s = s0; *s;) { 372 | s += chartorune(&rune, s); 373 | full = toupperrune_full(rune); 374 | if (full) { 375 | while (*full) { 376 | n += runelen(*full); 377 | ++full; 378 | } 379 | } else { 380 | rune = toupperrune(rune); 381 | n += runelen(rune); 382 | } 383 | } 384 | 385 | if (js_try(J)) { 386 | js_free(J, dst); 387 | js_throw(J); 388 | } 389 | 390 | d = dst = js_malloc(J, n); 391 | for (s = s0; *s;) { 392 | s += chartorune(&rune, s); 393 | full = toupperrune_full(rune); 394 | if (full) { 395 | while (*full) { 396 | d += runetochar(d, full); 397 | ++full; 398 | } 399 | } else { 400 | rune = toupperrune(rune); 401 | d += runetochar(d, &rune); 402 | } 403 | } 404 | *d = 0; 405 | 406 | js_pushstring(J, dst); 407 | js_endtry(J); 408 | js_free(J, dst); 409 | } 410 | 411 | static int istrim(int c) 412 | { 413 | return c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0 || c == 0xFEFF || 414 | c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029; 415 | } 416 | 417 | static void Sp_trim(js_State *J) 418 | { 419 | const char *s, *e; 420 | s = checkstring(J, 0); 421 | while (istrim(*s)) 422 | ++s; 423 | e = s + strlen(s); 424 | while (e > s && istrim(e[-1])) 425 | --e; 426 | js_pushlstring(J, s, e - s); 427 | } 428 | 429 | static void S_fromCharCode(js_State *J) 430 | { 431 | int i, top = js_gettop(J); 432 | char * volatile s = NULL; 433 | char *p; 434 | Rune c; 435 | 436 | if (js_try(J)) { 437 | js_free(J, s); 438 | js_throw(J); 439 | } 440 | 441 | s = p = js_malloc(J, (top-1) * UTFmax + 1); 442 | 443 | for (i = 1; i < top; ++i) { 444 | c = js_touint32(J, i); 445 | p += runetochar(p, &c); 446 | } 447 | *p = 0; 448 | 449 | js_pushstring(J, s); 450 | js_endtry(J); 451 | js_free(J, s); 452 | } 453 | 454 | static void Sp_match(js_State *J) 455 | { 456 | js_Regexp *re; 457 | const char *text; 458 | int len; 459 | const char *a, *b, *c, *e; 460 | Resub m; 461 | 462 | text = checkstring(J, 0); 463 | 464 | if (js_isregexp(J, 1)) 465 | js_copy(J, 1); 466 | else if (js_isundefined(J, 1)) 467 | js_newregexp(J, "", 0); 468 | else 469 | js_newregexp(J, js_tostring(J, 1), 0); 470 | 471 | re = js_toregexp(J, -1); 472 | if (!(re->flags & JS_REGEXP_G)) { 473 | js_RegExp_prototype_exec(J, re, text); 474 | return; 475 | } 476 | 477 | re->last = 0; 478 | 479 | js_newarray(J); 480 | 481 | len = 0; 482 | a = text; 483 | e = text + strlen(text); 484 | while (a <= e) { 485 | if (js_doregexec(J, re->prog, a, &m, a > text ? REG_NOTBOL : 0)) 486 | break; 487 | 488 | b = m.sub[0].sp; 489 | c = m.sub[0].ep; 490 | 491 | js_pushlstring(J, b, c - b); 492 | js_setindex(J, -2, len++); 493 | 494 | a = c; 495 | if (c - b == 0) 496 | ++a; 497 | } 498 | 499 | if (len == 0) { 500 | js_pop(J, 1); 501 | js_pushnull(J); 502 | } 503 | } 504 | 505 | static void Sp_search(js_State *J) 506 | { 507 | js_Regexp *re; 508 | const char *text; 509 | Resub m; 510 | 511 | text = checkstring(J, 0); 512 | 513 | if (js_isregexp(J, 1)) 514 | js_copy(J, 1); 515 | else if (js_isundefined(J, 1)) 516 | js_newregexp(J, "", 0); 517 | else 518 | js_newregexp(J, js_tostring(J, 1), 0); 519 | 520 | re = js_toregexp(J, -1); 521 | 522 | if (!js_doregexec(J, re->prog, text, &m, 0)) 523 | js_pushnumber(J, js_utfptrtoidx(text, m.sub[0].sp)); 524 | else 525 | js_pushnumber(J, -1); 526 | } 527 | 528 | static void Sp_replace_regexp(js_State *J) 529 | { 530 | js_Regexp *re; 531 | const char *source, *s, *r; 532 | js_Buffer *sb = NULL; 533 | int n, x; 534 | Resub m; 535 | 536 | source = checkstring(J, 0); 537 | re = js_toregexp(J, 1); 538 | 539 | if (js_doregexec(J, re->prog, source, &m, 0)) { 540 | js_copy(J, 0); 541 | return; 542 | } 543 | 544 | re->last = 0; 545 | 546 | loop: 547 | s = m.sub[0].sp; 548 | n = m.sub[0].ep - m.sub[0].sp; 549 | 550 | if (js_iscallable(J, 2)) { 551 | js_copy(J, 2); 552 | js_pushundefined(J); 553 | for (x = 0; m.sub[x].sp; ++x) /* arg 0..x: substring and subexps that matched */ 554 | js_pushlstring(J, m.sub[x].sp, m.sub[x].ep - m.sub[x].sp); 555 | js_pushnumber(J, s - source); /* arg x+2: offset within search string */ 556 | js_copy(J, 0); /* arg x+3: search string */ 557 | js_call(J, 2 + x); 558 | r = js_tostring(J, -1); 559 | js_putm(J, &sb, source, s); 560 | js_puts(J, &sb, r); 561 | js_pop(J, 1); 562 | } else { 563 | r = js_tostring(J, 2); 564 | js_putm(J, &sb, source, s); 565 | while (*r) { 566 | if (*r == '$') { 567 | switch (*(++r)) { 568 | case 0: --r; /* end of string; back up */ 569 | /* fallthrough */ 570 | case '$': js_putc(J, &sb, '$'); break; 571 | case '`': js_putm(J, &sb, source, s); break; 572 | case '\'': js_puts(J, &sb, s + n); break; 573 | case '&': 574 | js_putm(J, &sb, s, s + n); 575 | break; 576 | case '0': case '1': case '2': case '3': case '4': 577 | case '5': case '6': case '7': case '8': case '9': 578 | x = *r - '0'; 579 | if (r[1] >= '0' && r[1] <= '9') 580 | x = x * 10 + *(++r) - '0'; 581 | if (x > 0 && x < m.nsub) { 582 | js_putm(J, &sb, m.sub[x].sp, m.sub[x].ep); 583 | } else { 584 | js_putc(J, &sb, '$'); 585 | if (x > 10) { 586 | js_putc(J, &sb, '0' + x / 10); 587 | js_putc(J, &sb, '0' + x % 10); 588 | } else { 589 | js_putc(J, &sb, '0' + x); 590 | } 591 | } 592 | break; 593 | default: 594 | js_putc(J, &sb, '$'); 595 | js_putc(J, &sb, *r); 596 | break; 597 | } 598 | ++r; 599 | } else { 600 | js_putc(J, &sb, *r++); 601 | } 602 | } 603 | } 604 | 605 | if (re->flags & JS_REGEXP_G) { 606 | source = m.sub[0].ep; 607 | if (n == 0) { 608 | if (*source) 609 | js_putc(J, &sb, *source++); 610 | else 611 | goto end; 612 | } 613 | if (!js_doregexec(J, re->prog, source, &m, REG_NOTBOL)) 614 | goto loop; 615 | } 616 | 617 | end: 618 | js_puts(J, &sb, s + n); 619 | js_putc(J, &sb, 0); 620 | 621 | if (js_try(J)) { 622 | js_free(J, sb); 623 | js_throw(J); 624 | } 625 | js_pushstring(J, sb ? sb->s : ""); 626 | js_endtry(J); 627 | js_free(J, sb); 628 | } 629 | 630 | static void Sp_replace_string(js_State *J) 631 | { 632 | const char *source, *needle, *s, *r; 633 | js_Buffer *sb = NULL; 634 | int n; 635 | 636 | source = checkstring(J, 0); 637 | needle = js_tostring(J, 1); 638 | 639 | s = strstr(source, needle); 640 | if (!s) { 641 | js_copy(J, 0); 642 | return; 643 | } 644 | n = strlen(needle); 645 | 646 | if (js_iscallable(J, 2)) { 647 | js_copy(J, 2); 648 | js_pushundefined(J); 649 | js_pushlstring(J, s, n); /* arg 1: substring that matched */ 650 | js_pushnumber(J, s - source); /* arg 2: offset within search string */ 651 | js_copy(J, 0); /* arg 3: search string */ 652 | js_call(J, 3); 653 | r = js_tostring(J, -1); 654 | js_putm(J, &sb, source, s); 655 | js_puts(J, &sb, r); 656 | js_puts(J, &sb, s + n); 657 | js_putc(J, &sb, 0); 658 | js_pop(J, 1); 659 | } else { 660 | r = js_tostring(J, 2); 661 | js_putm(J, &sb, source, s); 662 | while (*r) { 663 | if (*r == '$') { 664 | switch (*(++r)) { 665 | case 0: --r; /* end of string; back up */ 666 | /* fallthrough */ 667 | case '$': js_putc(J, &sb, '$'); break; 668 | case '&': js_putm(J, &sb, s, s + n); break; 669 | case '`': js_putm(J, &sb, source, s); break; 670 | case '\'': js_puts(J, &sb, s + n); break; 671 | default: js_putc(J, &sb, '$'); js_putc(J, &sb, *r); break; 672 | } 673 | ++r; 674 | } else { 675 | js_putc(J, &sb, *r++); 676 | } 677 | } 678 | js_puts(J, &sb, s + n); 679 | js_putc(J, &sb, 0); 680 | } 681 | 682 | if (js_try(J)) { 683 | js_free(J, sb); 684 | js_throw(J); 685 | } 686 | js_pushstring(J, sb ? sb->s : ""); 687 | js_endtry(J); 688 | js_free(J, sb); 689 | } 690 | 691 | static void Sp_replace(js_State *J) 692 | { 693 | if (js_isregexp(J, 1)) 694 | Sp_replace_regexp(J); 695 | else 696 | Sp_replace_string(J); 697 | } 698 | 699 | static void Sp_split_regexp(js_State *J) 700 | { 701 | js_Regexp *re; 702 | const char *text; 703 | int limit, len, k; 704 | const char *p, *a, *b, *c, *e; 705 | Resub m; 706 | 707 | text = checkstring(J, 0); 708 | re = js_toregexp(J, 1); 709 | limit = js_isdefined(J, 2) ? js_tointeger(J, 2) : 1 << 30; 710 | 711 | js_newarray(J); 712 | len = 0; 713 | 714 | if (limit == 0) 715 | return; 716 | 717 | e = text + strlen(text); 718 | 719 | /* splitting the empty string */ 720 | if (e == text) { 721 | if (js_doregexec(J, re->prog, text, &m, 0)) { 722 | js_pushliteral(J, ""); 723 | js_setindex(J, -2, 0); 724 | } 725 | return; 726 | } 727 | 728 | p = a = text; 729 | while (a < e) { 730 | if (js_doregexec(J, re->prog, a, &m, a > text ? REG_NOTBOL : 0)) 731 | break; /* no match */ 732 | 733 | b = m.sub[0].sp; 734 | c = m.sub[0].ep; 735 | 736 | /* empty string at end of last match */ 737 | if (b == c && b == p) { 738 | ++a; 739 | continue; 740 | } 741 | 742 | if (len == limit) return; 743 | js_pushlstring(J, p, b - p); 744 | js_setindex(J, -2, len++); 745 | 746 | for (k = 1; k < m.nsub; ++k) { 747 | if (len == limit) return; 748 | js_pushlstring(J, m.sub[k].sp, m.sub[k].ep - m.sub[k].sp); 749 | js_setindex(J, -2, len++); 750 | } 751 | 752 | a = p = c; 753 | } 754 | 755 | if (len == limit) return; 756 | js_pushstring(J, p); 757 | js_setindex(J, -2, len); 758 | } 759 | 760 | static void Sp_split_string(js_State *J) 761 | { 762 | const char *str = checkstring(J, 0); 763 | const char *sep = js_tostring(J, 1); 764 | int limit = js_isdefined(J, 2) ? js_tointeger(J, 2) : 1 << 30; 765 | int i, n; 766 | 767 | js_newarray(J); 768 | 769 | if (limit == 0) 770 | return; 771 | 772 | n = strlen(sep); 773 | 774 | /* empty string */ 775 | if (n == 0) { 776 | Rune rune; 777 | for (i = 0; *str && i < limit; ++i) { 778 | n = chartorune(&rune, str); 779 | js_pushlstring(J, str, n); 780 | js_setindex(J, -2, i); 781 | str += n; 782 | } 783 | return; 784 | } 785 | 786 | for (i = 0; str && i < limit; ++i) { 787 | const char *s = strstr(str, sep); 788 | if (s) { 789 | js_pushlstring(J, str, s-str); 790 | js_setindex(J, -2, i); 791 | str = s + n; 792 | } else { 793 | js_pushstring(J, str); 794 | js_setindex(J, -2, i); 795 | str = NULL; 796 | } 797 | } 798 | } 799 | 800 | static void Sp_split(js_State *J) 801 | { 802 | if (js_isundefined(J, 1)) { 803 | js_newarray(J); 804 | js_pushstring(J, js_tostring(J, 0)); 805 | js_setindex(J, -2, 0); 806 | } else if (js_isregexp(J, 1)) { 807 | Sp_split_regexp(J); 808 | } else { 809 | Sp_split_string(J); 810 | } 811 | } 812 | 813 | void jsB_initstring(js_State *J) 814 | { 815 | J->String_prototype->u.s.shrstr[0] = 0; 816 | J->String_prototype->u.s.string = J->String_prototype->u.s.shrstr; 817 | J->String_prototype->u.s.length = 0; 818 | 819 | js_pushobject(J, J->String_prototype); 820 | { 821 | jsB_propf(J, "String.prototype.toString", Sp_toString, 0); 822 | jsB_propf(J, "String.prototype.valueOf", Sp_valueOf, 0); 823 | jsB_propf(J, "String.prototype.charAt", Sp_charAt, 1); 824 | jsB_propf(J, "String.prototype.charCodeAt", Sp_charCodeAt, 1); 825 | jsB_propf(J, "String.prototype.concat", Sp_concat, 0); /* 1 */ 826 | jsB_propf(J, "String.prototype.indexOf", Sp_indexOf, 1); 827 | jsB_propf(J, "String.prototype.lastIndexOf", Sp_lastIndexOf, 1); 828 | jsB_propf(J, "String.prototype.localeCompare", Sp_localeCompare, 1); 829 | jsB_propf(J, "String.prototype.match", Sp_match, 1); 830 | jsB_propf(J, "String.prototype.replace", Sp_replace, 2); 831 | jsB_propf(J, "String.prototype.search", Sp_search, 1); 832 | jsB_propf(J, "String.prototype.slice", Sp_slice, 2); 833 | jsB_propf(J, "String.prototype.split", Sp_split, 2); 834 | jsB_propf(J, "String.prototype.substring", Sp_substring, 2); 835 | jsB_propf(J, "String.prototype.toLowerCase", Sp_toLowerCase, 0); 836 | jsB_propf(J, "String.prototype.toLocaleLowerCase", Sp_toLowerCase, 0); 837 | jsB_propf(J, "String.prototype.toUpperCase", Sp_toUpperCase, 0); 838 | jsB_propf(J, "String.prototype.toLocaleUpperCase", Sp_toUpperCase, 0); 839 | 840 | /* ES5 */ 841 | jsB_propf(J, "String.prototype.trim", Sp_trim, 0); 842 | } 843 | js_newcconstructor(J, jsB_String, jsB_new_String, "String", 0); /* 1 */ 844 | { 845 | jsB_propf(J, "String.fromCharCode", S_fromCharCode, 0); /* 1 */ 846 | } 847 | js_defglobal(J, "String", JS_DONTENUM); 848 | } 849 | -------------------------------------------------------------------------------- /jsvalue.c: -------------------------------------------------------------------------------- 1 | #include "jsi.h" 2 | #include "utf.h" 3 | 4 | #define JSV_ISSTRING(v) (v->t.type==JS_TSHRSTR || v->t.type==JS_TMEMSTR || v->t.type==JS_TLITSTR) 5 | #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 : "") 6 | 7 | double js_strtol(const char *s, char **p, int base) 8 | { 9 | /* ascii -> digit value. max base is 36. */ 10 | static const unsigned char table[256] = { 11 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 12 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 13 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 14 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 80, 80, 80, 80, 80, 80, 15 | 80, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 16 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 80, 80, 80, 80, 80, 17 | 80, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 18 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 80, 80, 80, 80, 80, 19 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 20 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 21 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 22 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 23 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 24 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 25 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 26 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80 27 | }; 28 | double x; 29 | unsigned char c; 30 | if (base == 10) 31 | for (x = 0, c = *s++; (0 <= c - '0') && (c - '0' < 10); c = *s++) 32 | x = x * 10 + (c - '0'); 33 | else 34 | for (x = 0, c = *s++; table[c] < base; c = *s++) 35 | x = x * base + table[c]; 36 | if (p) 37 | *p = (char*)s-1; 38 | return x; 39 | } 40 | 41 | int jsV_numbertointeger(double n) 42 | { 43 | if (n == 0) return 0; 44 | if (isnan(n)) return 0; 45 | n = (n < 0) ? -floor(-n) : floor(n); 46 | if (n < INT_MIN) return INT_MIN; 47 | if (n > INT_MAX) return INT_MAX; 48 | return (int)n; 49 | } 50 | 51 | int jsV_numbertoint32(double n) 52 | { 53 | double two32 = 4294967296.0; 54 | double two31 = 2147483648.0; 55 | 56 | if (!isfinite(n) || n == 0) 57 | return 0; 58 | 59 | n = fmod(n, two32); 60 | n = n >= 0 ? floor(n) : ceil(n) + two32; 61 | if (n >= two31) 62 | return n - two32; 63 | else 64 | return n; 65 | } 66 | 67 | unsigned int jsV_numbertouint32(double n) 68 | { 69 | return (unsigned int)jsV_numbertoint32(n); 70 | } 71 | 72 | short jsV_numbertoint16(double n) 73 | { 74 | return jsV_numbertoint32(n); 75 | } 76 | 77 | unsigned short jsV_numbertouint16(double n) 78 | { 79 | return jsV_numbertoint32(n); 80 | } 81 | 82 | /* obj.toString() */ 83 | static int jsV_toString(js_State *J, js_Object *obj) 84 | { 85 | js_pushobject(J, obj); 86 | js_getproperty(J, -1, "toString"); 87 | if (js_iscallable(J, -1)) { 88 | js_rot2(J); 89 | js_call(J, 0); 90 | if (js_isprimitive(J, -1)) 91 | return 1; 92 | js_pop(J, 1); 93 | return 0; 94 | } 95 | js_pop(J, 2); 96 | return 0; 97 | } 98 | 99 | /* obj.valueOf() */ 100 | static int jsV_valueOf(js_State *J, js_Object *obj) 101 | { 102 | js_pushobject(J, obj); 103 | js_getproperty(J, -1, "valueOf"); 104 | if (js_iscallable(J, -1)) { 105 | js_rot2(J); 106 | js_call(J, 0); 107 | if (js_isprimitive(J, -1)) 108 | return 1; 109 | js_pop(J, 1); 110 | return 0; 111 | } 112 | js_pop(J, 2); 113 | return 0; 114 | } 115 | 116 | /* ToPrimitive() on a value */ 117 | void jsV_toprimitive(js_State *J, js_Value *v, int preferred) 118 | { 119 | js_Object *obj; 120 | 121 | if (v->t.type != JS_TOBJECT) 122 | return; 123 | 124 | obj = v->u.object; 125 | 126 | if (preferred == JS_HNONE) 127 | preferred = obj->type == JS_CDATE ? JS_HSTRING : JS_HNUMBER; 128 | 129 | if (preferred == JS_HSTRING) { 130 | if (jsV_toString(J, obj) || jsV_valueOf(J, obj)) { 131 | *v = *js_tovalue(J, -1); 132 | js_pop(J, 1); 133 | return; 134 | } 135 | } else { 136 | if (jsV_valueOf(J, obj) || jsV_toString(J, obj)) { 137 | *v = *js_tovalue(J, -1); 138 | js_pop(J, 1); 139 | return; 140 | } 141 | } 142 | 143 | if (J->strict) 144 | js_typeerror(J, "cannot convert object to primitive"); 145 | 146 | v->t.type = JS_TLITSTR; 147 | v->u.litstr = "[object]"; 148 | return; 149 | } 150 | 151 | /* ToBoolean() on a value */ 152 | int jsV_toboolean(js_State *J, js_Value *v) 153 | { 154 | switch (v->t.type) { 155 | default: 156 | case JS_TSHRSTR: return v->u.shrstr[0] != 0; 157 | case JS_TUNDEFINED: return 0; 158 | case JS_TNULL: return 0; 159 | case JS_TBOOLEAN: return v->u.boolean; 160 | case JS_TNUMBER: return v->u.number != 0 && !isnan(v->u.number); 161 | case JS_TLITSTR: return v->u.litstr[0] != 0; 162 | case JS_TMEMSTR: return v->u.memstr->p[0] != 0; 163 | case JS_TOBJECT: return 1; 164 | } 165 | } 166 | 167 | const char *js_itoa(char *out, int v) 168 | { 169 | char buf[32], *s = out; 170 | unsigned int a; 171 | int i = 0; 172 | if (v < 0) { 173 | a = -v; 174 | *s++ = '-'; 175 | } else { 176 | a = v; 177 | } 178 | while (a) { 179 | buf[i++] = (a % 10) + '0'; 180 | a /= 10; 181 | } 182 | if (i == 0) 183 | buf[i++] = '0'; 184 | while (i > 0) 185 | *s++ = buf[--i]; 186 | *s = 0; 187 | return out; 188 | } 189 | 190 | double js_stringtofloat(const char *s, char **ep) 191 | { 192 | char *end; 193 | double n; 194 | const char *e = s; 195 | int isflt = 0; 196 | if (*e == '+' || *e == '-') ++e; 197 | while (*e >= '0' && *e <= '9') ++e; 198 | if (*e == '.') { ++e; isflt = 1; } 199 | while (*e >= '0' && *e <= '9') ++e; 200 | if (*e == 'e' || *e == 'E') { 201 | ++e; 202 | if (*e == '+' || *e == '-') ++e; 203 | while (*e >= '0' && *e <= '9') ++e; 204 | isflt = 1; 205 | } 206 | if (isflt) 207 | n = js_strtod(s, &end); 208 | else { 209 | /* js_strtol doesn't parse the sign */ 210 | if (*s == '-') 211 | n = -js_strtol(s+1, &end, 10); 212 | else if (*s == '+') 213 | n = js_strtol(s+1, &end, 10); 214 | else 215 | n = js_strtol(s, &end, 10); 216 | } 217 | if (end == e) { 218 | *ep = (char*)e; 219 | return n; 220 | } 221 | *ep = (char*)s; 222 | return 0; 223 | } 224 | 225 | /* ToNumber() on a string */ 226 | double jsV_stringtonumber(js_State *J, const char *s) 227 | { 228 | char *e; 229 | double n; 230 | while (jsY_iswhite(*s) || jsY_isnewline(*s)) ++s; 231 | if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && s[2] != 0) 232 | n = js_strtol(s + 2, &e, 16); 233 | else if (!strncmp(s, "Infinity", 8)) 234 | n = INFINITY, e = (char*)s + 8; 235 | else if (!strncmp(s, "+Infinity", 9)) 236 | n = INFINITY, e = (char*)s + 9; 237 | else if (!strncmp(s, "-Infinity", 9)) 238 | n = -INFINITY, e = (char*)s + 9; 239 | else 240 | n = js_stringtofloat(s, &e); 241 | while (jsY_iswhite(*e) || jsY_isnewline(*e)) ++e; 242 | if (*e) return NAN; 243 | return n; 244 | } 245 | 246 | /* ToNumber() on a value */ 247 | double jsV_tonumber(js_State *J, js_Value *v) 248 | { 249 | switch (v->t.type) { 250 | default: 251 | case JS_TSHRSTR: return jsV_stringtonumber(J, v->u.shrstr); 252 | case JS_TUNDEFINED: return NAN; 253 | case JS_TNULL: return 0; 254 | case JS_TBOOLEAN: return v->u.boolean; 255 | case JS_TNUMBER: return v->u.number; 256 | case JS_TLITSTR: return jsV_stringtonumber(J, v->u.litstr); 257 | case JS_TMEMSTR: return jsV_stringtonumber(J, v->u.memstr->p); 258 | case JS_TOBJECT: 259 | jsV_toprimitive(J, v, JS_HNUMBER); 260 | return jsV_tonumber(J, v); 261 | } 262 | } 263 | 264 | double jsV_tointeger(js_State *J, js_Value *v) 265 | { 266 | return jsV_numbertointeger(jsV_tonumber(J, v)); 267 | } 268 | 269 | /* ToString() on a number */ 270 | const char *jsV_numbertostring(js_State *J, char buf[32], double f) 271 | { 272 | char digits[32], *p = buf, *s = digits; 273 | int exp, ndigits, point; 274 | 275 | if (f == 0) return "0"; 276 | if (isnan(f)) return "NaN"; 277 | if (isinf(f)) return f < 0 ? "-Infinity" : "Infinity"; 278 | 279 | /* Fast case for integers. This only works assuming all integers can be 280 | * exactly represented by a float. This is true for 32-bit integers and 281 | * 64-bit floats. */ 282 | if (f >= INT_MIN && f <= INT_MAX) { 283 | int i = (int)f; 284 | if ((double)i == f) 285 | return js_itoa(buf, i); 286 | } 287 | 288 | ndigits = js_grisu2(f, digits, &exp); 289 | point = ndigits + exp; 290 | 291 | if (signbit(f)) 292 | *p++ = '-'; 293 | 294 | if (point < -5 || point > 21) { 295 | *p++ = *s++; 296 | if (ndigits > 1) { 297 | int n = ndigits - 1; 298 | *p++ = '.'; 299 | while (n--) 300 | *p++ = *s++; 301 | } 302 | js_fmtexp(p, point - 1); 303 | } 304 | 305 | else if (point <= 0) { 306 | *p++ = '0'; 307 | *p++ = '.'; 308 | while (point++ < 0) 309 | *p++ = '0'; 310 | while (ndigits-- > 0) 311 | *p++ = *s++; 312 | *p = 0; 313 | } 314 | 315 | else { 316 | while (ndigits-- > 0) { 317 | *p++ = *s++; 318 | if (--point == 0 && ndigits > 0) 319 | *p++ = '.'; 320 | } 321 | while (point-- > 0) 322 | *p++ = '0'; 323 | *p = 0; 324 | } 325 | 326 | return buf; 327 | } 328 | 329 | /* ToString() on a value */ 330 | const char *jsV_tostring(js_State *J, js_Value *v) 331 | { 332 | char buf[32]; 333 | const char *p; 334 | switch (v->t.type) { 335 | default: 336 | case JS_TSHRSTR: return v->u.shrstr; 337 | case JS_TUNDEFINED: return "undefined"; 338 | case JS_TNULL: return "null"; 339 | case JS_TBOOLEAN: return v->u.boolean ? "true" : "false"; 340 | case JS_TLITSTR: return v->u.litstr; 341 | case JS_TMEMSTR: return v->u.memstr->p; 342 | case JS_TNUMBER: 343 | p = jsV_numbertostring(J, buf, v->u.number); 344 | if (p == buf) { 345 | int n = strlen(p); 346 | if (n <= soffsetof(js_Value, t.type)) { 347 | char *s = v->u.shrstr; 348 | while (n--) *s++ = *p++; 349 | *s = 0; 350 | v->t.type = JS_TSHRSTR; 351 | return v->u.shrstr; 352 | } else { 353 | v->u.memstr = jsV_newmemstring(J, p, n); 354 | v->t.type = JS_TMEMSTR; 355 | return v->u.memstr->p; 356 | } 357 | } 358 | return p; 359 | case JS_TOBJECT: 360 | jsV_toprimitive(J, v, JS_HSTRING); 361 | return jsV_tostring(J, v); 362 | } 363 | } 364 | 365 | /* Objects */ 366 | 367 | static js_Object *jsV_newboolean(js_State *J, int v) 368 | { 369 | js_Object *obj = jsV_newobject(J, JS_CBOOLEAN, J->Boolean_prototype); 370 | obj->u.boolean = v; 371 | return obj; 372 | } 373 | 374 | static js_Object *jsV_newnumber(js_State *J, double v) 375 | { 376 | js_Object *obj = jsV_newobject(J, JS_CNUMBER, J->Number_prototype); 377 | obj->u.number = v; 378 | return obj; 379 | } 380 | 381 | static js_Object *jsV_newstring(js_State *J, const char *v) 382 | { 383 | js_Object *obj = jsV_newobject(J, JS_CSTRING, J->String_prototype); 384 | size_t n = strlen(v); 385 | if (n < sizeof(obj->u.s.shrstr)) { 386 | obj->u.s.string = obj->u.s.shrstr; 387 | memcpy(obj->u.s.shrstr, v, n + 1); 388 | } else { 389 | obj->u.s.string = js_strdup(J, v); 390 | } 391 | obj->u.s.length = js_utflen(v); 392 | return obj; 393 | } 394 | 395 | /* ToObject() on a value */ 396 | js_Object *jsV_toobject(js_State *J, js_Value *v) 397 | { 398 | js_Object *o; 399 | switch (v->t.type) { 400 | default: 401 | case JS_TUNDEFINED: js_typeerror(J, "cannot convert undefined to object"); 402 | case JS_TNULL: js_typeerror(J, "cannot convert null to object"); 403 | case JS_TOBJECT: return v->u.object; 404 | case JS_TSHRSTR: o = jsV_newstring(J, v->u.shrstr); break; 405 | case JS_TLITSTR: o = jsV_newstring(J, v->u.litstr); break; 406 | case JS_TMEMSTR: o = jsV_newstring(J, v->u.memstr->p); break; 407 | case JS_TBOOLEAN: o = jsV_newboolean(J, v->u.boolean); break; 408 | case JS_TNUMBER: o = jsV_newnumber(J, v->u.number); break; 409 | } 410 | v->t.type = JS_TOBJECT; 411 | v->u.object = o; 412 | return o; 413 | } 414 | 415 | void js_newobjectx(js_State *J) 416 | { 417 | js_Object *prototype = NULL; 418 | if (js_isobject(J, -1)) 419 | prototype = js_toobject(J, -1); 420 | js_pop(J, 1); 421 | js_pushobject(J, jsV_newobject(J, JS_COBJECT, prototype)); 422 | } 423 | 424 | void js_newobject(js_State *J) 425 | { 426 | js_pushobject(J, jsV_newobject(J, JS_COBJECT, J->Object_prototype)); 427 | } 428 | 429 | void js_newarguments(js_State *J) 430 | { 431 | js_pushobject(J, jsV_newobject(J, JS_CARGUMENTS, J->Object_prototype)); 432 | } 433 | 434 | void js_newarray(js_State *J) 435 | { 436 | js_Object *obj = jsV_newobject(J, JS_CARRAY, J->Array_prototype); 437 | obj->u.a.simple = 1; 438 | js_pushobject(J, obj); 439 | } 440 | 441 | void js_newboolean(js_State *J, int v) 442 | { 443 | js_pushobject(J, jsV_newboolean(J, v)); 444 | } 445 | 446 | void js_newnumber(js_State *J, double v) 447 | { 448 | js_pushobject(J, jsV_newnumber(J, v)); 449 | } 450 | 451 | void js_newstring(js_State *J, const char *v) 452 | { 453 | js_pushobject(J, jsV_newstring(J, v)); 454 | } 455 | 456 | void js_newfunction(js_State *J, js_Function *fun, js_Environment *scope) 457 | { 458 | js_Object *obj = jsV_newobject(J, JS_CFUNCTION, J->Function_prototype); 459 | obj->u.f.function = fun; 460 | obj->u.f.scope = scope; 461 | js_pushobject(J, obj); 462 | { 463 | js_pushnumber(J, fun->numparams); 464 | js_defproperty(J, -2, "length", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 465 | js_newobject(J); 466 | { 467 | js_copy(J, -2); 468 | js_defproperty(J, -2, "constructor", JS_DONTENUM); 469 | } 470 | js_defproperty(J, -2, "prototype", JS_DONTENUM | JS_DONTCONF); 471 | } 472 | } 473 | 474 | void js_newscript(js_State *J, js_Function *fun, js_Environment *scope) 475 | { 476 | js_Object *obj = jsV_newobject(J, JS_CSCRIPT, NULL); 477 | obj->u.f.function = fun; 478 | obj->u.f.scope = scope; 479 | js_pushobject(J, obj); 480 | } 481 | 482 | void js_newcfunctionx(js_State *J, js_CFunction cfun, const char *name, int length, void *data, js_Finalize finalize) 483 | { 484 | js_Object *obj; 485 | 486 | if (js_try(J)) { 487 | if (finalize) 488 | finalize(J, data); 489 | js_throw(J); 490 | } 491 | 492 | obj = jsV_newobject(J, JS_CCFUNCTION, J->Function_prototype); 493 | obj->u.c.name = name; 494 | obj->u.c.function = cfun; 495 | obj->u.c.constructor = NULL; 496 | obj->u.c.length = length; 497 | obj->u.c.data = data; 498 | obj->u.c.finalize = finalize; 499 | 500 | js_endtry(J); 501 | 502 | js_pushobject(J, obj); 503 | { 504 | js_pushnumber(J, length); 505 | js_defproperty(J, -2, "length", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 506 | js_newobject(J); 507 | { 508 | js_copy(J, -2); 509 | js_defproperty(J, -2, "constructor", JS_DONTENUM); 510 | } 511 | js_defproperty(J, -2, "prototype", JS_DONTENUM | JS_DONTCONF); 512 | } 513 | } 514 | 515 | void js_newcfunction(js_State *J, js_CFunction cfun, const char *name, int length) 516 | { 517 | js_newcfunctionx(J, cfun, name, length, NULL, NULL); 518 | } 519 | 520 | /* prototype -- constructor */ 521 | void js_newcconstructor(js_State *J, js_CFunction cfun, js_CFunction ccon, const char *name, int length) 522 | { 523 | js_Object *obj = jsV_newobject(J, JS_CCFUNCTION, J->Function_prototype); 524 | obj->u.c.name = name; 525 | obj->u.c.function = cfun; 526 | obj->u.c.constructor = ccon; 527 | obj->u.c.length = length; 528 | js_pushobject(J, obj); /* proto obj */ 529 | { 530 | js_pushnumber(J, length); 531 | js_defproperty(J, -2, "length", JS_READONLY | JS_DONTENUM | JS_DONTCONF); 532 | js_rot2(J); /* obj proto */ 533 | js_copy(J, -2); /* obj proto obj */ 534 | js_defproperty(J, -2, "constructor", JS_DONTENUM); 535 | js_defproperty(J, -2, "prototype", JS_DONTENUM | JS_DONTCONF); 536 | } 537 | } 538 | 539 | void js_newuserdatax(js_State *J, const char *tag, void *data, js_HasProperty has, js_Put put, js_Delete delete, js_Finalize finalize) 540 | { 541 | js_Object *prototype = NULL; 542 | js_Object *obj; 543 | 544 | if (js_isobject(J, -1)) 545 | prototype = js_toobject(J, -1); 546 | js_pop(J, 1); 547 | 548 | if (js_try(J)) { 549 | if (finalize) 550 | finalize(J, data); 551 | js_throw(J); 552 | } 553 | 554 | obj = jsV_newobject(J, JS_CUSERDATA, prototype); 555 | obj->u.user.tag = tag; 556 | obj->u.user.data = data; 557 | obj->u.user.has = has; 558 | obj->u.user.put = put; 559 | obj->u.user.delete = delete; 560 | obj->u.user.finalize = finalize; 561 | 562 | js_endtry(J); 563 | 564 | js_pushobject(J, obj); 565 | } 566 | 567 | void js_newuserdata(js_State *J, const char *tag, void *data, js_Finalize finalize) 568 | { 569 | js_newuserdatax(J, tag, data, NULL, NULL, NULL, finalize); 570 | } 571 | 572 | /* Non-trivial operations on values. These are implemented using the stack. */ 573 | 574 | int js_instanceof(js_State *J) 575 | { 576 | js_Object *O, *V; 577 | 578 | if (!js_iscallable(J, -1)) 579 | js_typeerror(J, "instanceof: invalid operand"); 580 | 581 | if (!js_isobject(J, -2)) 582 | return 0; 583 | 584 | js_getproperty(J, -1, "prototype"); 585 | if (!js_isobject(J, -1)) 586 | js_typeerror(J, "instanceof: 'prototype' property is not an object"); 587 | O = js_toobject(J, -1); 588 | js_pop(J, 1); 589 | 590 | V = js_toobject(J, -2); 591 | while (V) { 592 | V = V->prototype; 593 | if (O == V) 594 | return 1; 595 | } 596 | 597 | return 0; 598 | } 599 | 600 | void js_concat(js_State *J) 601 | { 602 | js_toprimitive(J, -2, JS_HNONE); 603 | js_toprimitive(J, -1, JS_HNONE); 604 | 605 | if (js_isstring(J, -2) || js_isstring(J, -1)) { 606 | const char *sa = js_tostring(J, -2); 607 | const char *sb = js_tostring(J, -1); 608 | char * volatile sab = NULL; 609 | /* TODO: create js_String directly */ 610 | if (js_try(J)) { 611 | js_free(J, sab); 612 | js_throw(J); 613 | } 614 | sab = js_malloc(J, strlen(sa) + strlen(sb) + 1); 615 | strcpy(sab, sa); 616 | strcat(sab, sb); 617 | js_pop(J, 2); 618 | js_pushstring(J, sab); 619 | js_endtry(J); 620 | js_free(J, sab); 621 | } else { 622 | double x = js_tonumber(J, -2); 623 | double y = js_tonumber(J, -1); 624 | js_pop(J, 2); 625 | js_pushnumber(J, x + y); 626 | } 627 | } 628 | 629 | int js_compare(js_State *J, int *okay) 630 | { 631 | js_toprimitive(J, -2, JS_HNUMBER); 632 | js_toprimitive(J, -1, JS_HNUMBER); 633 | 634 | *okay = 1; 635 | if (js_isstring(J, -2) && js_isstring(J, -1)) { 636 | return strcmp(js_tostring(J, -2), js_tostring(J, -1)); 637 | } else { 638 | double x = js_tonumber(J, -2); 639 | double y = js_tonumber(J, -1); 640 | if (isnan(x) || isnan(y)) 641 | *okay = 0; 642 | return x < y ? -1 : x > y ? 1 : 0; 643 | } 644 | } 645 | 646 | int js_equal(js_State *J) 647 | { 648 | js_Value *x = js_tovalue(J, -2); 649 | js_Value *y = js_tovalue(J, -1); 650 | 651 | retry: 652 | if (JSV_ISSTRING(x) && JSV_ISSTRING(y)) 653 | return !strcmp(JSV_TOSTRING(x), JSV_TOSTRING(y)); 654 | if (x->t.type == y->t.type) { 655 | if (x->t.type == JS_TUNDEFINED) return 1; 656 | if (x->t.type == JS_TNULL) return 1; 657 | if (x->t.type == JS_TNUMBER) return x->u.number == y->u.number; 658 | if (x->t.type == JS_TBOOLEAN) return x->u.boolean == y->u.boolean; 659 | if (x->t.type == JS_TOBJECT) return x->u.object == y->u.object; 660 | return 0; 661 | } 662 | 663 | if (x->t.type == JS_TNULL && y->t.type == JS_TUNDEFINED) return 1; 664 | if (x->t.type == JS_TUNDEFINED && y->t.type == JS_TNULL) return 1; 665 | 666 | if (x->t.type == JS_TNUMBER && JSV_ISSTRING(y)) 667 | return x->u.number == jsV_tonumber(J, y); 668 | if (JSV_ISSTRING(x) && y->t.type == JS_TNUMBER) 669 | return jsV_tonumber(J, x) == y->u.number; 670 | 671 | if (x->t.type == JS_TBOOLEAN) { 672 | x->t.type = JS_TNUMBER; 673 | x->u.number = x->u.boolean ? 1 : 0; 674 | goto retry; 675 | } 676 | if (y->t.type == JS_TBOOLEAN) { 677 | y->t.type = JS_TNUMBER; 678 | y->u.number = y->u.boolean ? 1 : 0; 679 | goto retry; 680 | } 681 | if ((JSV_ISSTRING(x) || x->t.type == JS_TNUMBER) && y->t.type == JS_TOBJECT) { 682 | jsV_toprimitive(J, y, JS_HNONE); 683 | goto retry; 684 | } 685 | if (x->t.type == JS_TOBJECT && (JSV_ISSTRING(y) || y->t.type == JS_TNUMBER)) { 686 | jsV_toprimitive(J, x, JS_HNONE); 687 | goto retry; 688 | } 689 | 690 | return 0; 691 | } 692 | 693 | int js_strictequal(js_State *J) 694 | { 695 | js_Value *x = js_tovalue(J, -2); 696 | js_Value *y = js_tovalue(J, -1); 697 | 698 | if (JSV_ISSTRING(x) && JSV_ISSTRING(y)) 699 | return !strcmp(JSV_TOSTRING(x), JSV_TOSTRING(y)); 700 | 701 | if (x->t.type != y->t.type) return 0; 702 | if (x->t.type == JS_TUNDEFINED) return 1; 703 | if (x->t.type == JS_TNULL) return 1; 704 | if (x->t.type == JS_TNUMBER) return x->u.number == y->u.number; 705 | if (x->t.type == JS_TBOOLEAN) return x->u.boolean == y->u.boolean; 706 | if (x->t.type == JS_TOBJECT) return x->u.object == y->u.object; 707 | return 0; 708 | } 709 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #ifdef _MSC_VER 5 | #include 6 | #else 7 | #include 8 | #endif 9 | #include 10 | 11 | #include "mujs.h" 12 | 13 | static char *xoptarg; /* Global argument pointer. */ 14 | static int xoptind = 0; /* Global argv index. */ 15 | static int xgetopt(int argc, char *argv[], char *optstring) 16 | { 17 | static char *scan = NULL; /* Private scan pointer. */ 18 | 19 | char c; 20 | char *place; 21 | 22 | xoptarg = NULL; 23 | 24 | if (!scan || *scan == '\0') { 25 | if (xoptind == 0) 26 | xoptind++; 27 | 28 | if (xoptind >= argc || argv[xoptind][0] != '-' || argv[xoptind][1] == '\0') 29 | return EOF; 30 | if (argv[xoptind][1] == '-' && argv[xoptind][2] == '\0') { 31 | xoptind++; 32 | return EOF; 33 | } 34 | 35 | scan = argv[xoptind]+1; 36 | xoptind++; 37 | } 38 | 39 | c = *scan++; 40 | place = strchr(optstring, c); 41 | 42 | if (!place || c == ':') { 43 | fprintf(stderr, "%s: unknown option -%c\n", argv[0], c); 44 | return '?'; 45 | } 46 | 47 | place++; 48 | if (*place == ':') { 49 | if (*scan != '\0') { 50 | xoptarg = scan; 51 | scan = NULL; 52 | } else if (xoptind < argc) { 53 | xoptarg = argv[xoptind]; 54 | xoptind++; 55 | } else { 56 | fprintf(stderr, "%s: option requires argument -%c\n", argv[0], c); 57 | return ':'; 58 | } 59 | } 60 | 61 | return c; 62 | } 63 | 64 | #ifdef HAVE_READLINE 65 | #include 66 | #include 67 | #else 68 | void using_history(void) { } 69 | void add_history(const char *string) { } 70 | void rl_bind_key(int key, void (*fun)(void)) { } 71 | void rl_insert(void) { } 72 | char *readline(const char *prompt) 73 | { 74 | static char line[500], *p; 75 | int n; 76 | fputs(prompt, stdout); 77 | p = fgets(line, sizeof line, stdin); 78 | if (p) { 79 | n = strlen(line); 80 | if (n > 0 && line[n-1] == '\n') 81 | line[--n] = 0; 82 | p = malloc(n+1); 83 | memcpy(p, line, n+1); 84 | return p; 85 | } 86 | return NULL; 87 | } 88 | #endif 89 | 90 | #define PS1 "> " 91 | 92 | static void jsB_gc(js_State *J) 93 | { 94 | int report = js_toboolean(J, 1); 95 | js_gc(J, report); 96 | js_pushundefined(J); 97 | } 98 | 99 | static void jsB_load(js_State *J) 100 | { 101 | int i, n = js_gettop(J); 102 | for (i = 1; i < n; ++i) { 103 | js_loadfile(J, js_tostring(J, i)); 104 | js_pushundefined(J); 105 | js_call(J, 0); 106 | js_pop(J, 1); 107 | } 108 | js_pushundefined(J); 109 | } 110 | 111 | static void jsB_compile(js_State *J) 112 | { 113 | const char *source = js_tostring(J, 1); 114 | const char *filename = js_isdefined(J, 2) ? js_tostring(J, 2) : "[string]"; 115 | js_loadstring(J, filename, source); 116 | } 117 | 118 | static void jsB_print(js_State *J) 119 | { 120 | int i, top = js_gettop(J); 121 | for (i = 1; i < top; ++i) { 122 | const char *s = js_tostring(J, i); 123 | if (i > 1) putchar(' '); 124 | fputs(s, stdout); 125 | } 126 | putchar('\n'); 127 | js_pushundefined(J); 128 | } 129 | 130 | static void jsB_write(js_State *J) 131 | { 132 | int i, top = js_gettop(J); 133 | for (i = 1; i < top; ++i) { 134 | const char *s = js_tostring(J, i); 135 | if (i > 1) putchar(' '); 136 | fputs(s, stdout); 137 | } 138 | js_pushundefined(J); 139 | } 140 | 141 | static void jsB_read(js_State *J) 142 | { 143 | const char *filename = js_tostring(J, 1); 144 | FILE *f; 145 | char *s; 146 | int n, t; 147 | 148 | f = fopen(filename, "rb"); 149 | if (!f) { 150 | js_error(J, "cannot open file '%s': %s", filename, strerror(errno)); 151 | } 152 | 153 | if (fseek(f, 0, SEEK_END) < 0) { 154 | fclose(f); 155 | js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); 156 | } 157 | 158 | n = ftell(f); 159 | if (n < 0) { 160 | fclose(f); 161 | js_error(J, "cannot tell in file '%s': %s", filename, strerror(errno)); 162 | } 163 | 164 | if (fseek(f, 0, SEEK_SET) < 0) { 165 | fclose(f); 166 | js_error(J, "cannot seek in file '%s': %s", filename, strerror(errno)); 167 | } 168 | 169 | s = malloc(n + 1); 170 | if (!s) { 171 | fclose(f); 172 | js_error(J, "out of memory"); 173 | } 174 | 175 | t = fread(s, 1, n, f); 176 | if (t != n) { 177 | free(s); 178 | fclose(f); 179 | js_error(J, "cannot read data from file '%s': %s", filename, strerror(errno)); 180 | } 181 | s[n] = 0; 182 | 183 | js_pushstring(J, s); 184 | free(s); 185 | fclose(f); 186 | } 187 | 188 | static void jsB_readline(js_State *J) 189 | { 190 | char *line = readline(""); 191 | if (!line) { 192 | js_pushnull(J); 193 | return; 194 | } 195 | js_pushstring(J, line); 196 | if (*line) 197 | add_history(line); 198 | free(line); 199 | } 200 | 201 | static void jsB_quit(js_State *J) 202 | { 203 | exit(js_tonumber(J, 1)); 204 | } 205 | 206 | static void jsB_repr(js_State *J) 207 | { 208 | js_repr(J, 1); 209 | } 210 | 211 | static const char *require_js = 212 | "function require(name) {\n" 213 | "var cache = require.cache;\n" 214 | "if (name in cache) return cache[name];\n" 215 | "var exports = {};\n" 216 | "cache[name] = exports;\n" 217 | "Function('exports', read(name+'.js'))(exports);\n" 218 | "return exports;\n" 219 | "}\n" 220 | "require.cache = Object.create(null);\n" 221 | ; 222 | 223 | 224 | static const char *stacktrace_js = 225 | "Error.prototype.toString = function() {\n" 226 | "var s = this.name;\n" 227 | "if ('message' in this) s += ': ' + this.message;\n" 228 | "if ('stackTrace' in this) s += this.stackTrace;\n" 229 | "return s;\n" 230 | "};\n" 231 | ; 232 | 233 | static const char *console_js = 234 | "var console = { log: print, debug: print, warn: print, error: print };" 235 | ; 236 | 237 | static int eval_print(js_State *J, const char *source) 238 | { 239 | if (js_ploadstring(J, "[stdin]", source)) { 240 | fprintf(stderr, "%s\n", js_trystring(J, -1, "Error")); 241 | js_pop(J, 1); 242 | return 1; 243 | } 244 | js_pushundefined(J); 245 | if (js_pcall(J, 0)) { 246 | fprintf(stderr, "%s\n", js_trystring(J, -1, "Error")); 247 | js_pop(J, 1); 248 | return 1; 249 | } 250 | if (js_isdefined(J, -1)) { 251 | printf("%s\n", js_tryrepr(J, -1, "can't convert to string")); 252 | } 253 | js_pop(J, 1); 254 | return 0; 255 | } 256 | 257 | static char *read_stdin(void) 258 | { 259 | int n = 0; 260 | int t = 512; 261 | char *s = NULL; 262 | 263 | for (;;) { 264 | char *ss = realloc(s, t); 265 | if (!ss) { 266 | free(s); 267 | fprintf(stderr, "cannot allocate storage for stdin contents\n"); 268 | return NULL; 269 | } 270 | s = ss; 271 | n += fread(s + n, 1, t - n - 1, stdin); 272 | if (n < t - 1) 273 | break; 274 | t *= 2; 275 | } 276 | 277 | if (ferror(stdin)) { 278 | free(s); 279 | fprintf(stderr, "error reading stdin\n"); 280 | return NULL; 281 | } 282 | 283 | s[n] = 0; 284 | return s; 285 | } 286 | 287 | static void usage(void) 288 | { 289 | fprintf(stderr, "Usage: mujs [options] [script [scriptArgs*]]\n"); 290 | fprintf(stderr, "\t-i: Enter interactive prompt after running code.\n"); 291 | fprintf(stderr, "\t-s: Check strictness.\n"); 292 | exit(1); 293 | } 294 | 295 | int 296 | main(int argc, char **argv) 297 | { 298 | char *input; 299 | js_State *J; 300 | int status = 0; 301 | int strict = 0; 302 | int interactive = 0; 303 | int i, c; 304 | 305 | while ((c = xgetopt(argc, argv, "is")) != -1) { 306 | switch (c) { 307 | default: usage(); break; 308 | case 'i': interactive = 1; break; 309 | case 's': strict = 1; break; 310 | } 311 | } 312 | 313 | J = js_newstate(NULL, NULL, strict ? JS_STRICT : 0); 314 | if (!J) { 315 | fprintf(stderr, "Could not initialize MuJS.\n"); 316 | exit(1); 317 | } 318 | 319 | js_newcfunction(J, jsB_gc, "gc", 0); 320 | js_setglobal(J, "gc"); 321 | 322 | js_newcfunction(J, jsB_load, "load", 1); 323 | js_setglobal(J, "load"); 324 | 325 | js_newcfunction(J, jsB_compile, "compile", 2); 326 | js_setglobal(J, "compile"); 327 | 328 | js_newcfunction(J, jsB_print, "print", 0); 329 | js_setglobal(J, "print"); 330 | 331 | js_newcfunction(J, jsB_write, "write", 0); 332 | js_setglobal(J, "write"); 333 | 334 | js_newcfunction(J, jsB_read, "read", 1); 335 | js_setglobal(J, "read"); 336 | 337 | js_newcfunction(J, jsB_readline, "readline", 0); 338 | js_setglobal(J, "readline"); 339 | 340 | js_newcfunction(J, jsB_repr, "repr", 0); 341 | js_setglobal(J, "repr"); 342 | 343 | js_newcfunction(J, jsB_quit, "quit", 1); 344 | js_setglobal(J, "quit"); 345 | 346 | js_dostring(J, require_js); 347 | js_dostring(J, stacktrace_js); 348 | js_dostring(J, console_js); 349 | 350 | if (xoptind == argc) { 351 | interactive = 1; 352 | } else { 353 | c = xoptind++; 354 | 355 | js_newarray(J); 356 | i = 0; 357 | while (xoptind < argc) { 358 | js_pushstring(J, argv[xoptind++]); 359 | js_setindex(J, -2, i++); 360 | } 361 | js_setglobal(J, "scriptArgs"); 362 | 363 | if (js_dofile(J, argv[c])) 364 | status = 1; 365 | } 366 | 367 | if (interactive) { 368 | printf("Welcome to MuJS %d.%d.%d.\n", 369 | JS_VERSION_MAJOR, JS_VERSION_MINOR, JS_VERSION_PATCH); 370 | if (isatty(0)) { 371 | using_history(); 372 | rl_bind_key('\t', rl_insert); 373 | input = readline(PS1); 374 | while (input) { 375 | eval_print(J, input); 376 | if (*input) 377 | add_history(input); 378 | free(input); 379 | input = readline(PS1); 380 | } 381 | putchar('\n'); 382 | } else { 383 | input = read_stdin(); 384 | if (!input || !js_dostring(J, input)) 385 | status = 1; 386 | free(input); 387 | } 388 | } 389 | 390 | js_gc(J, 0); 391 | js_freestate(J); 392 | 393 | return status; 394 | } 395 | -------------------------------------------------------------------------------- /mujs.h: -------------------------------------------------------------------------------- 1 | #ifndef mujs_h 2 | #define mujs_h 3 | 4 | #include /* required for setjmp in fz_try macro */ 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #define JS_VERSION_MAJOR 1 11 | #define JS_VERSION_MINOR 3 12 | #define JS_VERSION_PATCH 5 13 | 14 | #define JS_VERSION (JS_VERSION_MAJOR * 10000 + JS_VERSION_MINOR * 100 + JS_VERSION_PATCH) 15 | #define JS_CHECKVERSION(x,y,z) (JS_VERSION >= ((x) * 10000 + (y) * 100 + (z))) 16 | 17 | /* noreturn is a GCC extension */ 18 | #ifdef __GNUC__ 19 | #define JS_NORETURN __attribute__((noreturn)) 20 | #else 21 | #ifdef _MSC_VER 22 | #define JS_NORETURN __declspec(noreturn) 23 | #else 24 | #define JS_NORETURN 25 | #endif 26 | #endif 27 | 28 | /* GCC can do type checking of printf strings */ 29 | #ifdef __printflike 30 | #define JS_PRINTFLIKE __printflike 31 | #else 32 | #if __GNUC__ > 2 || __GNUC__ == 2 && __GNUC_MINOR__ >= 7 33 | #define JS_PRINTFLIKE(fmtarg, firstvararg) \ 34 | __attribute__((__format__ (__printf__, fmtarg, firstvararg))) 35 | #else 36 | #define JS_PRINTFLIKE(fmtarg, firstvararg) 37 | #endif 38 | #endif 39 | 40 | typedef struct js_State js_State; 41 | 42 | typedef void *(*js_Alloc)(void *memctx, void *ptr, int size); 43 | typedef void (*js_Panic)(js_State *J); 44 | typedef void (*js_CFunction)(js_State *J); 45 | typedef void (*js_Finalize)(js_State *J, void *p); 46 | typedef int (*js_HasProperty)(js_State *J, void *p, const char *name); 47 | typedef int (*js_Put)(js_State *J, void *p, const char *name); 48 | typedef int (*js_Delete)(js_State *J, void *p, const char *name); 49 | typedef void (*js_Report)(js_State *J, const char *message); 50 | 51 | /* Basic functions */ 52 | js_State *js_newstate(js_Alloc alloc, void *actx, int flags); 53 | void js_setcontext(js_State *J, void *uctx); 54 | void *js_getcontext(js_State *J); 55 | void js_setreport(js_State *J, js_Report report); 56 | js_Panic js_atpanic(js_State *J, js_Panic panic); 57 | void js_freestate(js_State *J); 58 | void js_gc(js_State *J, int report); 59 | 60 | int js_dostring(js_State *J, const char *source); 61 | int js_dofile(js_State *J, const char *filename); 62 | int js_ploadstring(js_State *J, const char *filename, const char *source); 63 | int js_ploadfile(js_State *J, const char *filename); 64 | int js_pcall(js_State *J, int n); 65 | int js_pconstruct(js_State *J, int n); 66 | 67 | /* Exception handling */ 68 | 69 | void *js_savetry(js_State *J); /* returns a jmp_buf */ 70 | 71 | #define js_try(J) \ 72 | setjmp(js_savetry(J)) 73 | 74 | void js_endtry(js_State *J); 75 | 76 | /* State constructor flags */ 77 | enum { 78 | JS_STRICT = 1, 79 | }; 80 | 81 | /* RegExp flags */ 82 | enum { 83 | JS_REGEXP_G = 1, 84 | JS_REGEXP_I = 2, 85 | JS_REGEXP_M = 4, 86 | }; 87 | 88 | /* Property attribute flags */ 89 | enum { 90 | JS_READONLY = 1, 91 | JS_DONTENUM = 2, 92 | JS_DONTCONF = 4, 93 | }; 94 | 95 | /* enum for js_type() */ 96 | enum { 97 | JS_ISUNDEFINED, 98 | JS_ISNULL, 99 | JS_ISBOOLEAN, 100 | JS_ISNUMBER, 101 | JS_ISSTRING, 102 | JS_ISFUNCTION, 103 | JS_ISOBJECT 104 | }; 105 | 106 | void js_report(js_State *J, const char *message); 107 | 108 | void js_newerror(js_State *J, const char *message); 109 | void js_newevalerror(js_State *J, const char *message); 110 | void js_newrangeerror(js_State *J, const char *message); 111 | void js_newreferenceerror(js_State *J, const char *message); 112 | void js_newsyntaxerror(js_State *J, const char *message); 113 | void js_newtypeerror(js_State *J, const char *message); 114 | void js_newurierror(js_State *J, const char *message); 115 | 116 | JS_NORETURN void js_error(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); 117 | JS_NORETURN void js_evalerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); 118 | JS_NORETURN void js_rangeerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); 119 | JS_NORETURN void js_referenceerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); 120 | JS_NORETURN void js_syntaxerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); 121 | JS_NORETURN void js_typeerror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); 122 | JS_NORETURN void js_urierror(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); 123 | JS_NORETURN void js_throw(js_State *J); 124 | 125 | void js_loadstring(js_State *J, const char *filename, const char *source); 126 | void js_loadfile(js_State *J, const char *filename); 127 | 128 | void js_eval(js_State *J); 129 | void js_call(js_State *J, int n); 130 | void js_construct(js_State *J, int n); 131 | 132 | const char *js_ref(js_State *J); 133 | void js_unref(js_State *J, const char *ref); 134 | 135 | void js_getregistry(js_State *J, const char *name); 136 | void js_setregistry(js_State *J, const char *name); 137 | void js_delregistry(js_State *J, const char *name); 138 | 139 | void js_getglobal(js_State *J, const char *name); 140 | void js_setglobal(js_State *J, const char *name); 141 | void js_defglobal(js_State *J, const char *name, int atts); 142 | void js_delglobal(js_State *J, const char *name); 143 | 144 | int js_hasproperty(js_State *J, int idx, const char *name); 145 | void js_getproperty(js_State *J, int idx, const char *name); 146 | void js_setproperty(js_State *J, int idx, const char *name); 147 | void js_defproperty(js_State *J, int idx, const char *name, int atts); 148 | void js_delproperty(js_State *J, int idx, const char *name); 149 | void js_defaccessor(js_State *J, int idx, const char *name, int atts); 150 | 151 | int js_getlength(js_State *J, int idx); 152 | void js_setlength(js_State *J, int idx, int len); 153 | int js_hasindex(js_State *J, int idx, int i); 154 | void js_getindex(js_State *J, int idx, int i); 155 | void js_setindex(js_State *J, int idx, int i); 156 | void js_delindex(js_State *J, int idx, int i); 157 | 158 | void js_currentfunction(js_State *J); 159 | void *js_currentfunctiondata(js_State *J); 160 | void js_pushglobal(js_State *J); 161 | void js_pushundefined(js_State *J); 162 | void js_pushnull(js_State *J); 163 | void js_pushboolean(js_State *J, int v); 164 | void js_pushnumber(js_State *J, double v); 165 | void js_pushstring(js_State *J, const char *v); 166 | void js_pushlstring(js_State *J, const char *v, int n); 167 | void js_pushliteral(js_State *J, const char *v); 168 | 169 | void js_newobjectx(js_State *J); 170 | void js_newobject(js_State *J); 171 | void js_newarray(js_State *J); 172 | void js_newboolean(js_State *J, int v); 173 | void js_newnumber(js_State *J, double v); 174 | void js_newstring(js_State *J, const char *v); 175 | void js_newcfunction(js_State *J, js_CFunction fun, const char *name, int length); 176 | void js_newcfunctionx(js_State *J, js_CFunction fun, const char *name, int length, void *data, js_Finalize finalize); 177 | void js_newcconstructor(js_State *J, js_CFunction fun, js_CFunction con, const char *name, int length); 178 | void js_newuserdata(js_State *J, const char *tag, void *data, js_Finalize finalize); 179 | void js_newuserdatax(js_State *J, const char *tag, void *data, js_HasProperty has, js_Put put, js_Delete del, js_Finalize finalize); 180 | void js_newregexp(js_State *J, const char *pattern, int flags); 181 | 182 | void js_pushiterator(js_State *J, int idx, int own); 183 | const char *js_nextiterator(js_State *J, int idx); 184 | 185 | int js_isdefined(js_State *J, int idx); 186 | int js_isundefined(js_State *J, int idx); 187 | int js_isnull(js_State *J, int idx); 188 | int js_isboolean(js_State *J, int idx); 189 | int js_isnumber(js_State *J, int idx); 190 | int js_isstring(js_State *J, int idx); 191 | int js_isprimitive(js_State *J, int idx); 192 | int js_isobject(js_State *J, int idx); 193 | int js_isarray(js_State *J, int idx); 194 | int js_isregexp(js_State *J, int idx); 195 | int js_iscoercible(js_State *J, int idx); 196 | int js_iscallable(js_State *J, int idx); 197 | int js_isuserdata(js_State *J, int idx, const char *tag); 198 | int js_iserror(js_State *J, int idx); 199 | int js_isnumberobject(js_State *J, int idx); 200 | int js_isstringobject(js_State *J, int idx); 201 | int js_isbooleanobject(js_State *J, int idx); 202 | int js_isdateobject(js_State *J, int idx); 203 | 204 | int js_toboolean(js_State *J, int idx); 205 | double js_tonumber(js_State *J, int idx); 206 | const char *js_tostring(js_State *J, int idx); 207 | void *js_touserdata(js_State *J, int idx, const char *tag); 208 | 209 | const char *js_trystring(js_State *J, int idx, const char *error); 210 | double js_trynumber(js_State *J, int idx, double error); 211 | int js_tryinteger(js_State *J, int idx, int error); 212 | int js_tryboolean(js_State *J, int idx, int error); 213 | 214 | int js_tointeger(js_State *J, int idx); 215 | int js_toint32(js_State *J, int idx); 216 | unsigned int js_touint32(js_State *J, int idx); 217 | short js_toint16(js_State *J, int idx); 218 | unsigned short js_touint16(js_State *J, int idx); 219 | 220 | int js_gettop(js_State *J); 221 | void js_pop(js_State *J, int n); 222 | void js_rot(js_State *J, int n); 223 | void js_copy(js_State *J, int idx); 224 | void js_remove(js_State *J, int idx); 225 | void js_insert(js_State *J, int idx); 226 | void js_replace(js_State* J, int idx); 227 | 228 | void js_dup(js_State *J); 229 | void js_dup2(js_State *J); 230 | void js_rot2(js_State *J); 231 | void js_rot3(js_State *J); 232 | void js_rot4(js_State *J); 233 | void js_rot2pop1(js_State *J); 234 | void js_rot3pop2(js_State *J); 235 | 236 | void js_concat(js_State *J); 237 | int js_compare(js_State *J, int *okay); 238 | int js_equal(js_State *J); 239 | int js_strictequal(js_State *J); 240 | int js_instanceof(js_State *J); 241 | const char *js_typeof(js_State *J, int idx); 242 | int js_type(js_State *J, int idx); 243 | 244 | void js_repr(js_State *J, int idx); 245 | const char *js_torepr(js_State *J, int idx); 246 | const char *js_tryrepr(js_State *J, int idx, const char *error); 247 | 248 | #ifdef __cplusplus 249 | } 250 | #endif 251 | 252 | #endif 253 | -------------------------------------------------------------------------------- /one.c: -------------------------------------------------------------------------------- 1 | #include "jsarray.c" 2 | #include "jsboolean.c" 3 | #include "jsbuiltin.c" 4 | #include "jscompile.c" 5 | #include "jsdate.c" 6 | #include "jsdtoa.c" 7 | #include "jserror.c" 8 | #include "jsfunction.c" 9 | #include "jsgc.c" 10 | #include "jsintern.c" 11 | #include "jslex.c" 12 | #include "jsmath.c" 13 | #include "jsnumber.c" 14 | #include "jsobject.c" 15 | #include "json.c" 16 | #include "jsparse.c" 17 | #include "jsproperty.c" 18 | #include "jsregexp.c" 19 | #include "jsrepr.c" 20 | #include "jsrun.c" 21 | #include "jsstate.c" 22 | #include "jsstring.c" 23 | #include "jsvalue.c" 24 | #include "regexp.c" 25 | #include "utf.c" 26 | -------------------------------------------------------------------------------- /opnames.h: -------------------------------------------------------------------------------- 1 | "pop", 2 | "dup", 3 | "dup2", 4 | "rot2", 5 | "rot3", 6 | "rot4", 7 | "integer", 8 | "number", 9 | "string", 10 | "closure", 11 | "newarray", 12 | "newobject", 13 | "newregexp", 14 | "undef", 15 | "null", 16 | "true", 17 | "false", 18 | "this", 19 | "current", 20 | "getlocal", 21 | "setlocal", 22 | "dellocal", 23 | "hasvar", 24 | "getvar", 25 | "setvar", 26 | "delvar", 27 | "in", 28 | "skiparray", 29 | "initarray", 30 | "initprop", 31 | "initgetter", 32 | "initsetter", 33 | "getprop", 34 | "getprop_s", 35 | "setprop", 36 | "setprop_s", 37 | "delprop", 38 | "delprop_s", 39 | "iterator", 40 | "nextiter", 41 | "eval", 42 | "call", 43 | "new", 44 | "typeof", 45 | "pos", 46 | "neg", 47 | "bitnot", 48 | "lognot", 49 | "inc", 50 | "dec", 51 | "postinc", 52 | "postdec", 53 | "mul", 54 | "div", 55 | "mod", 56 | "add", 57 | "sub", 58 | "shl", 59 | "shr", 60 | "ushr", 61 | "lt", 62 | "gt", 63 | "le", 64 | "ge", 65 | "eq", 66 | "ne", 67 | "stricteq", 68 | "strictne", 69 | "jcase", 70 | "bitand", 71 | "bitxor", 72 | "bitor", 73 | "instanceof", 74 | "throw", 75 | "try", 76 | "endtry", 77 | "catch", 78 | "endcatch", 79 | "with", 80 | "endwith", 81 | "debugger", 82 | "jump", 83 | "jtrue", 84 | "jfalse", 85 | "return", 86 | -------------------------------------------------------------------------------- /regexp.h: -------------------------------------------------------------------------------- 1 | #ifndef regexp_h 2 | #define regexp_h 3 | 4 | #define regcompx js_regcompx 5 | #define regfreex js_regfreex 6 | #define regcomp js_regcomp 7 | #define regexec js_regexec 8 | #define regfree js_regfree 9 | 10 | typedef struct Reprog Reprog; 11 | typedef struct Resub Resub; 12 | 13 | Reprog *regcompx(void *(*alloc)(void *ctx, void *p, int n), void *ctx, 14 | const char *pattern, int cflags, const char **errorp); 15 | void regfreex(void *(*alloc)(void *ctx, void *p, int n), void *ctx, 16 | Reprog *prog); 17 | 18 | Reprog *regcomp(const char *pattern, int cflags, const char **errorp); 19 | int regexec(Reprog *prog, const char *string, Resub *sub, int eflags); 20 | void regfree(Reprog *prog); 21 | 22 | enum { 23 | /* regcomp flags */ 24 | REG_ICASE = 1, 25 | REG_NEWLINE = 2, 26 | 27 | /* regexec flags */ 28 | REG_NOTBOL = 4, 29 | }; 30 | 31 | /* If you redefine REG_MAXSUB, you must make sure both the calling 32 | * code and the regexp.c compilation unit use the same value! 33 | */ 34 | #ifndef REG_MAXSUB 35 | #define REG_MAXSUB 16 36 | #endif 37 | 38 | struct Resub { 39 | int nsub; 40 | struct { 41 | const char *sp; 42 | const char *ep; 43 | } sub[REG_MAXSUB]; 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /tools/test262: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | usage() { 4 | [ "${1-}" ] && { to=2; >&$to printf "Error: %s\n" "$1"; } || to=1 5 | >&$to echo "Usage: ${0##*/} [OPTIONS] test-file | test-dir" 6 | >&$to echo "Run test-262 ES5 test file or directory (at the test262 dir)." 7 | >&$to echo " -s Print source code of failed tests." 8 | >&$to echo " -p Print every test name before running it" 9 | >&$to echo " -f Display full paths and full stack trace when possible" 10 | >&$to echo " -l file.js Load file.js after the harness and before the test (up to one)" 11 | >&$to echo " -m MUJS MUJS is [path/to/]mujs binary to test" 12 | >&$to echo " Default is $(dirname "$0")/../build/release/mujs or mujs at \$PATH" 13 | >&$to echo " -b Don't skip known bad (crashing/hanging) tests" 14 | >&$to echo " -B Run only known bad tests" 15 | exit $((to-1)) 16 | } 17 | 18 | KNOWN_BAD=" 19 | --hang-with-sta.js: 20 | S15.1.3.2_A2.5_T1.js 21 | S15.1.3.1_A2.5_T1.js 22 | 23 | --Hang-(or-taking-more-than-few-seconds): 24 | 15.4.4.18-3-14.js 25 | 15.4.4.20-3-14.js 26 | S15.4.4.10_A3_T2.js 27 | S15.4.4.10_A3_T1.js 28 | 15.4.4.19-3-29.js 29 | 15.4.4.19-3-28.js 30 | 15.4.4.19-3-8.js 31 | 15.4.4.19-3-14.js 32 | S15.4.4.8_A3_T3.js 33 | 15.4.4.22-3-9.js 34 | 15.4.4.22-3-7.js 35 | 15.4.4.22-3-25.js 36 | 15.4.4.22-3-14.js 37 | 15.4.4.21-3-14.js 38 | 15.4.4.15-3-28.js 39 | 15.4.4.15-3-14.js 40 | 15.4.4.15-3-7.js 41 | 15.4.4.15-3-25.js 42 | 15.4.4.15-3-9.js 43 | " 44 | 45 | SKIP_KNOWN=yes # "yes": skip bad "no": don't skip "neg": run only bad 46 | PRINT_ALL= 47 | EXTRA_ARGS= 48 | mujs= lopt= 49 | 50 | while getopts bBfhl:ps o; do 51 | case $o in 52 | h) usage ;; 53 | b) SKIP_KNOWN=no ;; 54 | B) SKIP_KNOWN=neg ;; 55 | p) PRINT_ALL=yes ;; 56 | s) EXTRA_ARGS="$EXTRA_ARGS -s" ;; 57 | f) EXTRA_ARGS="$EXTRA_ARGS -f" ;; 58 | l) [ "$OPTARG" ] && lopt=$OPTARG || usage "empty file for -l" ;; 59 | m) mujs=$OPTARG;; 60 | *) usage "unknown option -$o" ;; 61 | esac 62 | done 63 | shift $((OPTIND-1)) 64 | [ $# = 1 ] || usage "expecting one file/dir" 65 | 66 | BAD= 67 | if [ "$SKIP_KNOWN" != no ]; then 68 | for b in $KNOWN_BAD; do 69 | BAD="$BAD $b " 70 | done 71 | fi 72 | 73 | find_root() { 74 | ROOT=$1 75 | n=0 76 | while ! [ -e "$ROOT"/test/harness/sta.js ]; do 77 | ROOT=$ROOT/.. 78 | n=$((n+1)) 79 | [ $n -lt 10 ] || usage "can't find test-suite root" 80 | done 81 | } 82 | 83 | if [ -d "$1" ]; then 84 | find_root "$1" 85 | 86 | if [ "$ROOT" = "$1" ]; then 87 | FILES_CMD='find "$1/test/suite" -name "*.js" | sort -V' 88 | else 89 | FILES_CMD='find "$1" -name "*.js" | sort -V' 90 | fi 91 | else 92 | find_root "$(dirname "$1")" 93 | FILES_CMD='printf "%s\n" "$1"' 94 | fi 95 | 96 | if ! [ "$mujs" ]; then 97 | # try to use a recently built mujs rather than a global one 98 | mujs=$(dirname "$0")/../build/release/mujs 99 | [ -e "$mujs" ] || mujs=mujs 100 | fi 101 | jsharness=$(dirname "$0")/test262-harness.js 102 | 103 | total=0 104 | skipped=0 105 | failed=0 106 | 107 | eval "$FILES_CMD" | ( 108 | while IFS= read -r f && [ "$f" ]; do 109 | total=$((total+1)) 110 | base=${f##*/} 111 | 112 | case $BAD in *" $base "*) bad=yes;; *) bad=no;; esac 113 | 114 | case $bad-$SKIP_KNOWN in 115 | yes-yes) 116 | skipped=$((skipped+1)) 117 | printf "[Skipping: $base]\n\n" 118 | ;; 119 | no-neg) # not known bad and running only bad - don't print anything 120 | skipped=$((skipped+1)) 121 | ;; 122 | *) 123 | [ "$PRINT_ALL" ] && echo "Testing: $f" 124 | if ! "$mujs" -- "$jsharness" $EXTRA_ARGS ${lopt:+-l "$lopt"} "$ROOT" "$f" 2>&1; then 125 | failed=$((failed+1)) 126 | echo 127 | fi 128 | esac 129 | done 130 | 131 | if [ $total -gt 1 ]; then 132 | printf "Total: $total\n" 133 | printf "Pass: %${#total}s\n" $((total - skipped - failed)) 134 | printf "Skip: %${#total}s\n" $skipped 135 | printf "Fail: %${#total}s\n" $failed 136 | fi 137 | 138 | [ "$failed" = 0 ] 139 | ) 140 | -------------------------------------------------------------------------------- /tools/test262-harness.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Runs one test file from the ES5 test suite test-262 3 | * Usage: mujs [-s ] [-f] [-l file1.js -l ...] suit-root test-file 4 | * -s: print test source on failure 5 | * -f: print full paths/stacktraces if possible 6 | * -l: load a js file after the harness and before the test (to override things) 7 | * 8 | * If there are errors, print them and exits with code 1, else exit code is 0. 9 | * 10 | * The test suite is at: https://github.com/tc39/test262.git 11 | * The ES5 suite is at branch "es5-tests" 12 | * 13 | * - The test suite throws on any error, possibly with info at ex.message . 14 | * - Some tests make irreversible changes to global attrubutes, therefore it's 15 | * required to run each test file in a new mujs instance. 16 | */ 17 | 18 | (function(global) { 19 | "use strict"; 20 | 21 | // clean the global environment 22 | var mujs = {}; 23 | 24 | ["gc", "load", "compile", "print", "write", "read", "readline", "quit", "scriptArgs"] 25 | .forEach(function(a) { 26 | mujs[a] = global[a]; 27 | delete global[a]; 28 | }); 29 | 30 | // restore the original Error.toString behavior - it's being tested too 31 | Error.prototype.toString = function() { 32 | return this.name + ': ' + this.message; 33 | } 34 | 35 | function die_usage(str) { 36 | if (str) 37 | mujs.print(str); 38 | mujs.print("Usage: mujs [-f] [-l file1.js -l ...] suit-root test-file"); 39 | mujs.quit(1); 40 | } 41 | 42 | // our file loader 43 | function load(str, as_filename) { 44 | try { 45 | var runtime_err = false; 46 | var compiled = mujs.compile(str, as_filename); 47 | runtime_err = true; 48 | compiled(); 49 | return false; 50 | } catch (e) { 51 | return {err: e, runtime: runtime_err}; 52 | } 53 | } 54 | 55 | var args = mujs.scriptArgs; 56 | var full_mode = false; 57 | var print_src = false; 58 | var overrides = []; 59 | while ((""+args[0])[0] == "-") { 60 | switch (args[0]) { 61 | case "-f": full_mode = true; 62 | break; 63 | case "-s": print_src = true; 64 | break; 65 | case "-l": args.shift(); 66 | overrides.push(args[0]); 67 | break; 68 | default: die_usage("Unknown option " + args[0]); 69 | } 70 | args.shift(); 71 | } 72 | if (args.length != 2) 73 | die_usage("Exactly 2 paths are expected"); 74 | var root_path = args[0]; 75 | var test_path = args[1]; 76 | 77 | // load suite utils 78 | ["sta.js", "testBuiltInObject.js", "testIntl.js"] 79 | .forEach(function(u) { 80 | var path = root_path + "/test/harness/" + u; 81 | var as_file = full_mode ? path : "test/harness/" + u; 82 | var err = load(mujs.read(path), as_file); 83 | if (err) throw (err.err); 84 | }); 85 | 86 | // load user overrides (e.g. reduced getPrecision), with a global mujs 87 | if (overrides.length) { 88 | global.mujs = mujs 89 | overrides.forEach(function(f) { 90 | var err = load(mujs.read(f), f); 91 | if (err) throw (err.err); 92 | }); 93 | delete global.mujs; 94 | } 95 | 96 | // the actual test 97 | var source = mujs.read(test_path); 98 | var negative = !!source.match(/@negative/); 99 | if (negative) 100 | var neg_str = (source.match(/@negative (.*)/) || [])[1]; 101 | var as_file = test_path; 102 | if (!full_mode) { 103 | as_file = test_path.replace(/\\/g, "/"); 104 | var sub = as_file.indexOf("/suite/"); 105 | if (sub >= 0) 106 | as_file = "test" + as_file.substring(sub); 107 | } 108 | 109 | var result = load(mujs.read(test_path), as_file); 110 | if (!!result == negative) { 111 | // The docs don't really help about matching str, but this covers all cases 112 | if (neg_str) 113 | var err_for_match = /NotEarlyError/.test(neg_str) ? result.err.message : result.err.name; 114 | if (!negative || !neg_str || RegExp(neg_str).exec(err_for_match)) 115 | mujs.quit(0); 116 | } 117 | 118 | // failed 119 | // FIXME: @description can span lines. E.g. test/suite/bestPractice/Sbp_A3_T2.js 120 | var desc = source.match(/@description (.*)/); 121 | var info = "[File] " + as_file + 122 | (desc ? "\n[Desc] " + desc[1] : "") + 123 | "\n"; 124 | 125 | if (result) { 126 | var err = result.err; 127 | var msg = !neg_str ? err : "[Mismatch @negative " + neg_str + "]" + "\n " + err; 128 | 129 | info += (result.runtime ? "[run] " : "[load] ") + msg; 130 | if (err && err.stackTrace && (result.runtime || full_mode)) { 131 | if (full_mode) { 132 | info += err.stackTrace; 133 | } else { 134 | // trim the internal loader from the trace 135 | var internal = err.stackTrace.indexOf("\n" + load("mujs_blahblah()").err.stackTrace.trim().split("\n")[1]); 136 | if (internal >= 0) 137 | info += err.stackTrace.substring(0, internal); 138 | else 139 | info += err.stackTrace; 140 | } 141 | } 142 | } else { 143 | info += "[run] [Error expected but none thrown]"; 144 | } 145 | 146 | if (print_src) 147 | info += "\n[Source]\n" + source; 148 | 149 | mujs.print(info); 150 | mujs.quit(1); 151 | 152 | })(this) 153 | -------------------------------------------------------------------------------- /utf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The authors of this software are Rob Pike and Ken Thompson. 3 | * Copyright (c) 2002 by Lucent Technologies. 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose without fee is hereby granted, provided that this entire notice 6 | * is included in all copies of any software which is or includes a copy 7 | * or modification of this software and in all copies of the supporting 8 | * documentation for such software. 9 | * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED 10 | * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE 11 | * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY 12 | * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. 13 | */ 14 | #include 15 | #include 16 | 17 | #include "utf.h" 18 | #include "utfdata.h" 19 | 20 | #define nelem(a) (int)(sizeof (a) / sizeof (a)[0]) 21 | 22 | typedef unsigned char uchar; 23 | 24 | enum 25 | { 26 | Bit1 = 7, 27 | Bitx = 6, 28 | Bit2 = 5, 29 | Bit3 = 4, 30 | Bit4 = 3, 31 | Bit5 = 2, 32 | 33 | T1 = ((1<<(Bit1+1))-1) ^ 0xFF, /* 0000 0000 */ 34 | Tx = ((1<<(Bitx+1))-1) ^ 0xFF, /* 1000 0000 */ 35 | T2 = ((1<<(Bit2+1))-1) ^ 0xFF, /* 1100 0000 */ 36 | T3 = ((1<<(Bit3+1))-1) ^ 0xFF, /* 1110 0000 */ 37 | T4 = ((1<<(Bit4+1))-1) ^ 0xFF, /* 1111 0000 */ 38 | T5 = ((1<<(Bit5+1))-1) ^ 0xFF, /* 1111 1000 */ 39 | 40 | Rune1 = (1<<(Bit1+0*Bitx))-1, /* 0000 0000 0000 0000 0111 1111 */ 41 | Rune2 = (1<<(Bit2+1*Bitx))-1, /* 0000 0000 0000 0111 1111 1111 */ 42 | Rune3 = (1<<(Bit3+2*Bitx))-1, /* 0000 0000 1111 1111 1111 1111 */ 43 | Rune4 = (1<<(Bit4+3*Bitx))-1, /* 0001 1111 1111 1111 1111 1111 */ 44 | 45 | Maskx = (1< T1 66 | */ 67 | c = *(uchar*)str; 68 | if(c < Tx) { 69 | *rune = c; 70 | return 1; 71 | } 72 | 73 | /* 74 | * two character sequence 75 | * 0080-07FF => T2 Tx 76 | */ 77 | c1 = *(uchar*)(str+1) ^ Tx; 78 | if(c1 & Testx) 79 | goto bad; 80 | if(c < T3) { 81 | if(c < T2) 82 | goto bad; 83 | l = ((c << Bitx) | c1) & Rune2; 84 | if(l <= Rune1) 85 | goto bad; 86 | *rune = l; 87 | return 2; 88 | } 89 | 90 | /* 91 | * three character sequence 92 | * 0800-FFFF => T3 Tx Tx 93 | */ 94 | c2 = *(uchar*)(str+2) ^ Tx; 95 | if(c2 & Testx) 96 | goto bad; 97 | if(c < T4) { 98 | l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3; 99 | if(l <= Rune2) 100 | goto bad; 101 | *rune = l; 102 | return 3; 103 | } 104 | 105 | /* 106 | * four character sequence 107 | * 10000-10FFFF => T4 Tx Tx Tx 108 | */ 109 | if(UTFmax >= 4) { 110 | c3 = *(uchar*)(str+3) ^ Tx; 111 | if(c3 & Testx) 112 | goto bad; 113 | if(c < T5) { 114 | l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) & Rune4; 115 | if(l <= Rune3) 116 | goto bad; 117 | if(l > Runemax) 118 | goto bad; 119 | *rune = l; 120 | return 4; 121 | } 122 | } 123 | 124 | /* 125 | * bad decoding 126 | */ 127 | bad: 128 | *rune = Bad; 129 | return 1; 130 | } 131 | 132 | int 133 | runetochar(char *str, const Rune *rune) 134 | { 135 | int c = *rune; 136 | 137 | /* overlong null character */ 138 | if (c == 0) { 139 | str[0] = (char)0xc0; 140 | str[1] = (char)0x80; 141 | return 2; 142 | } 143 | 144 | /* 145 | * one character sequence 146 | * 00000-0007F => 00-7F 147 | */ 148 | if(c <= Rune1) { 149 | str[0] = c; 150 | return 1; 151 | } 152 | 153 | /* 154 | * two character sequence 155 | * 00080-007FF => T2 Tx 156 | */ 157 | if(c <= Rune2) { 158 | str[0] = T2 | (c >> 1*Bitx); 159 | str[1] = Tx | (c & Maskx); 160 | return 2; 161 | } 162 | 163 | /* 164 | * three character sequence 165 | * 00800-0FFFF => T3 Tx Tx 166 | */ 167 | if(c > Runemax) 168 | c = Runeerror; 169 | if(c <= Rune3) { 170 | str[0] = T3 | (c >> 2*Bitx); 171 | str[1] = Tx | ((c >> 1*Bitx) & Maskx); 172 | str[2] = Tx | (c & Maskx); 173 | return 3; 174 | } 175 | 176 | /* 177 | * four character sequence 178 | * 010000-1FFFFF => T4 Tx Tx Tx 179 | */ 180 | str[0] = T4 | (c >> 3*Bitx); 181 | str[1] = Tx | ((c >> 2*Bitx) & Maskx); 182 | str[2] = Tx | ((c >> 1*Bitx) & Maskx); 183 | str[3] = Tx | (c & Maskx); 184 | return 4; 185 | } 186 | 187 | int 188 | runelen(int c) 189 | { 190 | Rune rune; 191 | char str[10]; 192 | 193 | rune = c; 194 | return runetochar(str, &rune); 195 | } 196 | 197 | static const Rune * 198 | ucd_bsearch(Rune c, const Rune *t, int n, int ne) 199 | { 200 | const Rune *p; 201 | int m; 202 | 203 | while(n > 1) { 204 | m = n/2; 205 | p = t + m*ne; 206 | if(c >= p[0]) { 207 | t = p; 208 | n = n-m; 209 | } else 210 | n = m; 211 | } 212 | if(n && c >= t[0]) 213 | return t; 214 | return 0; 215 | } 216 | 217 | Rune 218 | tolowerrune(Rune c) 219 | { 220 | const Rune *p; 221 | 222 | p = ucd_bsearch(c, ucd_tolower2, nelem(ucd_tolower2)/3, 3); 223 | if(p && c >= p[0] && c <= p[1]) 224 | return c + p[2]; 225 | p = ucd_bsearch(c, ucd_tolower1, nelem(ucd_tolower1)/2, 2); 226 | if(p && c == p[0]) 227 | return c + p[1]; 228 | return c; 229 | } 230 | 231 | Rune 232 | toupperrune(Rune c) 233 | { 234 | const Rune *p; 235 | 236 | p = ucd_bsearch(c, ucd_toupper2, nelem(ucd_toupper2)/3, 3); 237 | if(p && c >= p[0] && c <= p[1]) 238 | return c + p[2]; 239 | p = ucd_bsearch(c, ucd_toupper1, nelem(ucd_toupper1)/2, 2); 240 | if(p && c == p[0]) 241 | return c + p[1]; 242 | return c; 243 | } 244 | 245 | int 246 | islowerrune(Rune c) 247 | { 248 | const Rune *p; 249 | 250 | p = ucd_bsearch(c, ucd_toupper2, nelem(ucd_toupper2)/3, 3); 251 | if(p && c >= p[0] && c <= p[1]) 252 | return 1; 253 | p = ucd_bsearch(c, ucd_toupper1, nelem(ucd_toupper1)/2, 2); 254 | if(p && c == p[0]) 255 | return 1; 256 | return 0; 257 | } 258 | 259 | int 260 | isupperrune(Rune c) 261 | { 262 | const Rune *p; 263 | 264 | p = ucd_bsearch(c, ucd_tolower2, nelem(ucd_tolower2)/3, 3); 265 | if(p && c >= p[0] && c <= p[1]) 266 | return 1; 267 | p = ucd_bsearch(c, ucd_tolower1, nelem(ucd_tolower1)/2, 2); 268 | if(p && c == p[0]) 269 | return 1; 270 | return 0; 271 | } 272 | 273 | int 274 | isalpharune(Rune c) 275 | { 276 | const Rune *p; 277 | 278 | p = ucd_bsearch(c, ucd_alpha2, nelem(ucd_alpha2)/2, 2); 279 | if(p && c >= p[0] && c <= p[1]) 280 | return 1; 281 | p = ucd_bsearch(c, ucd_alpha1, nelem(ucd_alpha1), 1); 282 | if(p && c == p[0]) 283 | return 1; 284 | return 0; 285 | } 286 | 287 | const Rune * 288 | tolowerrune_full(Rune c) 289 | { 290 | const Rune *p; 291 | p = ucd_bsearch(c, ucd_tolower_full, nelem(ucd_tolower_full)/4, 4); 292 | if(p && c == p[0]) 293 | return p + 1; 294 | return NULL; 295 | } 296 | 297 | const Rune * 298 | toupperrune_full(Rune c) 299 | { 300 | const Rune *p; 301 | p = ucd_bsearch(c, ucd_toupper_full, nelem(ucd_toupper_full)/5, 5); 302 | if(p && c == p[0]) 303 | return p + 1; 304 | return NULL; 305 | } 306 | -------------------------------------------------------------------------------- /utf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The authors of this software are Rob Pike and Ken Thompson. 3 | * Copyright (c) 2002 by Lucent Technologies. 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose without fee is hereby granted, provided that this entire notice 6 | * is included in all copies of any software which is or includes a copy 7 | * or modification of this software and in all copies of the supporting 8 | * documentation for such software. 9 | * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED 10 | * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE 11 | * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY 12 | * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. 13 | */ 14 | #ifndef js_utf_h 15 | #define js_utf_h 16 | 17 | typedef int Rune; /* 32 bits */ 18 | 19 | #define chartorune jsU_chartorune 20 | #define runetochar jsU_runetochar 21 | #define runelen jsU_runelen 22 | 23 | #define isalpharune jsU_isalpharune 24 | #define islowerrune jsU_islowerrune 25 | #define isupperrune jsU_isupperrune 26 | #define tolowerrune jsU_tolowerrune 27 | #define toupperrune jsU_toupperrune 28 | #define tolowerrune_full jsU_tolowerrune_full 29 | #define toupperrune_full jsU_toupperrune_full 30 | 31 | enum 32 | { 33 | UTFmax = 4, /* maximum bytes per rune */ 34 | Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */ 35 | Runeself = 0x80, /* rune and UTF sequences are the same (<) */ 36 | Runeerror = 0xFFFD, /* decoding error in UTF */ 37 | Runemax = 0x10FFFF, /* maximum rune value */ 38 | }; 39 | 40 | int chartorune(Rune *rune, const char *str); 41 | int runetochar(char *str, const Rune *rune); 42 | int runelen(int c); 43 | 44 | int isalpharune(Rune c); 45 | int islowerrune(Rune c); 46 | int isupperrune(Rune c); 47 | Rune tolowerrune(Rune c); 48 | Rune toupperrune(Rune c); 49 | const Rune* tolowerrune_full(Rune c); 50 | const Rune* toupperrune_full(Rune c); 51 | 52 | #endif 53 | --------------------------------------------------------------------------------