From e655dc21086e1d0cd00b7bf391ab02f61e01e603 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 22 May 2015 16:30:57 -0700 Subject: [PATCH 1/7] Break out Go return statement code gen. --- compiler/src/CFCGoFunc.c | 46 ++++++++++++++++++++++++++++++++++++++ compiler/src/CFCGoFunc.h | 13 +++++++++++ compiler/src/CFCGoMethod.c | 38 ++----------------------------- 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/compiler/src/CFCGoFunc.c b/compiler/src/CFCGoFunc.c index 59ac1ed3..79c129a1 100644 --- a/compiler/src/CFCGoFunc.c +++ b/compiler/src/CFCGoFunc.c @@ -101,3 +101,49 @@ CFCGoFunc_func_start(CFCParcel *parcel, const char *name, CFCClass *invoker, return content; } +char* +CFCGoFunc_return_statement(CFCParcel *parcel, CFCType *return_type, + const char *cf_retval) { + char *statement = NULL; + if (CFCType_is_void(return_type)) { + return CFCUtil_strdup(""); + } + else { + char *ret_type_str = CFCGoTypeMap_go_type_name(return_type, parcel); + if (ret_type_str == NULL) { + CFCUtil_die("Can't convert type to Go: %s", + CFCType_to_c(return_type)); + } + + if (CFCType_is_primitive(return_type)) { + statement = CFCUtil_sprintf("\treturn %s(retvalCF)\n", ret_type_str); + } + else if (CFCType_is_object(return_type)) { + char *go_type_name = CFCGoTypeMap_go_type_name(return_type, parcel); + char *struct_name = go_type_name; + char *go_package = CFCUtil_strdup(go_type_name); + for (int i = strlen(go_package) - 1; i >= 0; i--) { + if (go_package[i] == '.') { + struct_name += i + 1; + break; + } + go_package[i] = '\0'; + } + char *pattern; + if (CFCType_incremented(return_type)) { + pattern = "\treturn %sWRAP%s(unsafe.Pointer(retvalCF))\n"; + } + else { + pattern = "\treturn %sWRAP%s(unsafe.Pointer(C.cfish_inc_refcount(unsafe.Pointer(retvalCF))))\n"; + } + statement = CFCUtil_sprintf(pattern, go_package, struct_name); + FREEMEM(go_type_name); + FREEMEM(go_package); + } + else { + CFCUtil_die("Unexpected type: %s", CFCType_to_c(return_type)); + } + } + + return statement; +} diff --git a/compiler/src/CFCGoFunc.h b/compiler/src/CFCGoFunc.h index a8a47c38..213b5500 100644 --- a/compiler/src/CFCGoFunc.h +++ b/compiler/src/CFCGoFunc.h @@ -37,6 +37,19 @@ CFCGoFunc_func_start(struct CFCParcel *parcel, const char *name, struct CFCParamList *param_list, struct CFCType *return_type, int is_method); +/** Generate a Go return statement which maps from a CGO Clownfish type to a + * Go type. + * + * @param parcel The parcel in which the code is being generated. + * @param type The type of return value, which must be convertible. + * @param cf_retval A Go expression representing the return value of a + * Clownfish subroutine. + */ +char* +CFCGoFunc_return_statement(struct CFCParcel *parcel, + struct CFCType *return_type, + const char *cf_retval); + #ifdef __cplusplus } #endif diff --git a/compiler/src/CFCGoMethod.c b/compiler/src/CFCGoMethod.c index a6d04963..9626e590 100644 --- a/compiler/src/CFCGoMethod.c +++ b/compiler/src/CFCGoMethod.c @@ -199,49 +199,16 @@ CFCGoMethod_func_def(CFCGoMethod *self, CFCClass *invoker) { char *cfargs = S_prep_cfargs(invoker, param_list); - char *ret_type_str; char *maybe_retval; char *maybe_return; if (CFCType_is_void(ret_type)) { - ret_type_str = CFCUtil_strdup(""); maybe_retval = CFCUtil_strdup(""); maybe_return = CFCUtil_strdup(""); } else { - ret_type_str = CFCGoTypeMap_go_type_name(ret_type, parcel); - if (ret_type_str == NULL) { - CFCUtil_die("Can't convert invalid type in method %s", name); - } maybe_retval = CFCUtil_strdup("retvalCF := "); - - if (CFCType_is_primitive(ret_type)) { - maybe_return = CFCUtil_sprintf("\treturn %s(retvalCF)\n", ret_type_str); - } - else if (CFCType_is_object(ret_type)) { - char *go_type_name = CFCGoTypeMap_go_type_name(ret_type, parcel); - char *struct_name = go_type_name; - char *go_package = CFCUtil_strdup(go_type_name); - for (int i = strlen(go_package) - 1; i >= 0; i--) { - if (go_package[i] == '.') { - struct_name += i + 1; - break; - } - go_package[i] = '\0'; - } - char *pattern; - if (CFCType_incremented(ret_type)) { - pattern = "\treturn %sWRAP%s(unsafe.Pointer(retvalCF))\n"; - } - else { - pattern = "\treturn %sWRAP%s(unsafe.Pointer(C.cfish_inc_refcount(unsafe.Pointer(retvalCF))))\n"; - } - maybe_return = CFCUtil_sprintf(pattern, go_package, struct_name); - FREEMEM(go_type_name); - FREEMEM(go_package); - } - else { - CFCUtil_die("Unexpected type: %s", CFCType_to_c(ret_type)); - } + maybe_return = CFCGoFunc_return_statement(parcel, ret_type, + "retvalCF"); } char pattern[] = @@ -255,7 +222,6 @@ CFCGoMethod_func_def(CFCGoMethod *self, CFCClass *invoker) { FREEMEM(maybe_retval); FREEMEM(maybe_return); - FREEMEM(ret_type_str); FREEMEM(cfunc); FREEMEM(cfargs); FREEMEM(first_line); From 83881f0f1abd357afeee364ee2042218a607e894 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 22 May 2015 18:44:45 -0700 Subject: [PATCH 2/7] Add Go built-in types to list of reserved words. --- compiler/src/CFCGoTypeMap.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCGoTypeMap.c b/compiler/src/CFCGoTypeMap.c index 9872d101..6dd77002 100644 --- a/compiler/src/CFCGoTypeMap.c +++ b/compiler/src/CFCGoTypeMap.c @@ -90,7 +90,32 @@ static const char* go_keywords[] = { "struct", "switch", "type", - "var" + "var", + "true", + "false", + "bool", + "int", + "uint", + "uintptr", + "int8", + "int16", + "int32", + "int32", + "int8", + "int16", + "int32", + "int64", + "uint8", + "uint16", + "uint32", + "uint64", + "float32", + "float64", + "complex64", + "complex128", + "byte", + "rune", + "string" }; static int num_go_keywords = sizeof(go_keywords) / sizeof(go_keywords[0]); From 69b35c3e7ba1c3b93e47c62ac7a0d51656086b5c Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Thu, 28 May 2015 10:55:12 -0700 Subject: [PATCH 3/7] Add helper identifying Clownfish runtime parcel. --- compiler/src/CFCBindClass.c | 8 ++++---- compiler/src/CFCBindCore.c | 2 +- compiler/src/CFCParcel.c | 5 +++++ compiler/src/CFCParcel.h | 5 +++++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/compiler/src/CFCBindClass.c b/compiler/src/CFCBindClass.c index a30ea18e..527538b8 100644 --- a/compiler/src/CFCBindClass.c +++ b/compiler/src/CFCBindClass.c @@ -436,8 +436,8 @@ S_struct_definition(CFCBindClass *self) { const char *struct_sym; char *member_decs = CFCUtil_strdup(""); - const char *prefix = CFCClass_get_prefix(client); - if (strcmp(prefix, "cfish_") == 0) { + CFCParcel *parcel = CFCClass_get_parcel(client); + if (CFCParcel_is_cfish(parcel)) { struct_sym = CFCClass_full_struct_sym(client); member_decs = CFCUtil_cat(member_decs, "\n CFISH_OBJ_HEAD", NULL); } @@ -473,12 +473,12 @@ char* CFCBindClass_spec_def(CFCBindClass *self) { CFCClass *client = self->client; + CFCParcel *parcel = CFCClass_get_parcel(client); CFCClass *parent = CFCClass_get_parent(client); const char *class_name = CFCClass_get_name(client); const char *class_var = CFCClass_full_class_var(client); const char *struct_sym = CFCClass_full_struct_sym(client); const char *ivars_struct = CFCClass_full_ivars_struct(client); - const char *prefix = CFCClass_get_prefix(client); // Create a pointer to the parent Class object. char *parent_ref; @@ -525,7 +525,7 @@ CFCBindClass_spec_def(CFCBindClass *self) { char *ivars_size = NULL; - if (strcmp(prefix, "cfish_") == 0) { + if (CFCParcel_is_cfish(parcel)) { ivars_size = CFCUtil_sprintf("sizeof(%s)", struct_sym); } else { diff --git a/compiler/src/CFCBindCore.c b/compiler/src/CFCBindCore.c index 43e25f07..985f00cb 100644 --- a/compiler/src/CFCBindCore.c +++ b/compiler/src/CFCBindCore.c @@ -351,7 +351,7 @@ S_write_parcel_h(CFCBindCore *self, CFCParcel *parcel) { char *extra_defs; char *extra_includes; - if (strcmp(prefix, "cfish_") == 0) { + if (CFCParcel_is_cfish(parcel)) { extra_defs = CFCUtil_sprintf("%s%s", cfish_defs_1, cfish_defs_2); extra_includes = CFCUtil_strdup(cfish_includes); } diff --git a/compiler/src/CFCParcel.c b/compiler/src/CFCParcel.c index f54c81c8..15e10da6 100644 --- a/compiler/src/CFCParcel.c +++ b/compiler/src/CFCParcel.c @@ -601,6 +601,11 @@ CFCParcel_lookup_struct_sym(CFCParcel *self, const char *struct_sym) { return parcel; } +int +CFCParcel_is_cfish(CFCParcel *self) { + return !strcmp(self->prefix, "cfish_"); +} + /**************************************************************************/ struct CFCPrereq { diff --git a/compiler/src/CFCParcel.h b/compiler/src/CFCParcel.h index dff7deb7..9ecec9b8 100644 --- a/compiler/src/CFCParcel.h +++ b/compiler/src/CFCParcel.h @@ -177,6 +177,11 @@ CFCParcel_add_struct_sym(CFCParcel *self, const char *struct_sym); CFCParcel* CFCParcel_lookup_struct_sym(CFCParcel *self, const char *struct_sym); +/** Indicate whether the parcel is "clownfish", the main Clownfish runtime. + */ +int +CFCParcel_is_cfish(CFCParcel *self); + /**************************************************************************/ CFCPrereq* From ba550db5546412b391ea4fe413cac355e5627a1b Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Thu, 28 May 2015 10:59:25 -0700 Subject: [PATCH 4/7] Be more strict about what is a "string type". --- compiler/src/CFCType.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCType.c b/compiler/src/CFCType.c index 5684f509..ee04abf7 100644 --- a/compiler/src/CFCType.c +++ b/compiler/src/CFCType.c @@ -182,7 +182,9 @@ CFCType_new_object(int flags, CFCParcel *parcel, const char *specifier, // Add flags. flags |= CFCTYPE_OBJECT; - if (strstr(specifier, "String")) { + if (strcmp(specifier, "String") == 0 + || strcmp(specifier, "cfish_String") == 0 + ) { // Determine whether this type is a string type. flags |= CFCTYPE_STRING_TYPE; } From 9da720363a68aad40b62bafcd4e8b843c3384a21 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 22 May 2015 18:47:02 -0700 Subject: [PATCH 5/7] Convert Clownfish/Go string types in glue. Convert Clownfish Strings to Go strings and vice versa when crossing the Go/C border. --- compiler/src/CFCGoFunc.c | 16 ++++++++++++++++ compiler/src/CFCGoMethod.c | 18 ++++++++++++++++-- compiler/src/CFCGoTypeMap.c | 5 ++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/compiler/src/CFCGoFunc.c b/compiler/src/CFCGoFunc.c index 79c129a1..31386058 100644 --- a/compiler/src/CFCGoFunc.c +++ b/compiler/src/CFCGoFunc.c @@ -118,6 +118,22 @@ CFCGoFunc_return_statement(CFCParcel *parcel, CFCType *return_type, if (CFCType_is_primitive(return_type)) { statement = CFCUtil_sprintf("\treturn %s(retvalCF)\n", ret_type_str); } + else if (CFCType_is_string_type(return_type)) { + const char *clownfish_dot = CFCParcel_is_cfish(parcel) + ? "" : "clownfish."; + if (CFCType_incremented(return_type)) { + char pattern[] = + "\tdefer C.cfish_dec_refcount(unsafe.Pointer(retvalCF))\n" + "\treturn %sCFStringToGo(unsafe.Pointer(retvalCF))\n" + ; + statement = CFCUtil_sprintf(pattern, clownfish_dot); + } + else { + char pattern[] = + "\treturn %sCFStringToGo(unsafe.Pointer(retvalCF))\n"; + statement = CFCUtil_sprintf(pattern, clownfish_dot); + } + } else if (CFCType_is_object(return_type)) { char *go_type_name = CFCGoTypeMap_go_type_name(return_type, parcel); char *struct_name = go_type_name; diff --git a/compiler/src/CFCGoMethod.c b/compiler/src/CFCGoMethod.c index 9626e590..b095347f 100644 --- a/compiler/src/CFCGoMethod.c +++ b/compiler/src/CFCGoMethod.c @@ -136,7 +136,8 @@ CFCGoMethod_get_sig(CFCGoMethod *self, CFCClass *invoker) { #define GO_NAME_BUF_SIZE 128 static char* -S_prep_cfargs(CFCClass *invoker, CFCParamList *param_list) { +S_prep_cfargs(CFCParcel *parcel, CFCClass *invoker, + CFCParamList *param_list) { CFCVariable **vars = CFCParamList_get_variables(param_list); char go_name[GO_NAME_BUF_SIZE]; char *cfargs = CFCUtil_strdup(""); @@ -158,6 +159,19 @@ S_prep_cfargs(CFCClass *invoker, CFCParamList *param_list) { cfargs = CFCUtil_cat(cfargs, "C.", CFCType_get_specifier(type), "(", go_name, ")", NULL); } + else if (CFCType_is_string_type(type) + && i != 0) { // Don't convert a clownfish.String invocant. + const char *format; + if (CFCParcel_is_cfish(parcel)) { + format = "%s((*C.cfish_String)(unsafe.Pointer(NewString(%s).TOPTR())))"; + } + else { + format = "%s((*C.cfish_String)(unsafe.Pointer(clownfish.NewString(%s).TOPTR())))"; + } + char *temp = CFCUtil_sprintf(format, cfargs, go_name); + FREEMEM(cfargs); + cfargs = temp; + } else if (CFCType_is_object(type)) { char *obj_pattern; @@ -197,7 +211,7 @@ CFCGoMethod_func_def(CFCGoMethod *self, CFCClass *invoker) { cfunc = CFCMethod_full_method_sym(novel_method, invoker); } - char *cfargs = S_prep_cfargs(invoker, param_list); + char *cfargs = S_prep_cfargs(parcel, invoker, param_list); char *maybe_retval; char *maybe_return; diff --git a/compiler/src/CFCGoTypeMap.c b/compiler/src/CFCGoTypeMap.c index 6dd77002..d61ca47f 100644 --- a/compiler/src/CFCGoTypeMap.c +++ b/compiler/src/CFCGoTypeMap.c @@ -126,7 +126,10 @@ static int num_go_keywords = sizeof(go_keywords) / sizeof(go_keywords[0]); char* CFCGoTypeMap_go_type_name(CFCType *type, CFCParcel *current_parcel) { - if (CFCType_is_object(type)) { + if (CFCType_is_string_type(type)) { + return CFCUtil_strdup("string"); + } + else if (CFCType_is_object(type)) { // Divide the specifier into prefix and struct name. const char *specifier = CFCType_get_specifier(type); size_t prefix_len = 0; From 0fdbd3daef0e1d1534cab9487a31866341acd809 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 22 May 2015 19:17:50 -0700 Subject: [PATCH 6/7] Add tests for Go bindings of clownfish.String. --- runtime/go/clownfish/string_test.go | 209 ++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 runtime/go/clownfish/string_test.go diff --git a/runtime/go/clownfish/string_test.go b/runtime/go/clownfish/string_test.go new file mode 100644 index 00000000..22d0267f --- /dev/null +++ b/runtime/go/clownfish/string_test.go @@ -0,0 +1,209 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package clownfish + +import "testing" + +func TestCat(t *testing.T) { + s := NewString("foo") + got := s.Cat("bar") + if got != "foobar" { + t.Error("Expected 'foobar', got", got) + } +} + +func TestSwapChars(t *testing.T) { + s := NewString("foo") + got := s.SwapChars('o', 'u') + if got != "fuu" { + t.Error("Expected 'fuu', got", got) + } +} + +func TestStartsWithEndsWith(t *testing.T) { + s := NewString("foobar") + if !s.StartsWith("foo") { + t.Error("StartsWith yes") + } + if s.StartsWith("bar") { + t.Error("StartsWith no") + } + if !s.EndsWith("bar") { + t.Error("EndsWith yes") + } + if !s.EndsWith("bar") { + t.Error("EndsWith no") + } +} + +func TestBaseXToI64(t *testing.T) { + s := NewString("100000000") + var got int64 = s.BaseXToI64(10) + if got != 100000000 { + t.Error("positive base 10", got) + } + got = s.BaseXToI64(2) + if got != 256 { + t.Error("positive base 2", got) + } + s = NewString("-100000000") + got = s.BaseXToI64(10) + if got != -100000000 { + t.Error("negative base 10", got) + } + got = s.BaseXToI64(2) + if got != -256 { + t.Error("negative base 2", got) + } +} + +func TestFind(t *testing.T) { + s := NewString("foobarbaz") + var got int64 = s.Find("bar") + if got != 3 { + t.Error("Find yes", got) + } + got = s.Find("banana") + if got != -1 { + t.Error("Find no", got) + } +} + +func TestEquals(t *testing.T) { + t.Skip("Skip Equals because Obj arg won't accept string") + /* + s := NewString("foo") + if !s.Equals("foo") { + t.Error("Equals should succeed") + } + if s.Equals("bar") { + t.Error("Equals should fail") + } + */ +} + +func TestCompareTo(t *testing.T) { + t.Skip("Skip CompareTo() because Obj arg won't accept string") + /* + s := NewString("foo") + if !(s.CompareTo("boo") > 0) { + t.Error("'foo' > 'boo'") + } + if !(s.CompareTo("foo") == 0) { + t.Error("'foo' == 'foo'") + } + if !(s.CompareTo("zoo") < 0) { + t.Error("'foo' < 'zoo'") + } + if !(s.CompareTo("fo") > 0) { + t.Error("'foo' > 'fo'") + } + if !(s.CompareTo("food") < 0) { + t.Error("'foo' < 'food'") + } + if !(s.CompareTo("foo\u0000") < 0) { + t.Error("'foo' < 'foo\\0'") + } + if !(s.CompareTo("") > 0) { + t.Error("'foo' > ''") + } + */ +} + +func TestLenAndGetSize(t *testing.T) { + s := NewString("\u263a") + var len uintptr = s.Length() + if len != 1 { + t.Error("Length() should return 1, got", len) + } + var size uintptr = s.GetSize() + if size != 3 { + t.Error("GetSize() should return 3, got", size) + } +} + +func TestClone(t *testing.T) { + t.Skip("Skip Clone() because it shouldn't return an Obj") + s := NewString("foo") + got := s.Clone() + if !s.Equals(got) { + t.Fail() + } +} + +func TestHashSum(t *testing.T) { + // Test compilation only. + s := NewString("foo") + var _ uintptr = s.HashSum() +} + +func TestToString(t *testing.T) { + s := NewString("foo") + if s.ToString() != "foo" { + t.Fail() + } +} + +func TestTrim(t *testing.T) { + s := NewString(" foo ") + var got string = s.Trim() + if got != "foo" { + t.Error("Trim: '" + got + "'") + } + got = s.TrimTop() + if got != "foo " { + t.Error("TrimTop: '" + got + "'") + } + got = s.TrimTail() + if got != " foo" { + t.Error("TrimTail: '" + got + "'") + } +} + +func TestCodePointAtFrom(t *testing.T) { + s := NewString("foobar") + var got rune = s.CodePointAt(3) + if got != 'b' { + t.Error("CodePointAt returned", got) + } + got = s.CodePointFrom(2) + if got != 'a' { + t.Error("CodePointFrom returned", got) + } +} + +func TestSubString(t *testing.T) { + s := NewString("foobarbaz") + var got string = s.SubString(3, 3) + if got != "bar" { + t.Error("SubString returned", got) + } +} + +func TestTopTail(t *testing.T) { + s := NewString("foo") + top := s.Top() + got := top.Next() + if got != 'f' { + t.Error("Top iter returned", got) + } + tail := s.Tail() + got = tail.Prev() + if got != 'o' { + t.Error("Tail iter returned", got) + } +} From 4028a4d4fb37a9ca15592f6e6bb2399f3050fc8e Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Sat, 23 May 2015 16:01:02 -0700 Subject: [PATCH 7/7] Custom Go bindings for some String methods. Map C int32_t to Go rune when representing Unicode code points. --- runtime/go/build.go | 6 ++++++ runtime/go/clownfish/clownfish.go | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/runtime/go/build.go b/runtime/go/build.go index 4a182c77..186eebd1 100644 --- a/runtime/go/build.go +++ b/runtime/go/build.go @@ -142,6 +142,12 @@ func specMethods(parcel *cfc.Parcel) { errBinding := cfc.NewGoClass(parcel, "Clownfish::Err") errBinding.SpecMethod("", "Error() string") errBinding.Register() + + stringBinding := cfc.NewGoClass(parcel, "Clownfish::String") + stringBinding.SpecMethod("Code_Point_At", "CodePointAt(uintptr) rune") + stringBinding.SpecMethod("Code_Point_From", "CodePointFrom(uintptr) rune") + stringBinding.SpecMethod("Swap_Chars", "SwapChars(rune, rune) string") + stringBinding.Register() } func prep() { diff --git a/runtime/go/clownfish/clownfish.go b/runtime/go/clownfish/clownfish.go index 143c7c6b..b68bf9b2 100644 --- a/runtime/go/clownfish/clownfish.go +++ b/runtime/go/clownfish/clownfish.go @@ -154,3 +154,22 @@ func TrapErr(routine func()) (trapped error) { routine() return trapped } + +func (s *StringIMP) CodePointAt(tick uintptr) rune { + self := ((*C.cfish_String)(unsafe.Pointer(s.TOPTR()))) + retvalCF := C.CFISH_Str_Code_Point_At(self, C.size_t(tick)) + return rune(retvalCF) +} + +func (s *StringIMP) CodePointFrom(tick uintptr) rune { + self := ((*C.cfish_String)(unsafe.Pointer(s.TOPTR()))) + retvalCF := C.CFISH_Str_Code_Point_From(self, C.size_t(tick)) + return rune(retvalCF) +} + +func (s *StringIMP) SwapChars(match, replacement rune) string { + self := ((*C.cfish_String)(unsafe.Pointer(s.TOPTR()))) + retvalCF := C.CFISH_Str_Swap_Chars(self, C.int32_t(match), C.int32_t(replacement)) + defer C.cfish_dec_refcount(unsafe.Pointer(retvalCF)) + return CFStringToGo(unsafe.Pointer(retvalCF)) +}