From ebb7b677ba661c980e0a9d582aac27785ae5256d Mon Sep 17 00:00:00 2001
From: Dan Hirsch <thequux@upstandinghackers.com>
Date: Sat, 4 Jan 2014 22:11:32 +0100
Subject: [PATCH] Added handwritten test for h_action to C#

---
 src/bindings/dotnet/ext/hammer.cs             | 78 ++++++++++++++++---
 src/bindings/dotnet/hammer.i                  | 36 +++++++++
 src/bindings/dotnet/test/hammer_hand_tests.cs | 18 +++++
 src/hammer.h                                  |  6 +-
 src/registry.c                                | 18 ++---
 5 files changed, 132 insertions(+), 24 deletions(-)
 create mode 100644 src/bindings/dotnet/test/hammer_hand_tests.cs

diff --git a/src/bindings/dotnet/ext/hammer.cs b/src/bindings/dotnet/ext/hammer.cs
index 9b816ef..00f217f 100644
--- a/src/bindings/dotnet/ext/hammer.cs
+++ b/src/bindings/dotnet/ext/hammer.cs
@@ -1,9 +1,23 @@
 using Hammer.Internal;
 using System;
 using System.Runtime.InteropServices;
+using System.Collections;
 namespace Hammer
 {
 
+  public delegate Object HAction(Object obj);
+  public delegate bool HPredicate(Object obj);
+
+  public class ParseError : Exception
+  {
+    public readonly string Reason;
+    public ParseError() : this(null) {}
+    public ParseError(string reason) : base() {
+      Reason = reason;
+    }
+  }
+    
+
   public class Parser
   {
     internal HParser wrapped;
@@ -19,7 +33,7 @@ namespace Hammer
       pins.Add(o);
       return this;
     }
-    
+
     public Object Parse(byte[] str)
     {
       byte[] strp;
@@ -27,15 +41,20 @@ namespace Hammer
         strp = new byte[1];
       else
         strp = str;
-      unsafe {
-        fixed(byte* b = &strp[0]) {
-          HParseResult res = hammer.h_parse(wrapped, (IntPtr)b, (uint)str.Length);
-          if (res != null) {
-            return Unmarshal(res.ast);
-          } else {
-            return null;
+      try {
+        unsafe {
+          fixed(byte* b = &strp[0]) {
+            HParseResult res = hammer.h_parse(wrapped, (IntPtr)b, (uint)str.Length);
+            if (res != null) {
+              // TODO: free the PR
+              return Unmarshal(res.ast);
+            } else {
+              throw new ParseError();
+            }
           }
         }
+      } catch (ParseError e) {
+        return null;
       }
     }
     
@@ -65,6 +84,22 @@ namespace Hammer
           return ret;
         }
       default:
+        if (tok.token_type == Hammer.tt_dotnet)
+          {
+            HTaggedToken tagged = hammer.h_parsed_token_get_tagged_token(tok);
+            Object cb = Hammer.tag_to_action[tagged.label];
+            Object unmarshalled = Unmarshal(tagged.token);
+            if (cb is HAction) {
+              HAction act = (HAction)cb;
+              return act(unmarshalled);
+            } else if (cb is HPredicate) {
+              HPredicate pred = (HPredicate)cb;
+              if (!pred(unmarshalled))
+                throw new ParseError("Predicate failed");
+              else
+                return unmarshalled;
+            }
+          }
         throw new Exception("Should not reach here");
       }
     }
@@ -83,10 +118,25 @@ namespace Hammer
       hammer.h_bind_indirect(this.wrapped, p.wrapped);
     }
   }
-    
+
   public class Hammer
   {
-
+    internal static IDictionary tag_to_action;
+    internal static HTokenType tt_dotnet;
+    static Hammer()
+    {
+      tt_dotnet = hammer.h_allocate_token_type("com.upstandinghackers.hammer.dotnet.tagged");
+      hammer.h_set_dotnet_tagged_token_type(tt_dotnet);
+      tag_to_action = new System.Collections.Hashtable();
+    }
+    
+    internal static ulong RegisterAction(HAction action)
+    {
+      ulong newAction = (ulong)tag_to_action.Count;
+      tag_to_action[newAction] = action;
+      return newAction;
+    }
+    
     internal static byte[] ToBytes(string s)
     {
       // Probably not what you want unless you're parsing binary data.
@@ -104,7 +154,7 @@ namespace Hammer
       IntPtr[] rlist = new IntPtr[parsers.Length+1];
       for (int i = 0; i < parsers.Length; i++)
         {
-          rlist[i] = HParser.getCPtr(parsers[i].wrapped).Handle;          
+          rlist[i] = HParser.getCPtr(parsers[i].wrapped).Handle;
         }
       rlist[parsers.Length] = IntPtr.Zero;
       return rlist;
@@ -307,7 +357,11 @@ namespace Hammer
     {
       return new Parser(hammer.h_repeat_n(p.wrapped, count));
     }
-
+    public static Parser Action(Parser p, HAction action)
+    {
+      ulong actionNo = Hammer.RegisterAction(action);
+      return new Parser(hammer.h_tag(p.wrapped, actionNo)).Pin(p).Pin(action);
+    }
   }
   
 }
\ No newline at end of file
diff --git a/src/bindings/dotnet/hammer.i b/src/bindings/dotnet/hammer.i
index b959bb9..98ef59b 100644
--- a/src/bindings/dotnet/hammer.i
+++ b/src/bindings/dotnet/hammer.i
@@ -42,3 +42,39 @@
  }
 
 %include "../swig/hammer.i";
+
+%{
+HTokenType dotnet_tagged_token_type;
+ %}
+%inline {
+  void h_set_dotnet_tagged_token_type(HTokenType new_tt) {
+    dotnet_tagged_token_type = new_tt;
+  }
+  // Need this inline as well
+  struct HTaggedToken {
+    HParsedToken *token;
+    uint64_t label;
+  };
+
+// this is to make it easier to access via SWIG
+struct HTaggedToken *h_parsed_token_get_tagged_token(HParsedToken* hpt) {
+  return (struct HTaggedToken*)hpt->token_data.user;
+}
+
+HParsedToken *act_tag(const HParseResult* p, void* user_data) {
+  struct HTaggedToken *tagged = H_ALLOC(struct HTaggedToken);
+  tagged->label = *(uint64_t*)user_data;
+  tagged->token = p->ast;
+  return h_make(p->arena, dotnet_tagged_token_type, tagged);
+}
+
+HParser *h_tag__m(HAllocator *mm__, HParser *p, uint64_t tag) {
+  uint64_t *tagptr = h_new(uint64_t, 1);
+  *tagptr = tag;
+  return h_action__m(mm__, p, act_tag, tagptr);
+}
+
+HParser *h_tag(HParser *p, uint64_t tag) {
+  return h_tag__m(&system_allocator, p, tag);
+}
+ }
diff --git a/src/bindings/dotnet/test/hammer_hand_tests.cs b/src/bindings/dotnet/test/hammer_hand_tests.cs
new file mode 100644
index 0000000..0d88528
--- /dev/null
+++ b/src/bindings/dotnet/test/hammer_hand_tests.cs
@@ -0,0 +1,18 @@
+namespace Hammer.Test
+{
+  using NUnit.Framework;
+  [TestFixture]
+  public partial class HammerTest
+  {
+    [Test]
+    public void TestAction()
+    {
+      Parser parser = Hammer.Action(Hammer.Sequence(Hammer.Choice(Hammer.Token("a"),
+                                                                  Hammer.Token("A")),
+                                                    Hammer.Choice(Hammer.Token("b"),
+                                                                  Hammer.Token("B"))),
+                                    (HAction)(x => char.ToUpper(((string)x)[0])));
+                                                                  
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/hammer.h b/src/hammer.h
index 2914b8f..dc403c0 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -666,13 +666,13 @@ void h_benchmark_report(FILE* stream, HBenchmarkResults* results);
 
 // {{{ Token type registry
 /// Allocate a new, unused (as far as this function knows) token type.
-int h_allocate_token_type(const char* name);
+HTokenType h_allocate_token_type(const char* name);
 
 /// Get the token type associated with name. Returns -1 if name is unkown
-int h_get_token_type_number(const char* name);
+HTokenType h_get_token_type_number(const char* name);
 
 /// Get the name associated with token_type. Returns NULL if the token type is unkown
-const char* h_get_token_type_name(int token_type);
+const char* h_get_token_type_name(HTokenType token_type);
 // }}}
 
 #ifdef __cplusplus
diff --git a/src/registry.c b/src/registry.c
index c59b6ea..60aa886 100644
--- a/src/registry.c
+++ b/src/registry.c
@@ -22,14 +22,14 @@
 
 typedef struct Entry_ {
   const char* name;
-  int value;
+  HTokenType value;
 } Entry;
 
 static void *tt_registry = NULL;
 static Entry** tt_by_id = NULL;
-static int tt_by_id_sz = 0;
+static unsigned int tt_by_id_sz = 0;
 #define TT_START TT_USER
-static int tt_next = TT_START;
+static HTokenType tt_next = TT_START;
 
 /*
   // TODO: These are for the extension registry, which does not yet have a good name.
@@ -45,12 +45,12 @@ static int compare_entries(const void* v1, const void* v2) {
   return strcmp(e1->name, e2->name);
 }
 
-int h_allocate_token_type(const char* name) {
+HTokenType h_allocate_token_type(const char* name) {
   Entry* new_entry = malloc(sizeof(*new_entry));
   new_entry->name = name;
-  new_entry->value = -1;
+  new_entry->value = 0;
   Entry* probe = *(Entry**)tsearch(new_entry, &tt_registry, compare_entries);
-  if (probe->value != -1) {
+  if (probe->value != 0) {
     // Token type already exists...
     // TODO: treat this as a bug?
     free(new_entry);
@@ -70,16 +70,16 @@ int h_allocate_token_type(const char* name) {
     return probe->value;
   }
 }
-int h_get_token_type_number(const char* name) {
+HTokenType h_get_token_type_number(const char* name) {
   Entry e;
   e.name = name;
   Entry **ret = (Entry**)tfind(&e, &tt_registry, compare_entries);
   if (ret == NULL)
-    return -1;
+    return 0;
   else
     return (*ret)->value;
 }
-const char* h_get_token_type_name(int token_type) {
+const char* h_get_token_type_name(HTokenType token_type) {
   if (token_type >= tt_next || token_type < TT_START)
     return NULL;
   else
-- 
GitLab