]> git.saurik.com Git - cycript.git/blob - libcycript.cy
5c809401c01394c5c2dba712d65107058767260d
[cycript.git] / libcycript.cy
1 /* Cycript - The Truly Universal Scripting Language
2 * Copyright (C) 2009-2016 Jay Freeman (saurik)
3 */
4
5 /* GNU Affero General Public License, Version 3 {{{ */
6 /*
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20 /* }}} */
21
22 (function() {
23
24 this.typeid = function(object) {
25 return object.$cyt;
26 };
27
28 let $cy_set = function(object, properties) {
29 for (const name in properties)
30 if ("defineProperty" in Object)
31 Object.defineProperty(object, name, {
32 configurable: true,
33 enumerable: false,
34 writable: true,
35 value: properties[name],
36 }); else object[name] = properties[name];
37 };
38
39 $cy_set(Boolean.prototype, {
40 toCYON: function() {
41 return `new Boolean(${this.toString()})`;
42 },
43 });
44
45 $cy_set(Date.prototype, {
46 toCYON: function() {
47 return `new ${this.constructor.name}(${this.toUTCString().toCYON()})`;
48 },
49 });
50
51 $cy_set(Error.prototype, {
52 toCYON: function() {
53 let stack = this.stack;
54 if (typeof stack == 'undefined')
55 stack = '';
56 else {
57 stack = stack.split('\n');
58 if (stack.slice(-1)[0] == "global code")
59 stack = stack.slice(0, -1);
60 for (let i = 0; i != stack.length; ++i)
61 stack[i] = '\n ' + stack[i];
62 if (stack.length == 0)
63 stack = '';
64 else {
65 stack = stack.join('');
66 stack = ` /*${stack} */`;
67 }
68 }
69 return `new ${this.constructor.name}(${this.message.toCYON()})${stack}`;
70 },
71 });
72
73 $cy_set(Number.prototype, {
74 toCYON: function() {
75 if ("$cyt" in this)
76 //return `${this.$cyt.toCYON()}(${this.toString()})`;
77 return this.toString();
78 return `new Number(${this.toString()})`;
79 },
80 });
81
82 $cy_set(RegExp.prototype, {
83 toCYON: function() {
84 return this.toString();
85 },
86 });
87
88 // XXX: Java should just always be available
89 if ("Java" in Cycript) {
90
91 // XXX: this is a half-assed EventEmitter
92 // XXX: this doesn't even have the same semantics
93
94 Java.handlers_ = {};
95
96 Java.on = function(event, handler) {
97 var handlers;
98 if (event in this.handlers_)
99 handlers = this.handlers_[event];
100 else {
101 handlers = [];
102 this.handlers_[event] = handlers;
103 }
104
105 if (this.handlers_ == null)
106 handler();
107 else
108 handlers.push(handler);
109 };
110
111 Java.emit = function(event) {
112 if (event in this.handlers_) {
113 var handlers = this.handlers_[event];
114 if (handlers != null)
115 for (var handler of handlers)
116 handler();
117 }
118
119 this.handlers_[event] = null;
120 };
121
122 Java.on('setup', function() {
123 $cy_set(java.lang.Boolean.prototype, {
124 toCYON: function() {
125 return `new java.lang.Boolean(${this->value})`;
126 },
127 });
128
129 $cy_set(java.lang.Byte.prototype, {
130 toCYON: function() {
131 return `new java.lang.Byte(${this->value})`;
132 },
133 });
134
135 $cy_set(java.lang.Character.prototype, {
136 toCYON: function() {
137 return `new java.lang.Character(${this->value})`;
138 },
139 });
140
141 $cy_set(java.lang.Short.prototype, {
142 toCYON: function() {
143 return `new java.lang.Short(${this->value})`;
144 },
145 });
146
147 $cy_set(java.lang.Integer.prototype, {
148 toCYON: function() {
149 return `new java.lang.Integer(${this->value})`;
150 },
151 });
152
153 $cy_set(java.lang.Long.prototype, {
154 toCYON: function() {
155 return `new java.lang.Long(${this->value})`;
156 },
157 });
158
159 $cy_set(java.lang.Float.prototype, {
160 toCYON: function() {
161 return `new java.lang.Float(${this->value})`;
162 },
163 });
164
165 $cy_set(java.lang.Double.prototype, {
166 toCYON: function() {
167 return `new java.lang.Double(${this->value})`;
168 },
169 });
170
171 $cy_set(java.lang.Object.prototype, {
172 toCYON: function(key) {
173 return "#" + this.toString().toCYON();
174 },
175
176 // XXX: due to lack of interface prototypes :(
177 $cyg: function(key) {
178 return this.get(key);
179 },
180
181 // XXX: due to lack of interface prototypes :(
182 $cys: function(key, value) {
183 if ("set" in this)
184 this.set(key, value);
185 else
186 this.put(key, value);
187 },
188 });
189
190 $cy_set(java.lang.Throwable.prototype, {
191 toCYON: function() {
192 var message = this.getMessage();
193 if (message == null)
194 message = '';
195 else
196 message = message.toCYON();
197
198 let stack = this.getStackTrace();
199 if (stack.length == 0)
200 stack = '';
201 else {
202 stack = stack.join('\n ');
203 stack = ` /*\n ${stack} */`;
204 }
205
206 return `new ${this.constructor.class.getName()}(${message})${stack}`;
207 },
208 });
209 });
210
211 }
212
213 if ("ObjectiveC" in Cycript) {
214 $cy_set(NSArray.prototype, {
215 $cyg: function(key) {
216 return objc_msgSend(this, "objectAtIndex:", key);
217 },
218
219 $cys: function(key, value) {
220 return objc_msgSend(this, "setObject:atIndex:", value, key);
221 },
222 });
223
224 $cy_set(NSDictionary.prototype, {
225 $cyg: function(key) {
226 return objc_msgSend(this, "objectForKey:", key);
227 },
228
229 $cys: function(key, value) {
230 return objc_msgSend(this, "setObject:forKey:", value, key);
231 },
232 });
233 }
234
235 let IsFile = function(path) {
236 // XXX: this doesn't work on symlinks, but I don't want to fix stat :/
237 return access(path, F_OK) == 0 && access(path + '/', F_OK) == -1;
238 };
239
240 let StartsWith = function(lhs, rhs) {
241 return lhs.substring(0, rhs.length) == rhs;
242 };
243
244 let ResolveFile = function(exact, name) {
245 if (exact && IsFile(name))
246 return name;
247 for (let suffix of ['.js', '.json'])
248 if (IsFile(name + suffix))
249 return name + suffix;
250 return null;
251 };
252
253
254 let GetLibraryPath = function() {
255 let handle = dlopen("/usr/lib/libcycript.dylib", RTLD_NOLOAD);
256 if (handle == null)
257 return null;
258
259 try {
260 let CYHandleServer = dlsym(handle, "CYHandleServer");
261 if (CYHandleServer == null)
262 return null;
263
264 let info = new Dl_info;
265 if (dladdr(CYHandleServer, info) == 0)
266 return null;
267
268 let path = info->dli_fname;
269 let slash = path.lastIndexOf('/');
270 if (slash == -1)
271 return null;
272
273 path = path.substr(0, slash);
274
275 GetLibraryPath = function() {
276 return path;
277 };
278
279 return GetLibraryPath();
280 } finally {
281 dlclose(handle);
282 }
283 };
284
285 let ResolveFolder = function(name) {
286 if (access(name + '/', F_OK) == -1)
287 return null;
288
289 if (IsFile(name + "/package.json")) {
290 let package = require(name + "/package.json");
291 let path = ResolveFile(true, name + "/" + package.main);
292 if (path != null)
293 return path;
294 }
295
296 return ResolveFile(false, name + "/index");
297 };
298
299 let ResolveEither = function(name) {
300 let path = null;
301 if (path == null)
302 path = ResolveFile(true, name);
303 if (path == null)
304 path = ResolveFolder(name);
305 return path;
306 };
307
308 require.resolve = function(name) {
309 if (StartsWith(name, '/')) {
310 let path = ResolveEither(name);
311 if (path != null)
312 return path;
313 } else {
314 let cwd = *new (typedef char[1024]);
315 cwd = getcwd(cwd, cwd.length).toString();
316 cwd = cwd.split('/');
317
318 if (StartsWith(name, './') || StartsWith(name, '../')) {
319 let path = ResolveEither(cwd + '/' + name);
320 if (path != null)
321 return path;
322 } else {
323 for (let i = cwd.length; i != 0; --i) {
324 let modules = cwd.slice(0, i).concat("node_modules").join('/');
325 let path = ResolveEither(modules + "/" + name);
326 if (path != null)
327 return path;
328 }
329
330 let library = GetLibraryPath();
331 let path = ResolveFile(true, library + "/cycript0.9/" + name + ".cy");
332 if (path != null)
333 return path;
334 }
335 }
336
337 throw new Error("Cannot find module '" + name + "'");
338 };
339
340 var _syscall = function(value) {
341 if (value == -1)
342 throw new Error(strerror(errno));
343 };
344
345 var info = *new (struct stat);
346 if (false) {
347 } else if ("st_atim" in info) {
348 var st_atime = "st_atim";
349 var st_mtime = "st_mtim";
350 var st_ctime = "st_ctim";
351 } else if ("st_atimespec" in info) {
352 var st_atime = "st_atimespec";
353 var st_mtime = "st_mtimespec";
354 var st_ctime = "st_ctimespec";
355 } else {
356 var st_atime = "st_atime";
357 var st_mtime = "st_mtime";
358 var st_ctime = "st_ctime";
359 }
360
361 var toDate = function(timespec) {
362 return new Date(timespec.tv_sec * 1000 + timespec.tv_nsec / 1000);
363 };
364
365 var bindings = {};
366
367 process.binding = function(name) {
368 let binding = bindings[name];
369 if (typeof binding != 'undefined')
370 return binding;
371
372 switch (name) {
373 case 'buffer': binding = {
374 setupBufferJS() {
375 },
376 }; break;
377
378 case 'cares_wrap': binding = {
379 }; break;
380
381 case 'constants': binding = {
382 }; break;
383
384 case 'fs': binding = {
385 FSInitialize() {
386 },
387
388 lstat(path) {
389 var info = new (struct stat);
390 _syscall(lstat(path, info));
391
392 return {
393 dev: info->st_dev,
394 mode: info->st_mode,
395 nlink: info->st_nlink,
396 uid: info->st_uid,
397 gid: info->st_gid,
398 rdev: info->st_rdev,
399 blksize: info->st_blksize,
400 ino: info->st_ino,
401 size: info->st_size,
402 blocks: info->st_blocks,
403
404 atime: toDate(info->[st_atime]),
405 mtime: toDate(info->[st_mtime]),
406 ctime: toDate(info->[st_ctime]),
407
408 isSymbolicLink() {
409 return S_ISLNK(this.mode);
410 },
411 };
412 },
413 }; break;
414
415 case 'pipe_wrap': binding = {
416 }; break;
417
418 case 'smalloc': binding = {
419 alloc() {
420 },
421 }; break;
422
423 case 'stream_wrap': binding = {
424 }; break;
425
426 case 'tcp_wrap': binding = {
427 }; break;
428
429 case 'timer_wrap': binding = {
430 kOnTimeout: 0,
431 Timer: {
432 },
433 }; break;
434
435 case 'tty_wrap': binding = {
436 }; break;
437
438 case 'uv': binding = {
439 }; break;
440
441 default:
442 throw new Error('No such module: ' + name);
443 }
444
445 bindings[name] = binding;
446 return binding;
447 };
448
449 process.env = {};
450
451 let environ = *(typedef char ***)(dlsym(RTLD_DEFAULT, "environ"));
452 for (let i = 0; environ[i] != null; ++i) {
453 let assign = environ[i];
454 let equal = assign.indexOf('=');
455 let name = assign.substr(0, equal);
456 let value = assign.substr(equal + 1);
457 process.env[name.toString()] = value;
458 }
459
460 process.cwd = function() {
461 let cwd = new (typedef char[1024]);
462 return getcwd(cwd, cwd.length).toString();
463 };
464
465 process.pid = getpid();
466
467 })();