1 /*
2  * QuickJS: Example of C module with a class
3  *
4  * Copyright (c) 2019 Fabrice Bellard
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 #include "../quickjs.h"
25 #include <math.h>
26 
27 #define countof(x) (sizeof(x) / sizeof((x)[0]))
28 
29 /* Point Class */
30 
31 typedef struct {
32     int x;
33     int y;
34 } JSPointData;
35 
36 static JSClassID js_point_class_id;
37 
js_point_finalizer(JSRuntime * rt,JSValue val)38 static void js_point_finalizer(JSRuntime *rt, JSValue val)
39 {
40     JSPointData *s = JS_GetOpaque(val, js_point_class_id);
41     /* Note: 's' can be NULL in case JS_SetOpaque() was not called */
42     js_free_rt(rt, s);
43 }
44 
js_point_ctor(JSContext * ctx,JSValueConst new_target,int argc,JSValueConst * argv)45 static JSValue js_point_ctor(JSContext *ctx,
46                              JSValueConst new_target,
47                              int argc, JSValueConst *argv)
48 {
49     JSPointData *s;
50     JSValue obj = JS_UNDEFINED;
51     JSValue proto;
52 
53     s = js_mallocz(ctx, sizeof(*s));
54     if (!s)
55         return JS_EXCEPTION;
56     if (JS_ToInt32(ctx, &s->x, argv[0]))
57         goto fail;
58     if (JS_ToInt32(ctx, &s->y, argv[1]))
59         goto fail;
60     /* using new_target to get the prototype is necessary when the
61        class is extended. */
62     proto = JS_GetPropertyStr(ctx, new_target, "prototype");
63     if (JS_IsException(proto))
64         goto fail;
65     obj = JS_NewObjectProtoClass(ctx, proto, js_point_class_id);
66     JS_FreeValue(ctx, proto);
67     if (JS_IsException(obj))
68         goto fail;
69     JS_SetOpaque(obj, s);
70     return obj;
71  fail:
72     js_free(ctx, s);
73     JS_FreeValue(ctx, obj);
74     return JS_EXCEPTION;
75 }
76 
js_point_get_xy(JSContext * ctx,JSValueConst this_val,int magic)77 static JSValue js_point_get_xy(JSContext *ctx, JSValueConst this_val, int magic)
78 {
79     JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id);
80     if (!s)
81         return JS_EXCEPTION;
82     if (magic == 0)
83         return JS_NewInt32(ctx, s->x);
84     else
85         return JS_NewInt32(ctx, s->y);
86 }
87 
js_point_set_xy(JSContext * ctx,JSValueConst this_val,JSValue val,int magic)88 static JSValue js_point_set_xy(JSContext *ctx, JSValueConst this_val, JSValue val, int magic)
89 {
90     JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id);
91     int v;
92     if (!s)
93         return JS_EXCEPTION;
94     if (JS_ToInt32(ctx, &v, val))
95         return JS_EXCEPTION;
96     if (magic == 0)
97         s->x = v;
98     else
99         s->y = v;
100     return JS_UNDEFINED;
101 }
102 
js_point_norm(JSContext * ctx,JSValueConst this_val,int argc,JSValueConst * argv)103 static JSValue js_point_norm(JSContext *ctx, JSValueConst this_val,
104                              int argc, JSValueConst *argv)
105 {
106     JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id);
107     if (!s)
108         return JS_EXCEPTION;
109     return JS_NewFloat64(ctx, sqrt((double)s->x * s->x + (double)s->y * s->y));
110 }
111 
112 static JSClassDef js_point_class = {
113     "Point",
114     .finalizer = js_point_finalizer,
115 };
116 
117 static const JSCFunctionListEntry js_point_proto_funcs[] = {
118     JS_CGETSET_MAGIC_DEF("x", js_point_get_xy, js_point_set_xy, 0),
119     JS_CGETSET_MAGIC_DEF("y", js_point_get_xy, js_point_set_xy, 1),
120     JS_CFUNC_DEF("norm", 0, js_point_norm),
121 };
122 
js_point_init(JSContext * ctx,JSModuleDef * m)123 static int js_point_init(JSContext *ctx, JSModuleDef *m)
124 {
125     JSValue point_proto, point_class;
126 
127     /* create the Point class */
128     JS_NewClassID(&js_point_class_id);
129     JS_NewClass(JS_GetRuntime(ctx), js_point_class_id, &js_point_class);
130 
131     point_proto = JS_NewObject(ctx);
132     JS_SetPropertyFunctionList(ctx, point_proto, js_point_proto_funcs, countof(js_point_proto_funcs));
133 
134     point_class = JS_NewCFunction2(ctx, js_point_ctor, "Point", 2, JS_CFUNC_constructor, 0);
135     /* set proto.constructor and ctor.prototype */
136     JS_SetConstructor(ctx, point_class, point_proto);
137     JS_SetClassProto(ctx, js_point_class_id, point_proto);
138 
139     JS_SetModuleExport(ctx, m, "Point", point_class);
140     return 0;
141 }
142 
js_init_module(JSContext * ctx,const char * module_name)143 JSModuleDef *js_init_module(JSContext *ctx, const char *module_name)
144 {
145     JSModuleDef *m;
146     m = JS_NewCModule(ctx, module_name, js_point_init);
147     if (!m)
148         return NULL;
149     JS_AddModuleExport(ctx, m, "Point");
150     return m;
151 }
152