time_parser_rb.c

Go to the documentation of this file.
00001 #include <stdio.h>
00002 #include <ruby.h>
00003 #include <alloca.h>
00004 #include "moment_parser.h"
00005 #include "interval_parser.h"
00006 
00007 #define Check_Time(t) \
00008   if (!rb_obj_is_instance_of(t, rb_cTime)) { \
00009     rb_raise(rb_eTypeError, "%s not a Time", #t); \
00010   }
00011 
00012 /*
00013 gboolean parse_moment(const char* s, time_t ctx, time_t now,
00014           time_t* result, GError** error)
00015 gboolean parse_interval(const char* s, time_t ctx, time_t now,
00016       time_t* beg, time_t* end, GError** error)
00017 */
00018 
00019 // Returns a result Time Range or :always or nil. Throws an
00020 // exception on a parse error.
00021 static VALUE rb_parse_interval(VALUE self, VALUE s,
00022              VALUE ctx, VALUE now)
00023 {
00024   Check_Time(ctx);
00025   Check_Time(now);
00026 
00027   char *c_str = StringValuePtr(s);
00028   time_t c_ctx = NUM2LONG(rb_funcall(ctx, rb_intern("to_i"), 0));
00029   time_t c_now = NUM2LONG(rb_funcall(now, rb_intern("to_i"), 0));
00030   time_t c_result_b = 0;
00031   time_t c_result_e = 0;
00032   GError* error = NULL;
00033   if (!parse_interval(c_str, c_ctx, c_now, 
00034           &c_result_b, &c_result_e, &error)) {
00035     char* msg = alloca(strlen(error->message) + 1);
00036     strcpy(msg, error->message);
00037     g_error_free(error);
00038     rb_raise(rb_eSyntaxError, msg);
00039   }
00040 
00041   if (!c_result_b && !c_result_e) {
00042     return Qnil;
00043   } else if (!c_result_e) {
00044     return ID2SYM(rb_intern("always"));
00045   } else {
00046     // We probably have to do GC registrations here. Suppose, when
00047     // allocating the second time object, the first one is GCd as the
00048     // runtime is not aware of any references to it.
00049     VALUE tms = rb_funcall(rb_cTime, rb_intern("at"), 1, LONG2NUM(c_result_b));
00050     rb_gc_register_address(&tms);
00051     VALUE tme = rb_funcall(rb_cTime, rb_intern("at"), 1, LONG2NUM(c_result_e));
00052     rb_gc_register_address(&tme);
00053     VALUE range = rb_funcall(rb_cRange, rb_intern("new"), 2, tms, tme);
00054     rb_gc_unregister_address(&tms);
00055     rb_gc_unregister_address(&tme);
00056     return range;
00057   }
00058 }
00059 
00060 // Returns the result Time or nil (indicating no such moment
00061 // upcoming). Throws an exception on a parse error.
00062 static VALUE rb_parse_moment(VALUE self, VALUE s,
00063            VALUE ctx, VALUE now)
00064 {
00065   Check_Time(ctx);
00066   Check_Time(now);
00067 
00068   char *c_str = StringValuePtr(s);
00069   time_t c_ctx = NUM2LONG(rb_funcall(ctx, rb_intern("to_i"), 0));
00070   time_t c_now = NUM2LONG(rb_funcall(now, rb_intern("to_i"), 0));
00071   time_t c_result = 0;
00072   GError* error = NULL;
00073   if (!parse_moment(c_str, c_ctx, c_now, &c_result, &error)) {
00074     // A problem here is that we must pass a string to rb_raise, one
00075     // which really has to be freed at some point, but rb_raise does
00076     // not give us a chance to do cleanup before causing a return from
00077     // this function. Also, wrapping the string inside a Ruby object
00078     // does not really work either, since Ruby would not know about
00079     // there being a reference to the string, and hence might GC it
00080     // too early, unless we used rb_gc_register_address, but if we
00081     // used that we would also have to later use
00082     // rb_gc_unregister_address, and there we are back to square one.
00083     // The only solution is to copy the string to the stack, either
00084     // using a fixed size buffer or "alloca".
00085     // 
00086     // Another alternative would be to construct the exception
00087     // instance first, and only then throw it.
00088     char* msg = alloca(strlen(error->message) + 1);
00089     strcpy(msg, error->message);
00090     g_error_free(error);
00091     rb_raise(rb_eSyntaxError, msg);
00092   }
00093 
00094   if (c_result == 0)
00095     return Qnil;
00096   else
00097     // This should be okay. The "at" method will have the number
00098     // object on its stack, and hence we can trust the number not to
00099     // get GCd too early. Besides, the number probably is not even a
00100     // real object, but rather just a labeled value.
00101     return rb_funcall(rb_cTime, rb_intern("at"), 1, LONG2NUM(c_result));
00102 }
00103 
00104 void Init_time_parser_rb()
00105 {
00106   VALUE topModule = rb_define_module("TimeParser");
00107   rb_define_module_function(topModule, "parse_moment", rb_parse_moment, 3);
00108   rb_define_module_function(topModule, "parse_interval", rb_parse_interval, 3);
00109 }

ContextLogger2—ContextLogger2 Logger Daemon Internals—Generated on Mon May 2 13:49:56 2011 by Doxygen 1.6.1