diff --git a/.gitignore b/.gitignore index 3524f32..88e5e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ # Generated by configure Makefile libwren*.pc -/src/vm.vala /src/basics.vapi +/src/marshal.vala +/src/trampoline.vala +/src/vm.vala +/src/wrennull.c /t/common.sh # Prerequisites diff --git a/Makefile.am b/Makefile.am index 3727bd0..434486a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,23 +24,47 @@ EXTRA_DIST = \ distcheck-hook: chmod -R u+w $(distdir) +# --- Code hygiene ------------------------------------------- + phony += prettyprint prettyprint: -$(AM_V_GEN)uncrustify -l VALA -c $(top_srcdir)/.uncrustifyrc --replace \ src/basics.vapi.in \ + src/marshal.vala.in \ + src/trampoline.vala.in \ src/vm.vala.in \ + src/wrennull.c.in \ t/000-sanity-t.vala \ + t/105-tramp-t.vala \ t/100-vmv-t.vala \ t/110-hello-world-s.vala \ t/120-read-var-from-wren-t.vala \ t/130-call-wren-t.vala \ t/140-roundtrip-data-t.vala \ + t/150-marshal-t.vala \ + t/200-expose-class-t.vala \ $(EOL) +phony += p +p: prettyprint + -ctags -R + phony += cleanwren cleanwren: -cd wren-pkg/wren && git clean -dfx +# --- Testing ------------------------------------------------ + +# Shortcuts for tests +phony += test +test: check + +phony += build-tests +build-tests: all + +$(MAKE) -C t $@ + +# --- Coverage ----------------------------------------------- + # Used by coverage.sh --- remove the existing code-coverage data. remove-code-coverage-data: -rm -rf "$(CODE_COVERAGE_OUTPUT_FILE)" "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" diff --git a/configure.ac b/configure.ac index 78e8452..c30d9ac 100644 --- a/configure.ac +++ b/configure.ac @@ -190,13 +190,18 @@ AC_CONFIG_FILES([ Makefile doc/Makefile src/Makefile - src/basics.vapi - src/vm.vala t/Makefile t/common.sh wren-pkg/Makefile ]) +dnl Source files: make them RO +AC_CONFIG_FILES([src/basics.vapi], [chmod a-w src/basics.vapi]) +AC_CONFIG_FILES([src/marshal.vala], [chmod a-w src/marshal.vala]) +AC_CONFIG_FILES([src/trampoline.vala], [chmod a-w src/trampoline.vala]) +AC_CONFIG_FILES([src/vm.vala], [chmod a-w src/vm.vala]) +AC_CONFIG_FILES([src/wrennull.c], [chmod a-w src/wrennull.c]) + dnl pkg-config files: name them after the Wren version AC_CONFIG_FILES([wren-pkg/libwren-"${APIVER}".pc:wren-pkg/libwren.pc.in], [], [APIVER=$APIVER]) AC_CONFIG_FILES([src/libwren-vala-"${APIVER}".pc:src/libwren-vala.pc.in], [], [APIVER=$APIVER]) diff --git a/doc/Makefile.am b/doc/Makefile.am index 9d62497..523b01b 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -17,7 +17,10 @@ valadoc: $(AM_V_GEN)valadoc -o valadoc -b $(top_srcdir)/src --verbose --force \ --package-name='@PACKAGE_TARNAME@' \ --package-version='@PACKAGE_VERSION@' \ - $(top_srcdir)/src/basics.vapi $(top_srcdir)/src/vm.vala \ + $(top_srcdir)/src/basics.vapi \ + $(top_srcdir)/src/marshal.vala \ + $(top_srcdir)/src/trampoline.vala \ + $(top_srcdir)/src/vm.vala \ --doclet=html \ $(MY_VALA_PKGS) \ $(EOL) diff --git a/src/Makefile.am b/src/Makefile.am index 11e4da1..508cd08 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,8 +28,11 @@ endif libwren_vala_la_SOURCES = \ basics.vapi \ + marshal.vala \ myconfig.vapi \ + trampoline.vala \ vm.vala \ + wrennull.c \ $(EOL) libwren_vala_la_VALAFLAGS = \ diff --git a/src/basics.vapi.in b/src/basics.vapi.in index 60b6d56..2e487d7 100644 --- a/src/basics.vapi.in +++ b/src/basics.vapi.in @@ -44,6 +44,7 @@ namespace Wren { [CCode(has_target = false)] public delegate void LoadModuleCompleteFn(VM vm, string name, LoadModuleResult result); + [SimpleType] public struct LoadModuleResult { string source; @@ -96,6 +97,8 @@ namespace Wren { public delegate void ErrorFn(VM vm, ErrorType type, string? module, int line, string message); + // Has to be a SimpleType so it can be returned by value from functions. + [SimpleType] public struct ForeignClassMethods { ForeignMethodFn allocate; @@ -177,7 +180,7 @@ namespace Wren { public extern int GetSlotCount(); public extern void EnsureSlots(int numSlots); - public extern Type GetSlotType(int slot); + public extern Wren.Type GetSlotType(int slot); public extern bool GetSlotBool(int slot); public extern unowned uint8[] GetSlotBytes(int slot); public extern double GetSlotDouble(int slot); diff --git a/src/marshal.vala.in b/src/marshal.vala.in new file mode 100644 index 0000000..773d0c4 --- /dev/null +++ b/src/marshal.vala.in @@ -0,0 +1,127 @@ +// @configure_input@ + +// marshal.vala: Wren<->Vala marshalling +// +// By Christopher White +// Copyright (c) 2021 Christopher White. All rights reserved. +// SPDX-License-Identifier: MIT + +[CCode(cheader_filename="libwren-vala-@APIVER@.h,@WREN_HEADER@")] +namespace Wren { + + /** + * Vala type representing the Wren null value. + * + * We use this because you can't create {@link GLib.Value}s of + * {@link GLib.Type.NONE}. + * + * In wrennull.c. + */ + public extern GLib.Type get_null_type(); + + /** + * Initialize the Wren Vala bindings. + * + * Call this before calling any of the marshalling functions. + * {@link Wren.VMV}'s constructors call this for you. + * + * Idempotent. + */ + public void static_init() + { + if(!static_init_done) { + return; + } + + // At the moment, this function is somewhat pointless, since the only + // thing in here is the idempotent initialization of the wren-null type. + // However, leaving the function here means that we won't have to change + // the API if we have to add more initialization steps in the future. + + get_null_type(); + static_init_done = true; + } + private bool static_init_done = false; + + [CCode(cheader_filename="libwren-vala-@APIVER@.h,@WREN_HEADER@")] + namespace Marshal { + + public errordomain MarshalError { + /** Slot or other item not found */ + ENOENT, + } + + /** + * Wrap a single Wren slot in a GValue. + * + * The mapping is: + * * Wren BOOL -> GLib.Type.BOOLEAN + * * Wren NULL -> GLib.Type.NONE + * + * @param vm The vm to read from + * @param slot The slot to grab. It must exist. + */ + public Value to_value_raw(Wren.VM vm, int slot) + { + var ty = vm.GetSlotType(slot); + + switch(ty) { + case BOOL: + return vm.GetSlotBool(slot); + case NUM: + return vm.GetSlotDouble(slot); + case FOREIGN: + // TODO + break; + case LIST: + // TODO + break; + case MAP: + // TODO + break; + case NULL: + return Value(get_null_type()); + case STRING: + return vm.GetSlotString(slot); + case UNKNOWN: + // TODO + break; + default: + assert_not_reached(); + } + + return Value(GLib.Type.INVALID); + } + + /** + * Wrap Wren slots in a GValue array. + * + * @param vm The vm to read from + * @param first_slot The first slot to grab (default 0) + * @param num_slots How many slots to grab (default -1 == the whole + * slot array) + * @return An array of freshly-created GValues. + */ + public Value[] to_values_raw(Wren.VM vm, int first_slot = 0, int num_slots = -1) + throws MarshalError + { + if(num_slots == -1) { + num_slots = vm.GetSlotCount(); + } + + if(first_slot + num_slots >= vm.GetSlotCount()) { + throw new MarshalError.ENOENT( + "Slots up to %d requested, but only %d are available".printf( + first_slot + num_slots, vm.GetSlotCount())); + } + + Value[] retval = new Value[num_slots]; + for(int i=0; i +// SPDX-License-Identifier: MIT + +[CCode(cheader_filename="libwren-vala-@APIVER@.h,@WREN_HEADER@")] +namespace Wren { + + /** + * Trampolines for allocating Object subclasses. + * + * Requires that the vm userdata be a {@link GLib.Object} subclass that: + * * has a tramp_ property returning an instance of this class; and + * * implements {@link Wren.HasMethods} + * */ + [CCode(cheader_filename="libwren-vala-@APIVER@.h,@WREN_HEADER@")] + public class Tramp : Object { + + /** + * Classes exposed to Wren + * + * Maps from {@link hash_key()} retval to index in {@link types}. + */ + HashTable classes = new HashTable(str_hash, str_equal); + + /** + * Types we can instantiate + * + * Indices map 1-1 to {@link allocators}. + */ + public Array types = new Array(); + + + /** + * Functions that instantiate those types. + * + * Indices map 1-1 to {@link types}. + */ + public Array allocators; + + /** + * Methods exposed to Wren + * + * Maps from {@link hash_key()} retval to index in {@link methodImpls}. + */ + HashTable methodIdxes = new HashTable(str_hash, str_equal); + + /** + * Functions that implement methods + */ + public Array methodThunks; + + /** + * Functions that implement methods + */ + public Array methodImpls = new Array(); + + // --- Helper functions --------------------------------------- + + /** + * Get a Tramp instance from a VM. + * + * Requires the VM's userdata hold a GLib.Object instance with a + * property called `tramp_`. + */ + private static Tramp tramp_from_vm(VM vm) + { + var userdata = vm.GetUserData() as Object; + assert(userdata != null); // TODO better error handling + Value self_value = Value(typeof(Tramp)); + userdata.get_property("tramp_", ref self_value); + var self = self_value.get_object() as Tramp; + assert(self != null); + return self; + } + + // --- Public interface --------------------------------------- + + /** + * Make a hash key for a class or method. + * + * * You must specify neither isStatic nor signature for a class. + * * You must specify both of those for a method. + */ + public static string hash_key(string module, string className, + bool isStatic = false, string? signature = null) + { + if(signature == null) { + return @"$module!$className"; + } else { + + // $ = arbitrary character not valid in a Wren identifier + var staticflag = isStatic ? "$" : ""; + + return @"$module!$className.$staticflag$signature"; + } + } + + /** Regex for foreign_decl_for_key */ + private static Regex fdfk_re = null; + + /** + * Return a foreign-method declaration for a given hash key. + * + * This in effect reverses {@link hash_key}. + */ + public static string foreign_decl_for_key(string key) + { + + if(fdfk_re == null) { + try { + var ident = "(?:[A-Za-z_][A-Za-z_0-9]*)"; + fdfk_re = new Regex( + "(?%s)!(?%s).(?\\$?)(?%s)(?.*)$".printf( + ident, ident, ident)); + } catch(RegexError e) { + debug("Could not create regex for Tramp.foreign_decl_for_key(): %s", e.message); + assert(false); // I can't go on + } + } + + MatchInfo matches; + if(!fdfk_re.match(key, 0, out matches)) { + return "INVALID_FORMAT!"; // an invalid function declaration + } + + var declaration = new StringBuilder(); + declaration.append("foreign "); + var staticflag = matches.fetch_named("static"); + if((!)staticflag != "") { + declaration.append("static "); + } + + declaration.append((!)matches.fetch_named("name")); + + var rest = (!)matches.fetch_named("rest"); + if(rest.data[0] == '=') { // setter + declaration.append("="); + rest = rest.substring(1); + } + + // Parameters, if any: convert `_` to unique identifiers + int paramnum = -1; + if(rest.data[0] == '(') { + declaration.append("("); + var parms = rest.substring(1,rest.length-2).split(","); + for(int i=0; i < parms.length; ++i) { + if(i!=0) { + declaration.append(","); + } + declaration.append_printf("p%d", ++paramnum); + } + declaration.append(")"); + } + + return declaration.str; + } // foreign_decl_for_key + + /** + * Return the allocator and finalizer functions for a type. + */ + public ForeignClassMethods get_functions(string module, string className) + { + ForeignClassMethods retval = {}; + + var key = hash_key(module, className); + if(classes.contains(key)) { + uint idx = classes[key]; + retval.allocate = allocators.data[idx]; + retval.finalize = deallocateObject; + debug("Functions for %s @ %u: %p, %p", key, idx, &retval.allocate, &retval.finalize); + } + return retval; + } // get_functions() + + /** + * Get a method of a class. + */ + public ForeignMethodFn get_method(string module, string className, bool isStatic, + string signature) + { + ForeignMethodFn retval = null; + + var key = hash_key(module, className, isStatic, signature); + assert(methodIdxes.contains(key)); // TODO better error handling + + uint idx = methodIdxes[key]; + retval = methodThunks.data[idx]; + debug("Function for %s @ %u: %p", key, idx, &retval); + + return retval; + } // get_method() + + public static void foreach_method_of_type(GLib.Type type, GLib.HFunc callback) + { + var instance = Object.new(type); + assert(instance != null); + var has_methods = instance as HasMethods; + if(has_methods != null) { + debug("Getting methods of %s", type.to_string()); + var methods = new HashTable(str_hash, str_equal); + has_methods.get_methods(ref methods); + methods.foreach(callback); + } + } + + /** + * Add a type. + * + * Idempotent. + */ + public void add_type(string module, string className, GLib.Type type) + { + var key = hash_key(module, className); + + if(classes.contains(key)) { + return; + } + + types.append_val(type); + classes[key] = types.length - 1; + assert(types.length <= allocators.length); // TODO better error handling + debug("Class %s now at index %u", key, classes[key]); + + debug("Loading methods"); + foreach_method_of_type(type, (k,v) => { + methodImpls.append_val(v); + var idx = methodImpls.length - 1; + methodIdxes[k] = idx; + }); + } + + // --- Allocating and deallocating instances ------------------ + + /** + * Instantiate a foreign class + * + * @param vm The Wren VM to use + * @param index The index in {@link types} and {@link allocators} to use. + */ + private static void instantiate(VM vm, uint index) + { + debug("instantiate: vm %p, index %u", vm, index); + + var self = tramp_from_vm(vm); + assert(index < self.types.length); + + // Do the work! + var type = self.types.data[index]; + var instance = Object.new(type); + + // ref() to counteract the automatic unref() at the end of this function + instance.ref (); + + // Give the instance to Wren + unowned Object **ppobject = (Object **)vm.SetSlotNewForeign(0, 0, sizeof(Object)); + *ppobject = instance; + + debug("index %u: %p", index, instance); + } + + /** Free a GObject instance */ + private static void deallocateObject(void *data) + { + unowned Object **ppobject = (Object **)data; + debug("deallocateObject %p\n", ppobject); + if(ppobject != null) { + unowned Object *obj = *ppobject; + obj->unref(); + } + } + + // This is incredibly tedious. We hold 100 functions and pointers to them. + private static void i0(VM vm) { + instantiate(vm, 0); + } + private static void i1(VM vm) { + instantiate(vm, 1); + } + private static void i2(VM vm) { + instantiate(vm, 2); + } + private static void i3(VM vm) { + instantiate(vm, 3); + } + private static void i4(VM vm) { + instantiate(vm, 4); + } + private static void i5(VM vm) { + instantiate(vm, 5); + } + private static void i6(VM vm) { + instantiate(vm, 6); + } + private static void i7(VM vm) { + instantiate(vm, 7); + } + private static void i8(VM vm) { + instantiate(vm, 8); + } + private static void i9(VM vm) { + instantiate(vm, 9); + } + private static void i10(VM vm) { + instantiate(vm, 10); + } + private static void i11(VM vm) { + instantiate(vm, 11); + } + private static void i12(VM vm) { + instantiate(vm, 12); + } + private static void i13(VM vm) { + instantiate(vm, 13); + } + private static void i14(VM vm) { + instantiate(vm, 14); + } + private static void i15(VM vm) { + instantiate(vm, 15); + } + private static void i16(VM vm) { + instantiate(vm, 16); + } + private static void i17(VM vm) { + instantiate(vm, 17); + } + private static void i18(VM vm) { + instantiate(vm, 18); + } + private static void i19(VM vm) { + instantiate(vm, 19); + } + private static void i20(VM vm) { + instantiate(vm, 20); + } + private static void i21(VM vm) { + instantiate(vm, 21); + } + private static void i22(VM vm) { + instantiate(vm, 22); + } + private static void i23(VM vm) { + instantiate(vm, 23); + } + private static void i24(VM vm) { + instantiate(vm, 24); + } + private static void i25(VM vm) { + instantiate(vm, 25); + } + private static void i26(VM vm) { + instantiate(vm, 26); + } + private static void i27(VM vm) { + instantiate(vm, 27); + } + private static void i28(VM vm) { + instantiate(vm, 28); + } + private static void i29(VM vm) { + instantiate(vm, 29); + } + private static void i30(VM vm) { + instantiate(vm, 30); + } + private static void i31(VM vm) { + instantiate(vm, 31); + } + private static void i32(VM vm) { + instantiate(vm, 32); + } + private static void i33(VM vm) { + instantiate(vm, 33); + } + private static void i34(VM vm) { + instantiate(vm, 34); + } + private static void i35(VM vm) { + instantiate(vm, 35); + } + private static void i36(VM vm) { + instantiate(vm, 36); + } + private static void i37(VM vm) { + instantiate(vm, 37); + } + private static void i38(VM vm) { + instantiate(vm, 38); + } + private static void i39(VM vm) { + instantiate(vm, 39); + } + private static void i40(VM vm) { + instantiate(vm, 40); + } + private static void i41(VM vm) { + instantiate(vm, 41); + } + private static void i42(VM vm) { + instantiate(vm, 42); + } + private static void i43(VM vm) { + instantiate(vm, 43); + } + private static void i44(VM vm) { + instantiate(vm, 44); + } + private static void i45(VM vm) { + instantiate(vm, 45); + } + private static void i46(VM vm) { + instantiate(vm, 46); + } + private static void i47(VM vm) { + instantiate(vm, 47); + } + private static void i48(VM vm) { + instantiate(vm, 48); + } + private static void i49(VM vm) { + instantiate(vm, 49); + } + private static void i50(VM vm) { + instantiate(vm, 50); + } + private static void i51(VM vm) { + instantiate(vm, 51); + } + private static void i52(VM vm) { + instantiate(vm, 52); + } + private static void i53(VM vm) { + instantiate(vm, 53); + } + private static void i54(VM vm) { + instantiate(vm, 54); + } + private static void i55(VM vm) { + instantiate(vm, 55); + } + private static void i56(VM vm) { + instantiate(vm, 56); + } + private static void i57(VM vm) { + instantiate(vm, 57); + } + private static void i58(VM vm) { + instantiate(vm, 58); + } + private static void i59(VM vm) { + instantiate(vm, 59); + } + private static void i60(VM vm) { + instantiate(vm, 60); + } + private static void i61(VM vm) { + instantiate(vm, 61); + } + private static void i62(VM vm) { + instantiate(vm, 62); + } + private static void i63(VM vm) { + instantiate(vm, 63); + } + private static void i64(VM vm) { + instantiate(vm, 64); + } + private static void i65(VM vm) { + instantiate(vm, 65); + } + private static void i66(VM vm) { + instantiate(vm, 66); + } + private static void i67(VM vm) { + instantiate(vm, 67); + } + private static void i68(VM vm) { + instantiate(vm, 68); + } + private static void i69(VM vm) { + instantiate(vm, 69); + } + private static void i70(VM vm) { + instantiate(vm, 70); + } + private static void i71(VM vm) { + instantiate(vm, 71); + } + private static void i72(VM vm) { + instantiate(vm, 72); + } + private static void i73(VM vm) { + instantiate(vm, 73); + } + private static void i74(VM vm) { + instantiate(vm, 74); + } + private static void i75(VM vm) { + instantiate(vm, 75); + } + private static void i76(VM vm) { + instantiate(vm, 76); + } + private static void i77(VM vm) { + instantiate(vm, 77); + } + private static void i78(VM vm) { + instantiate(vm, 78); + } + private static void i79(VM vm) { + instantiate(vm, 79); + } + private static void i80(VM vm) { + instantiate(vm, 80); + } + private static void i81(VM vm) { + instantiate(vm, 81); + } + private static void i82(VM vm) { + instantiate(vm, 82); + } + private static void i83(VM vm) { + instantiate(vm, 83); + } + private static void i84(VM vm) { + instantiate(vm, 84); + } + private static void i85(VM vm) { + instantiate(vm, 85); + } + private static void i86(VM vm) { + instantiate(vm, 86); + } + private static void i87(VM vm) { + instantiate(vm, 87); + } + private static void i88(VM vm) { + instantiate(vm, 88); + } + private static void i89(VM vm) { + instantiate(vm, 89); + } + private static void i90(VM vm) { + instantiate(vm, 90); + } + private static void i91(VM vm) { + instantiate(vm, 91); + } + private static void i92(VM vm) { + instantiate(vm, 92); + } + private static void i93(VM vm) { + instantiate(vm, 93); + } + private static void i94(VM vm) { + instantiate(vm, 94); + } + private static void i95(VM vm) { + instantiate(vm, 95); + } + private static void i96(VM vm) { + instantiate(vm, 96); + } + private static void i97(VM vm) { + instantiate(vm, 97); + } + private static void i98(VM vm) { + instantiate(vm, 98); + } + private static void i99(VM vm) { + instantiate(vm, 99); + } + + // --- Calling methods ---------------------------------------- + + /** + * Call a method on a class instance + * + * @param vm The Wren VM to use + * @param index The index in {@link methodImpls} to use. + */ + private static void call_method(VM vm, uint index) + { + debug("call_method: vm %p, index %u", vm, index); + + var self = tramp_from_vm(vm); + assert(index < self.methodImpls.length); + + unowned Object **ppobject = (Object **)vm.GetSlotForeign(0); + var vmv = vm.GetUserData() as Wren.VMV; + self.methodImpls.data[index](*ppobject, vmv); + } + + // Also tedious. 100 of these. `cN` = call N + private static void c0(VM vm) { + call_method(vm, 0); + } + private static void c1(VM vm) { + call_method(vm, 1); + } + private static void c2(VM vm) { + call_method(vm, 2); + } + private static void c3(VM vm) { + call_method(vm, 3); + } + private static void c4(VM vm) { + call_method(vm, 4); + } + private static void c5(VM vm) { + call_method(vm, 5); + } + private static void c6(VM vm) { + call_method(vm, 6); + } + private static void c7(VM vm) { + call_method(vm, 7); + } + private static void c8(VM vm) { + call_method(vm, 8); + } + private static void c9(VM vm) { + call_method(vm, 9); + } + private static void c10(VM vm) { + call_method(vm, 10); + } + private static void c11(VM vm) { + call_method(vm, 11); + } + private static void c12(VM vm) { + call_method(vm, 12); + } + private static void c13(VM vm) { + call_method(vm, 13); + } + private static void c14(VM vm) { + call_method(vm, 14); + } + private static void c15(VM vm) { + call_method(vm, 15); + } + private static void c16(VM vm) { + call_method(vm, 16); + } + private static void c17(VM vm) { + call_method(vm, 17); + } + private static void c18(VM vm) { + call_method(vm, 18); + } + private static void c19(VM vm) { + call_method(vm, 19); + } + private static void c20(VM vm) { + call_method(vm, 20); + } + private static void c21(VM vm) { + call_method(vm, 21); + } + private static void c22(VM vm) { + call_method(vm, 22); + } + private static void c23(VM vm) { + call_method(vm, 23); + } + private static void c24(VM vm) { + call_method(vm, 24); + } + private static void c25(VM vm) { + call_method(vm, 25); + } + private static void c26(VM vm) { + call_method(vm, 26); + } + private static void c27(VM vm) { + call_method(vm, 27); + } + private static void c28(VM vm) { + call_method(vm, 28); + } + private static void c29(VM vm) { + call_method(vm, 29); + } + private static void c30(VM vm) { + call_method(vm, 30); + } + private static void c31(VM vm) { + call_method(vm, 31); + } + private static void c32(VM vm) { + call_method(vm, 32); + } + private static void c33(VM vm) { + call_method(vm, 33); + } + private static void c34(VM vm) { + call_method(vm, 34); + } + private static void c35(VM vm) { + call_method(vm, 35); + } + private static void c36(VM vm) { + call_method(vm, 36); + } + private static void c37(VM vm) { + call_method(vm, 37); + } + private static void c38(VM vm) { + call_method(vm, 38); + } + private static void c39(VM vm) { + call_method(vm, 39); + } + private static void c40(VM vm) { + call_method(vm, 40); + } + private static void c41(VM vm) { + call_method(vm, 41); + } + private static void c42(VM vm) { + call_method(vm, 42); + } + private static void c43(VM vm) { + call_method(vm, 43); + } + private static void c44(VM vm) { + call_method(vm, 44); + } + private static void c45(VM vm) { + call_method(vm, 45); + } + private static void c46(VM vm) { + call_method(vm, 46); + } + private static void c47(VM vm) { + call_method(vm, 47); + } + private static void c48(VM vm) { + call_method(vm, 48); + } + private static void c49(VM vm) { + call_method(vm, 49); + } + private static void c50(VM vm) { + call_method(vm, 50); + } + private static void c51(VM vm) { + call_method(vm, 51); + } + private static void c52(VM vm) { + call_method(vm, 52); + } + private static void c53(VM vm) { + call_method(vm, 53); + } + private static void c54(VM vm) { + call_method(vm, 54); + } + private static void c55(VM vm) { + call_method(vm, 55); + } + private static void c56(VM vm) { + call_method(vm, 56); + } + private static void c57(VM vm) { + call_method(vm, 57); + } + private static void c58(VM vm) { + call_method(vm, 58); + } + private static void c59(VM vm) { + call_method(vm, 59); + } + private static void c60(VM vm) { + call_method(vm, 60); + } + private static void c61(VM vm) { + call_method(vm, 61); + } + private static void c62(VM vm) { + call_method(vm, 62); + } + private static void c63(VM vm) { + call_method(vm, 63); + } + private static void c64(VM vm) { + call_method(vm, 64); + } + private static void c65(VM vm) { + call_method(vm, 65); + } + private static void c66(VM vm) { + call_method(vm, 66); + } + private static void c67(VM vm) { + call_method(vm, 67); + } + private static void c68(VM vm) { + call_method(vm, 68); + } + private static void c69(VM vm) { + call_method(vm, 69); + } + private static void c70(VM vm) { + call_method(vm, 70); + } + private static void c71(VM vm) { + call_method(vm, 71); + } + private static void c72(VM vm) { + call_method(vm, 72); + } + private static void c73(VM vm) { + call_method(vm, 73); + } + private static void c74(VM vm) { + call_method(vm, 74); + } + private static void c75(VM vm) { + call_method(vm, 75); + } + private static void c76(VM vm) { + call_method(vm, 76); + } + private static void c77(VM vm) { + call_method(vm, 77); + } + private static void c78(VM vm) { + call_method(vm, 78); + } + private static void c79(VM vm) { + call_method(vm, 79); + } + private static void c80(VM vm) { + call_method(vm, 80); + } + private static void c81(VM vm) { + call_method(vm, 81); + } + private static void c82(VM vm) { + call_method(vm, 82); + } + private static void c83(VM vm) { + call_method(vm, 83); + } + private static void c84(VM vm) { + call_method(vm, 84); + } + private static void c85(VM vm) { + call_method(vm, 85); + } + private static void c86(VM vm) { + call_method(vm, 86); + } + private static void c87(VM vm) { + call_method(vm, 87); + } + private static void c88(VM vm) { + call_method(vm, 88); + } + private static void c89(VM vm) { + call_method(vm, 89); + } + private static void c90(VM vm) { + call_method(vm, 90); + } + private static void c91(VM vm) { + call_method(vm, 91); + } + private static void c92(VM vm) { + call_method(vm, 92); + } + private static void c93(VM vm) { + call_method(vm, 93); + } + private static void c94(VM vm) { + call_method(vm, 94); + } + private static void c95(VM vm) { + call_method(vm, 95); + } + private static void c96(VM vm) { + call_method(vm, 96); + } + private static void c97(VM vm) { + call_method(vm, 97); + } + private static void c98(VM vm) { + call_method(vm, 98); + } + private static void c99(VM vm) { + call_method(vm, 99); + } + + // --- Constructor -------------------------------------------- + + public Tramp() { + + allocators = new Array(); + allocators.set_size(100); + allocators.data[0] = i0; + allocators.data[1] = i1; + allocators.data[2] = i2; + allocators.data[3] = i3; + allocators.data[4] = i4; + allocators.data[5] = i5; + allocators.data[6] = i6; + allocators.data[7] = i7; + allocators.data[8] = i8; + allocators.data[9] = i9; + allocators.data[10] = i10; + allocators.data[11] = i11; + allocators.data[12] = i12; + allocators.data[13] = i13; + allocators.data[14] = i14; + allocators.data[15] = i15; + allocators.data[16] = i16; + allocators.data[17] = i17; + allocators.data[18] = i18; + allocators.data[19] = i19; + allocators.data[20] = i20; + allocators.data[21] = i21; + allocators.data[22] = i22; + allocators.data[23] = i23; + allocators.data[24] = i24; + allocators.data[25] = i25; + allocators.data[26] = i26; + allocators.data[27] = i27; + allocators.data[28] = i28; + allocators.data[29] = i29; + allocators.data[30] = i30; + allocators.data[31] = i31; + allocators.data[32] = i32; + allocators.data[33] = i33; + allocators.data[34] = i34; + allocators.data[35] = i35; + allocators.data[36] = i36; + allocators.data[37] = i37; + allocators.data[38] = i38; + allocators.data[39] = i39; + allocators.data[40] = i40; + allocators.data[41] = i41; + allocators.data[42] = i42; + allocators.data[43] = i43; + allocators.data[44] = i44; + allocators.data[45] = i45; + allocators.data[46] = i46; + allocators.data[47] = i47; + allocators.data[48] = i48; + allocators.data[49] = i49; + allocators.data[50] = i50; + allocators.data[51] = i51; + allocators.data[52] = i52; + allocators.data[53] = i53; + allocators.data[54] = i54; + allocators.data[55] = i55; + allocators.data[56] = i56; + allocators.data[57] = i57; + allocators.data[58] = i58; + allocators.data[59] = i59; + allocators.data[60] = i60; + allocators.data[61] = i61; + allocators.data[62] = i62; + allocators.data[63] = i63; + allocators.data[64] = i64; + allocators.data[65] = i65; + allocators.data[66] = i66; + allocators.data[67] = i67; + allocators.data[68] = i68; + allocators.data[69] = i69; + allocators.data[70] = i70; + allocators.data[71] = i71; + allocators.data[72] = i72; + allocators.data[73] = i73; + allocators.data[74] = i74; + allocators.data[75] = i75; + allocators.data[76] = i76; + allocators.data[77] = i77; + allocators.data[78] = i78; + allocators.data[79] = i79; + allocators.data[80] = i80; + allocators.data[81] = i81; + allocators.data[82] = i82; + allocators.data[83] = i83; + allocators.data[84] = i84; + allocators.data[85] = i85; + allocators.data[86] = i86; + allocators.data[87] = i87; + allocators.data[88] = i88; + allocators.data[89] = i89; + allocators.data[90] = i90; + allocators.data[91] = i91; + allocators.data[92] = i92; + allocators.data[93] = i93; + allocators.data[94] = i94; + allocators.data[95] = i95; + allocators.data[96] = i96; + allocators.data[97] = i97; + allocators.data[98] = i98; + allocators.data[99] = i99; + + methodThunks = new Array(); + methodThunks.set_size(100); + methodThunks.data[0] = c0; + methodThunks.data[1] = c1; + methodThunks.data[2] = c2; + methodThunks.data[3] = c3; + methodThunks.data[4] = c4; + methodThunks.data[5] = c5; + methodThunks.data[6] = c6; + methodThunks.data[7] = c7; + methodThunks.data[8] = c8; + methodThunks.data[9] = c9; + methodThunks.data[10] = c10; + methodThunks.data[11] = c11; + methodThunks.data[12] = c12; + methodThunks.data[13] = c13; + methodThunks.data[14] = c14; + methodThunks.data[15] = c15; + methodThunks.data[16] = c16; + methodThunks.data[17] = c17; + methodThunks.data[18] = c18; + methodThunks.data[19] = c19; + methodThunks.data[20] = c20; + methodThunks.data[21] = c21; + methodThunks.data[22] = c22; + methodThunks.data[23] = c23; + methodThunks.data[24] = c24; + methodThunks.data[25] = c25; + methodThunks.data[26] = c26; + methodThunks.data[27] = c27; + methodThunks.data[28] = c28; + methodThunks.data[29] = c29; + methodThunks.data[30] = c30; + methodThunks.data[31] = c31; + methodThunks.data[32] = c32; + methodThunks.data[33] = c33; + methodThunks.data[34] = c34; + methodThunks.data[35] = c35; + methodThunks.data[36] = c36; + methodThunks.data[37] = c37; + methodThunks.data[38] = c38; + methodThunks.data[39] = c39; + methodThunks.data[40] = c40; + methodThunks.data[41] = c41; + methodThunks.data[42] = c42; + methodThunks.data[43] = c43; + methodThunks.data[44] = c44; + methodThunks.data[45] = c45; + methodThunks.data[46] = c46; + methodThunks.data[47] = c47; + methodThunks.data[48] = c48; + methodThunks.data[49] = c49; + methodThunks.data[50] = c50; + methodThunks.data[51] = c51; + methodThunks.data[52] = c52; + methodThunks.data[53] = c53; + methodThunks.data[54] = c54; + methodThunks.data[55] = c55; + methodThunks.data[56] = c56; + methodThunks.data[57] = c57; + methodThunks.data[58] = c58; + methodThunks.data[59] = c59; + methodThunks.data[60] = c60; + methodThunks.data[61] = c61; + methodThunks.data[62] = c62; + methodThunks.data[63] = c63; + methodThunks.data[64] = c64; + methodThunks.data[65] = c65; + methodThunks.data[66] = c66; + methodThunks.data[67] = c67; + methodThunks.data[68] = c68; + methodThunks.data[69] = c69; + methodThunks.data[70] = c70; + methodThunks.data[71] = c71; + methodThunks.data[72] = c72; + methodThunks.data[73] = c73; + methodThunks.data[74] = c74; + methodThunks.data[75] = c75; + methodThunks.data[76] = c76; + methodThunks.data[77] = c77; + methodThunks.data[78] = c78; + methodThunks.data[79] = c79; + methodThunks.data[80] = c80; + methodThunks.data[81] = c81; + methodThunks.data[82] = c82; + methodThunks.data[83] = c83; + methodThunks.data[84] = c84; + methodThunks.data[85] = c85; + methodThunks.data[86] = c86; + methodThunks.data[87] = c87; + methodThunks.data[88] = c88; + methodThunks.data[89] = c89; + methodThunks.data[90] = c90; + methodThunks.data[91] = c91; + methodThunks.data[92] = c92; + methodThunks.data[93] = c93; + methodThunks.data[94] = c94; + methodThunks.data[95] = c95; + methodThunks.data[96] = c96; + methodThunks.data[97] = c97; + methodThunks.data[98] = c98; + methodThunks.data[99] = c99; + } // ctor + } +} diff --git a/src/vm.vala.in b/src/vm.vala.in index 2733774..f4c3921 100644 --- a/src/vm.vala.in +++ b/src/vm.vala.in @@ -40,6 +40,65 @@ namespace Wren { } } + /** + * Print a message to stdout. + * + * This is the default writeFn for a {@link Wren.VMV}. + */ + public void defaultWriteFn(VM vm, string text) + { + print("%s", text); + } + + /** + * Report an error to stderr. + * + * This is the default errorFn for a {@link Wren.VMV}. + * Modified from wren-pkg/wren/example/embedding/main.c. + */ + public void defaultErrorFn(VM vm, Wren.ErrorType errorType, + string? module, int line, string msg) + { + switch (errorType) + { + case COMPILE: + printerr("[%s line %d] [Error] %s\n", module, line, msg); + break; + case STACK_TRACE: + printerr("[%s line %d] in %s\n", module, line, msg); + break; + case RUNTIME: + printerr("[Runtime Error] %s\n", msg); + break; + } + } + + /** + * Interface exposing functions to Wren + * + * This is used by {@link Wren.Tramp} to get the available methods. + */ + public interface HasMethods : GLib.Object + { + /** + * Implement a Wren method. + * + * @param self The object instance (from Wren's foreign data) + * @param vm The Wren VM + */ + [CCode(has_target = false)] + public delegate void MethodImpl(GLib.Object self, VMV vm); + + /** + * Fill a hash table with a map from {@link Wren.Tramp.hash_key} to + * {@link Wren.HasMethods.MethodImpl}. + * + * Must be callable multiple times, returning the same value every time. + * @param methods An empty hash table to be filled + */ + public abstract void get_methods(ref HashTable methods); + } // interface HasMethods + /** * Virtual machine, with HandleV (VMV = "VM - Vala") * @@ -57,59 +116,71 @@ namespace Wren { [CCode(cheader_filename="libwren-vala-@APIVER@.h,@WREN_HEADER@")] public class VMV : Object { - // TODO? provide VMV wrappers for the writeFn and errorFn? + // --- Instance data ------------------------------------------ - /** Print a message to stdout */ - private static void writeFn(VM vm, string text) - { - print("%s", text); - } + /** The wrapped Wren VM */ + private VM vm = null; /** - * Report an error to stderr. + * Trampolines * - * Modified from wren-pkg/wren/example/embedding/main.c. + * Has to be public, but if you use it, your bindings will break! */ - private static void errorFn(VM vm, Wren.ErrorType errorType, - string? module, int line, string msg) - { - switch (errorType) - { - case COMPILE: - printerr("[%s line %d] [Error] %s\n", module, line, msg); - break; - case STACK_TRACE: - printerr("[%s line %d] in %s\n", module, line, msg); - break; - case RUNTIME: - printerr("[Runtime Error] %s\n", msg); - break; - } + [CCode(notify = false)] + public Tramp tramp_ { get; private set; default = new Tramp(); } + + // --- VM functions ------------------------------------------- + + /** Bind a foreign class */ + private static ForeignClassMethods bindForeignClass(VM vm, string module, string className) + { + var self = vm.GetUserData() as VMV; + debug("bindForeignClass %s in %p\n", + Tramp.hash_key(module, className), self); + return self.tramp_.get_functions(module, className); } - /** The wrapped Wren VM */ - private VM vm = null; + /** Bind a foreign method */ + private static ForeignMethodFn bindForeignMethod(VM vm, string module, + string className, bool isStatic, string signature) + { + var self = vm.GetUserData() as VMV; + debug("bindForeignMethod %s in %p\n", + Tramp.hash_key(module, className, isStatic, signature), self); + return self.tramp_.get_method(module, className, isStatic, signature); + } + + // --- Constructors ------------------------------------------- /** Create a VM with a particular configuration */ public VMV.with_configuration(Configuration? config = null) { + Wren.static_init(); vm = new VM(config); + vm.SetUserData(this); } + // TODO support gobject-style construction? /** * Create a VM with a default configuration. * * The default is the Wren default configuration, plus a writeFn and - * an errorFn modified from wren-pkg/wren/example/embedding/main.c. + * an errorFn modified from wren-pkg/wren/example/embedding/main.c, + * and binding functions that support expose_class(). */ public VMV() { var conf = Wren.Configuration.default (); - conf.writeFn = writeFn; - conf.errorFn = errorFn; + conf.writeFn = defaultWriteFn; + conf.errorFn = defaultErrorFn; + conf.bindForeignClassFn = bindForeignClass; + conf.bindForeignMethodFn = bindForeignMethod; this.with_configuration(conf); } + // --- Low-level interface ------------------------------------ + // This section exposes the Wren.VM methods. + public void collect_garbage() { vm.CollectGarbage(); } @@ -160,7 +231,7 @@ namespace Wren { vm.EnsureSlots(num_slots); } - public Type get_slot_type(int slot) + public Wren.Type get_slot_type(int slot) { return vm.GetSlotType(slot); } @@ -295,14 +366,67 @@ namespace Wren { vm.AbortFiber(slot); } - public void *get_user_data() + // Userdata: private because VMV takes this for its own use. +#if 0 + private void *get_user_data() { return vm.GetUserData(); } - public void set_user_data(void *user_data) + private void set_user_data(void *user_data) { vm.SetUserData(user_data); } +#endif + + // --- High-level interface ----------------------------------- + // This section binds classes as a whole between Vala and Wren + +#if 0 + /** + * Get a slot as a GValue + */ + public Value get_slot(int slot) + { + // TODO + return Value(GLib.Type.INVALID); + } +#endif + + /** + * Expose class T to Wren, as part of module `mod` + * + * @param mod The module to add the class to (default "main"). + */ + public InterpretResult expose_class(string mod = "main") + { + StringBuilder wren = new StringBuilder(); // source to be interpreted + var type = typeof(T); + assert(!type.is_abstract()); // TODO more sophisticated error handling + assert(type.is_instantiatable()); + + // ObjectClass ocl = (ObjectClass)type.class_ref(); + + wren.append_printf("foreign class %s {\n", type.name()); + + // Properties + // foreach (ParamSpec spec in ocl.list_properties()) { + // TODO print ("%s\n", spec.get_name ()); + // } + + wren.append("construct new() {}\n"); + + // Functions + Tramp.foreach_method_of_type(type, (k,v) => { + wren.append(Tramp.foreign_decl_for_key(k) + "\n"); + }); + + wren.append("}\n"); + debug("Wren source: >>%s<<", wren.str); + + tramp_.add_type(mod, type.name(), type); + + return interpret(mod, wren.str); + } } // class VMV } // namespace Wren diff --git a/src/wrennull.c.in b/src/wrennull.c.in new file mode 100644 index 0000000..a1284ba --- /dev/null +++ b/src/wrennull.c.in @@ -0,0 +1,99 @@ +// @configure_input@ + +// wrennull.c: Wren's null type, as a GType +// +// By Christopher White +// Copyright (c) 2021 Christopher White. All Rights Reserved. +// SPDX-License-Identifier: MIT + +#include +#include +#include "libwren-vala-@APIVER@.h" +#include "@WREN_HEADER@" + +G_BEGIN_DECLS + +// Stub functions for GValue holding a WrenNull + +static void value_nop(GValue *v) +{ +} +static void value_nop2(const GValue *s, GValue *d) +{ +} +static gchar *value_collect_nop(GValue *value, guint n_collect_values, + GTypeCValue *collect_values, + guint collect_flags) +{ + return (gchar *)NULL; +} +static gchar *value_collect_nop_const(const GValue *value, guint n_collect_values, + GTypeCValue *collect_values, + guint collect_flags) +{ + return (gchar *)NULL; +} + +/** + * wrennull_get_type_once: + * + * Register a fundamental type representing Wren's null value. + * Thanks to glib's _g_value_types_init() and to Vala's get_type_once output + * for explanation of what to do in this function. + * + * A GValue of this type has no data; the GType itself represents the Wren null. + * + * Return: The GType + */ +static GType +wrennull_get_type_once(void) +{ + static const GTypeValueTable g_define_type_value_table = { + value_nop, // init + value_nop, // free + value_nop2, // copy + NULL, // peek_pointer + "i", // collect format + value_collect_nop, + "p", // lcopy format + value_collect_nop_const, + }; + + static const GTypeInfo g_define_type_info = { + 0, + (GBaseInitFunc)NULL, + (GBaseFinalizeFunc)NULL, + (GClassInitFunc)NULL, + (GClassFinalizeFunc)NULL, + NULL, + 0, + 0, + (GInstanceInitFunc)NULL, + &g_define_type_value_table + }; + + static const GTypeFundamentalInfo g_define_type_fundamental_info = { + G_TYPE_FLAG_DERIVABLE + }; + + GType wrennull_type_id; + wrennull_type_id = g_type_register_fundamental( + g_type_fundamental_next(), "wren-null", &g_define_type_info, + &g_define_type_fundamental_info, 0); + + return wrennull_type_id; +} + +GType +wrenget_null_type() +{ + static volatile gsize wrennull_type_id__volatile = 0; + if (g_once_init_enter(&wrennull_type_id__volatile)) { + GType wrennull_type_id; + wrennull_type_id = wrennull_get_type_once(); + g_once_init_leave(&wrennull_type_id__volatile, wrennull_type_id); + } + return wrennull_type_id__volatile; +} + +G_END_DECLS diff --git a/t/100-vmv-t.vala b/t/100-vmv-t.vala index 058d843..9f108ff 100644 --- a/t/100-vmv-t.vala +++ b/t/100-vmv-t.vala @@ -56,24 +56,11 @@ void test_misc() // for coverage assert_true(vm.has_variable("main", "answer")); } -void test_userdata() -{ - var vm = new Wren.VMV(); - assert_nonnull(vm); - - string test = "Yowza"; - vm.set_user_data((void *)test); - - string got = (string)vm.get_user_data(); - assert_cmpstr(got, EQ, test); -} - public static int main(string[] args) { Test.init(ref args); Test.set_nonfatal_assertions(); Test.add_func("/100-vmv/instantiate", test_instantiate); Test.add_func("/100-vmv/misc", test_misc); - Test.add_func("/100-vmv/userdata", test_userdata); return Test.run(); } diff --git a/t/105-tramp-t.vala b/t/105-tramp-t.vala new file mode 100644 index 0000000..d2515e2 --- /dev/null +++ b/t/105-tramp-t.vala @@ -0,0 +1,32 @@ +// t/105-tramp-t.vala + +using Wren; // required to pull in the headers on valac 0.50.1.79-3f2a6 + +void test_hash_key() +{ + assert_cmpstr(Tramp.hash_key("m","c"), EQ, "m!c"); + assert_cmpstr(Tramp.hash_key("m","c",false,"sig()"), EQ, "m!c.sig()"); + assert_cmpstr(Tramp.hash_key("m","c",true,"sig()"), EQ, "m!c.$sig()"); +} + +void test_foreign_decl_for_key() +{ + assert_cmpstr(Tramp.foreign_decl_for_key("m!c"), EQ, "INVALID_FORMAT!"); + assert_cmpstr(Tramp.foreign_decl_for_key("m!c.$sig()"), EQ, "foreign static sig()"); + assert_cmpstr(Tramp.foreign_decl_for_key("m!c.sig()"), EQ, "foreign sig()"); + + assert_cmpstr(Tramp.foreign_decl_for_key("m!c.$sig(_)"), EQ, "foreign static sig(p0)"); + assert_cmpstr(Tramp.foreign_decl_for_key("m!c.sig(_)"), EQ, "foreign sig(p0)"); + + assert_cmpstr(Tramp.foreign_decl_for_key("m!c.$sig(_,_)"), EQ, "foreign static sig(p0,p1)"); + assert_cmpstr(Tramp.foreign_decl_for_key("m!c.sig(_,_)"), EQ, "foreign sig(p0,p1)"); +} + +public static int main(string[] args) +{ + Test.init(ref args); + Test.set_nonfatal_assertions(); + Test.add_func("/105-tramp/hash_key", test_hash_key); + Test.add_func("/105-tramp/foreign_decl_for_key", test_foreign_decl_for_key); + return Test.run(); +} diff --git a/t/150-marshal-t.vala b/t/150-marshal-t.vala new file mode 100644 index 0000000..60fd051 --- /dev/null +++ b/t/150-marshal-t.vala @@ -0,0 +1,60 @@ +// t/150-marshal-t.vala + +using Wren; // required to pull in the headers on valac 0.50.1.79-3f2a6 + +private Wren.VM g_vm; + +// Test reading Wren data into a GValue +void test_from_wren() +{ + Value v; + g_vm.EnsureSlots(1); + + g_vm.SetSlotBool(0, true); + v = Marshal.to_value_raw(g_vm, 0); + assert_true(v.type() == GLib.Type.BOOLEAN); + assert_true(v.get_boolean()); + + g_vm.SetSlotDouble(0, 42); + v = Marshal.to_value_raw(g_vm, 0); + assert_true(v.type() == GLib.Type.DOUBLE); + assert_cmpfloat(v.get_double(), EQ, 42); + + // TODO FOREIGN, LIST, MAP + + g_vm.SetSlotNull(0); + v = Marshal.to_value_raw(g_vm, 0); + assert_true(v.type() == Wren.get_null_type()); + + g_vm.SetSlotString(0, "hello"); + v = Marshal.to_value_raw(g_vm, 0); + assert_true(v.type() == GLib.Type.STRING); + assert_true(v.get_string() == "hello"); + + // TODO UNKNOWN + +} // test_from_wren + +public static int main(string[] args) +{ + Wren.static_init(); + Test.init(ref args); + Test.set_nonfatal_assertions(); + Test.add_func("/150-marshal/from-wren", test_from_wren); + + // Make the VM + var conf = Wren.Configuration(); + Wren.InitConfiguration(ref conf); + conf.writeFn = Wren.defaultWriteFn; + conf.errorFn = Wren.defaultErrorFn; + + g_vm = new Wren.VM(conf); + assert_nonnull(g_vm); + if(g_vm == null) { + print("# Could not create VM\n"); + return 1; + } + + // Run the tests + return Test.run(); +} diff --git a/t/200-expose-class-t.vala b/t/200-expose-class-t.vala new file mode 100644 index 0000000..41b126e --- /dev/null +++ b/t/200-expose-class-t.vala @@ -0,0 +1,175 @@ +// t/200-expose-class-t.vala + +using Wren; // required to pull in the headers on valac 0.50.1.79-3f2a6 + + +class Sample : Object, Wren.HasMethods { + + // --- How we observe Wren effects ----- + + public static bool wasInitialized = false; + public static string gMsg; + public static unowned Object gObj; + + // --- Properties ---------------------- + + public int prop { get; set; default = 42; } + + // --- Ctor/dtor ----------------------- + construct { + wasInitialized = true; + debug("Initialized!"); + } + + ~Sample() { + debug("Dtor"); + } + + // --- Methods ------------------------- + + /** Wren-visible function of no args */ + void sayHello() + { + gMsg = "Hello, world!"; + gObj = this; + } + + /** Wren invoker for sayHello() */ + static void wren_sayHello(GLib.Object self, VMV vm) + { + ((!)(self as Sample)).sayHello(); + } + + /** Wren-visible function of one arg */ + void stashMessage(string msg) + { + gMsg = msg; + } + + /** Wren invoker for sayHello() */ + static void wren_stashMessage(GLib.Object self, VMV vm) + { + ((!)(self as Sample)).stashMessage(vm.get_slot_string(1)); + } + + /** Wren-visible function of two args */ + void join(string part2, string part1) + { + gMsg = part1 + part2; // opposite order of the args on purpose + } + + /** Wren invoker for join() */ + static void wren_join(GLib.Object self, VMV vm) + { + ((!)(self as Sample)).join(vm.get_slot_string(1), vm.get_slot_string(2)); + } + + /** Wren-visible STATIC function of two args */ + static void sjoin(string part2, string part1) + { + gMsg = "static" + part1 + part2; // opposite order of the args on purpose + } + + /** Wren invoker for join() */ + static void wren_sjoin(GLib.Object self, VMV vm) + { + sjoin(vm.get_slot_string(1), vm.get_slot_string(2)); + } + + /** Tell Wren about our methods */ + public void get_methods(ref HashTable methods) + { + methods["main!Sample.hello()"] = wren_sayHello; + methods["main!Sample.stashMessage(_)"] = wren_stashMessage; + methods["main!Sample.join(_,_)"] = wren_join; + methods["main!Sample.$sjoin(_,_)"] = wren_sjoin; + } +} + +void test_instantiate() +{ + var vm = new Wren.VMV(); + + var ok = vm.expose_class("main"); + assert_true(ok == SUCCESS); + + Sample.wasInitialized = false; + ok = vm.interpret("main", """ + var Obj = Sample.new() + //var Prop = Obj.prop + """); + assert_true(ok == SUCCESS); + assert_true(Sample.wasInitialized); +} + +void test_methods() +{ + var vm = new Wren.VMV(); + + Sample.gMsg = null; + Sample.gObj = null; + + var ok = vm.expose_class("main"); + assert_true(ok == SUCCESS); + + // No args + ok = vm.interpret("main", """ + var Obj = Sample.new() + Obj.hello() + """); + assert_true(ok == SUCCESS); + assert_cmpstr(Sample.gMsg, EQ, "Hello, world!"); + assert_true(Sample.gObj != null); + + // One arg + Sample.gMsg = null; + ok = vm.interpret("main", """ + Obj.stashMessage("Yep!") + """); + assert_true(ok == SUCCESS); + assert_cmpstr(Sample.gMsg, EQ, "Yep!"); + + // Two args + Sample.gMsg = null; + ok = vm.interpret("main", """ + Obj.join("A","B") + """); + assert_true(ok == SUCCESS); + assert_cmpstr(Sample.gMsg, EQ, "BA"); +} + +void test_static_methods() +{ + var vm = new Wren.VMV(); + + Sample.gMsg = null; + Sample.gObj = null; + + var ok = vm.expose_class("main"); + assert_true(ok == SUCCESS); + + // Instance function + ok = vm.interpret("main", """ + var Obj = Sample.new() + Obj.join("A","B") + """); + assert_true(ok == SUCCESS); + assert_cmpstr(Sample.gMsg, EQ, "BA"); + + // Static function + ok = vm.interpret("main", """ + Sample.sjoin("C","D") + """); + assert_true(ok == SUCCESS); + assert_cmpstr(Sample.gMsg, EQ, "staticDC"); +} + +public static int main(string[] args) +{ + Test.init(ref args); + Test.set_nonfatal_assertions(); + Test.add_func("/200-expose-class/instantiate", test_instantiate); + Test.add_func("/200-expose-class/methods", test_methods); + Test.add_func("/200-expose-class/static_methods", test_static_methods); + return Test.run(); +} diff --git a/t/Makefile.am b/t/Makefile.am index 8147529..9541633 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -47,9 +47,12 @@ if !WREN_INSTALL test_programs += \ 000-sanity-t \ 100-vmv-t \ + 105-tramp-t \ 120-read-var-from-wren-t \ 130-call-wren-t \ 140-roundtrip-data-t \ + 150-marshal-t \ + 200-expose-class-t \ $(EOL) dist_test_scripts += \