Created
June 17, 2014 03:50
-
-
Save bmorton/46e037136741d95c9926 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/lib/mkmf.rb b/lib/mkmf.rb | |
index 898e4a7..122cf19 100644 | |
--- a/lib/mkmf.rb | |
+++ b/lib/mkmf.rb | |
@@ -2206,7 +2206,7 @@ def init_mkmf(config = CONFIG, rbconfig = RbConfig::CONFIG) | |
$LOCAL_LIBS = "" | |
$cleanfiles = config_string('CLEANFILES') {|s| Shellwords.shellwords(s)} || [] | |
- $cleanfiles << "mkmf.log" | |
+ $cleanfiles << "mkmf.log .*.time" | |
$distcleanfiles = config_string('DISTCLEANFILES') {|s| Shellwords.shellwords(s)} || [] | |
$distcleandirs = config_string('DISTCLEANDIRS') {|s| Shellwords.shellwords(s)} || [] | |
diff --git a/configure.in b/configure.in | |
index 2736233..1a31b16 100644 | |
--- a/configure.in | |
+++ b/configure.in | |
@@ -2481,6 +2481,10 @@ if test "$EXEEXT" = .exe; then | |
AC_SUBST(EXECUTABLE_EXTS) | |
fi | |
+dnl enable gc debugging | |
+AC_ARG_ENABLE(gcdebug, | |
+ AS_HELP_STRING([--enable-gcdebug], [build garbage collector with debugging enabled]), | |
+ [AC_DEFINE(GC_DEBUG,1)]) | |
dnl } | |
dnl build section { | |
diff --git a/gc.c b/gc.c | |
index 6eaf4ae..5f1d469 100644 | |
--- a/gc.c | |
+++ b/gc.c | |
@@ -98,6 +98,15 @@ ruby_gc_params_t initial_params = { | |
#endif | |
}; | |
+#ifndef HAVE_LONG_LONG | |
+#define LONG_LONG long | |
+#endif | |
+ | |
+static int heap_min_slots = 10000; | |
+static int heap_slots_increment = 10000; | |
+static int initial_heap_slots_increment = 10000; | |
+static double heap_slots_growth_factor = 1.8; | |
+ | |
#define nomem_error GET_VM()->special_exceptions[ruby_error_nomemory] | |
#if SIZEOF_LONG == SIZEOF_VOIDP | |
@@ -302,7 +311,7 @@ typedef struct RVALUE { | |
struct RComplex complex; | |
} as; | |
#ifdef GC_DEBUG | |
- const char *file; | |
+ VALUE file; | |
int line; | |
#endif | |
} RVALUE; | |
@@ -372,11 +381,25 @@ typedef struct rb_objspace { | |
size_t free_min; | |
size_t final_num; | |
size_t do_heap_free; | |
+ unsigned long max_blocks_to_free; | |
+ unsigned long freed_blocks; | |
} heap; | |
struct { | |
+ unsigned long processed; | |
+ unsigned long freed_objects; | |
+ unsigned long freelist_size; | |
+ unsigned long zombies; | |
+ unsigned long free_counts[T_MASK+1]; | |
+ unsigned long live_counts[T_MASK+1]; | |
+ unsigned long gc_time_accumulator_before_gc; | |
+ unsigned long live_after_last_mark_phase; | |
+ } stats; | |
+ struct { | |
int dont_gc; | |
int dont_lazy_sweep; | |
int during_gc; | |
+ int gc_statistics; | |
+ int verbose_gc_stats; | |
rb_atomic_t finalizing; | |
} flags; | |
struct { | |
@@ -394,6 +417,14 @@ typedef struct rb_objspace { | |
struct gc_list *global_list; | |
size_t count; | |
int gc_stress; | |
+ long heap_size; | |
+ unsigned LONG_LONG gc_time_accumulator; | |
+ FILE* gc_data_file; | |
+ long gc_collections; | |
+ unsigned LONG_LONG gc_allocated_size; | |
+ unsigned LONG_LONG gc_num_allocations; | |
+ unsigned long live_objects; | |
+ unsigned LONG_LONG allocated_objects; | |
} rb_objspace_t; | |
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE | |
@@ -416,6 +447,16 @@ int *ruby_initial_gc_stress_ptr = &rb_objspace.gc_stress; | |
#define heaps_freed objspace->heap.freed | |
#define dont_gc objspace->flags.dont_gc | |
#define during_gc objspace->flags.during_gc | |
+#define gc_statistics objspace->flags.gc_statistics | |
+#define verbose_gc_stats objspace->flags.verbose_gc_stats | |
+#define heap_size objspace->heap_size | |
+#define gc_time_accumulator objspace->gc_time_accumulator | |
+#define gc_data_file objspace->gc_data_file | |
+#define gc_collections objspace->gc_collections | |
+#define gc_allocated_size objspace->gc_allocated_size | |
+#define gc_num_allocations objspace->gc_num_allocations | |
+#define live_objects objspace->live_objects | |
+#define allocated_objects objspace->allocated_objects | |
#define finalizing objspace->flags.finalizing | |
#define finalizer_table objspace->final.table | |
#define deferred_final_list objspace->final.deferred | |
@@ -424,6 +465,14 @@ int *ruby_initial_gc_stress_ptr = &rb_objspace.gc_stress; | |
#define initial_malloc_limit initial_params.initial_malloc_limit | |
#define initial_heap_min_slots initial_params.initial_heap_min_slots | |
#define initial_free_min initial_params.initial_free_min | |
+#define free_counts objspace->stats.free_counts | |
+#define live_counts objspace->stats.live_counts | |
+#define processed objspace->stats.processed | |
+#define zombies objspace->stats.zombies | |
+#define freelist_size objspace->stats.freelist_size | |
+#define freed_objects objspace->stats.freed_objects | |
+#define gc_time_accumulator_before_gc objspace->stats.gc_time_accumulator_before_gc | |
+#define live_after_last_mark_phase objspace->stats.live_after_last_mark_phase | |
static void rb_objspace_call_finalizer(rb_objspace_t *objspace); | |
@@ -446,24 +495,59 @@ static void init_mark_stack(mark_stack_t *stack); | |
void | |
rb_gc_set_params(void) | |
{ | |
- char *malloc_limit_ptr, *heap_min_slots_ptr, *free_min_ptr; | |
+ char *envp; | |
+ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ | |
+ gc_data_file = stderr; | |
if (rb_safe_level() > 0) return; | |
- malloc_limit_ptr = getenv("RUBY_GC_MALLOC_LIMIT"); | |
- if (malloc_limit_ptr != NULL) { | |
- int malloc_limit_i = atoi(malloc_limit_ptr); | |
+ envp = getenv("RUBY_GC_STATS"); | |
+ if (envp != NULL) { | |
+ int i = atoi(envp); | |
+ if (i > 0) { | |
+ verbose_gc_stats = 1; | |
+ fprintf(stderr, "RUBY_GC_STATS=%d\n", verbose_gc_stats); | |
+ } | |
+ /* child processes should not inherit RUBY_GC_STATS */ | |
+ ruby_unsetenv("RUBY_GC_STATS"); | |
+ } | |
+ | |
+ envp = getenv("RUBY_GC_DATA_FILE"); | |
+ if (envp != NULL) { | |
+ FILE* data_file = fopen(envp, "w"); | |
+ if (data_file != NULL) { | |
+ gc_data_file = data_file; | |
+ } | |
+ else { | |
+ fprintf(stderr, "can't open gc log file %s for writing, using default\n", envp); | |
+ } | |
+ /* child processes should not inherit RUBY_GC_DATA_FILE to avoid clobbering */ | |
+ ruby_unsetenv("RUBY_GC_DATA_FILE"); | |
+ } | |
+ | |
+ envp = getenv("RUBY_GC_MALLOC_LIMIT"); | |
+ if (envp != NULL) { | |
+ int malloc_limit_i = atoi(envp); | |
+ if (verbose_gc_stats) { | |
+ fprintf(gc_data_file, "RUBY_GC_MALLOC_LIMIT=%s\n", envp); | |
+ } | |
if (RTEST(ruby_verbose)) | |
fprintf(stderr, "malloc_limit=%d (%d)\n", | |
malloc_limit_i, initial_malloc_limit); | |
if (malloc_limit_i > 0) { | |
initial_malloc_limit = malloc_limit_i; | |
+ // malloc_limit = initial_malloc_limit; | |
} | |
} | |
- heap_min_slots_ptr = getenv("RUBY_HEAP_MIN_SLOTS"); | |
- if (heap_min_slots_ptr != NULL) { | |
- int heap_min_slots_i = atoi(heap_min_slots_ptr); | |
+ envp = getenv("RUBY_HEAP_MIN_SLOTS"); | |
+ if (envp != NULL) { | |
+ int heap_min_slots_i = atoi(envp); | |
+ if (verbose_gc_stats) { | |
+ fprintf(gc_data_file, "RUBY_HEAP_MIN_SLOTS=%s\n", envp); | |
+ } | |
if (RTEST(ruby_verbose)) | |
fprintf(stderr, "heap_min_slots=%d (%d)\n", | |
heap_min_slots_i, initial_heap_min_slots); | |
@@ -473,15 +557,42 @@ rb_gc_set_params(void) | |
} | |
} | |
- free_min_ptr = getenv("RUBY_FREE_MIN"); | |
- if (free_min_ptr != NULL) { | |
- int free_min_i = atoi(free_min_ptr); | |
+ if (!(envp = getenv("RUBY_FREE_MIN"))) | |
+ envp = getenv("RUBY_HEAP_FREE_MIN"); | |
+ if (envp != NULL) { | |
+ int free_min_i = atoi(envp); | |
+ if (verbose_gc_stats) { | |
+ fprintf(gc_data_file, "RUBY_HEAP_FREE_MIN=%s\n", envp); | |
+ } | |
if (RTEST(ruby_verbose)) | |
fprintf(stderr, "free_min=%d (%d)\n", free_min_i, initial_free_min); | |
if (free_min_i > 0) { | |
initial_free_min = free_min_i; | |
} | |
} | |
+ | |
+ envp = getenv("RUBY_HEAP_SLOTS_INCREMENT"); | |
+ if (envp != NULL) { | |
+ int i = atoi(envp); | |
+ if (verbose_gc_stats) { | |
+ fprintf(gc_data_file, "RUBY_HEAP_SLOTS_INCREMENT=%s\n", envp); | |
+ } | |
+ heap_slots_increment = i; | |
+ initial_heap_slots_increment = heap_slots_increment; | |
+ } | |
+ | |
+ envp = getenv("RUBY_HEAP_SLOTS_GROWTH_FACTOR"); | |
+ if (envp != NULL) { | |
+ double d = atof(envp); | |
+ if (verbose_gc_stats) { | |
+ fprintf(gc_data_file, "RUBY_HEAP_SLOTS_GROWTH_FACTOR=%s\n", envp); | |
+ } | |
+ if (d > 0) { | |
+ heap_slots_growth_factor = d; | |
+ } | |
+ } | |
+ | |
+ fflush(gc_data_file); | |
} | |
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE | |
@@ -778,6 +889,11 @@ vm_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size) | |
mem = (size_t *)mem + 1; | |
#endif | |
+ if (gc_statistics) { | |
+ gc_allocated_size += size; | |
+ gc_num_allocations += 1; | |
+ } | |
+ | |
return mem; | |
} | |
@@ -838,6 +954,13 @@ vm_xrealloc(rb_objspace_t *objspace, void *ptr, size_t size) | |
mem = (size_t *)mem + 1; | |
#endif | |
+ /* TODO: we can't count correctly unless we store old size on heap | |
+ if (gc_statistics) { | |
+ gc_allocated_size += size; | |
+ gc_num_allocations += 1; | |
+ } | |
+ */ | |
+ | |
return mem; | |
} | |
@@ -919,7 +1042,6 @@ ruby_xfree(void *x) | |
vm_xfree(&rb_objspace, x); | |
} | |
- | |
/* | |
* call-seq: | |
* GC.enable -> true or false | |
@@ -965,6 +1087,455 @@ rb_gc_disable(void) | |
return old ? Qtrue : Qfalse; | |
} | |
+/* | |
+ * call-seq: | |
+ * GC.enable_stats => true or false | |
+ * | |
+ * Enables garbage collection statistics, returning <code>true</code> if garbage | |
+ * collection statistics was already enabled. | |
+ * | |
+ * GC.enable_stats #=> false or true | |
+ * GC.enable_stats #=> true | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_enable_stats() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ int old = gc_statistics; | |
+ gc_statistics = 1; | |
+ return old ? Qtrue : Qfalse; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.disable_stats => true or false | |
+ * | |
+ * Disables garbage collection statistics, returning <code>true</code> if garbage | |
+ * collection statistics was already disabled. | |
+ * | |
+ * GC.disable_stats #=> false or true | |
+ * GC.disable_stats #=> true | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_disable_stats() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ int old = gc_statistics; | |
+ gc_statistics = 0; | |
+ return old ? Qtrue : Qfalse; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.stats_enabled? => true or false | |
+ * | |
+ * Check whether GC stats have been enabled. | |
+ * | |
+ * GC.stats_enabled? #=> false or true | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_stats_enabled() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return gc_statistics ? Qtrue : Qfalse; | |
+} | |
+ | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.clear_stats => nil | |
+ * | |
+ * Clears garbage collection statistics, returning nil. This resets the number | |
+ * of collections (GC.collections) and the time used (GC.time) to 0. | |
+ * | |
+ * GC.clear_stats #=> nil | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_clear_stats() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ gc_collections = 0; | |
+ gc_time_accumulator = 0; | |
+ gc_time_accumulator_before_gc = 0; | |
+ gc_allocated_size = 0; | |
+ gc_num_allocations = 0; | |
+ return Qnil; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.allocated_size => Integer | |
+ * | |
+ * Returns the size of memory (in bytes) allocated since GC statistics collection | |
+ * was enabled. | |
+ * | |
+ * GC.allocated_size #=> 35 | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_allocated_size() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+#if HAVE_LONG_LONG | |
+ return ULL2NUM(gc_allocated_size); | |
+#else | |
+ return ULONG2NUM(gc_allocated_size); | |
+#endif | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.num_allocations => Integer | |
+ * | |
+ * Returns the number of memory allocations since GC statistics collection | |
+ * was enabled. | |
+ * | |
+ * GC.num_allocations #=> 150 | |
+ * | |
+ */ | |
+VALUE | |
+rb_gc_num_allocations() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+#if HAVE_LONG_LONG | |
+ return ULL2NUM(gc_num_allocations); | |
+#else | |
+ return ULONG2NUM(gc_num_allocations); | |
+#endif | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.enable_trace => true or false | |
+ * | |
+ * Enables garbage collection tracing, returning <code>true</code> if garbage | |
+ * collection tracing was already enabled. | |
+ * | |
+ * GC.enable_trace #=> false or true | |
+ * GC.enable_trace #=> true | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_enable_trace() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ int old = verbose_gc_stats; | |
+ verbose_gc_stats = 1; | |
+ return old ? Qtrue : Qfalse; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.disable_trace => true or false | |
+ * | |
+ * Disables garbage collection tracing, returning <code>true</code> if garbage | |
+ * collection tracing was already disabled. | |
+ * | |
+ * GC.disable_trace #=> false or true | |
+ * GC.disable_trace #=> true | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_disable_trace() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ int old = verbose_gc_stats; | |
+ verbose_gc_stats = 0; | |
+ return old ? Qtrue : Qfalse; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.trace_enabled? => true or false | |
+ * | |
+ * Check whether GC tracing has been enabled. | |
+ * | |
+ * GC.trace_enabled? #=> false or true | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_trace_enabled() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return verbose_gc_stats ? Qtrue : Qfalse; | |
+} | |
+ | |
+ | |
+const char* GC_LOGFILE_IVAR = "@gc_logfile_name"; | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.log_file(filename=nil, mode="w") => boolean | |
+ * | |
+ * Changes the GC data log file. Closes the currently open logfile. | |
+ * Returns true if the file was successfully opened for | |
+ * writing. Returns false if the file could not be opened for | |
+ * writing. Returns the name of the current logfile (or nil) if no | |
+ * parameter is given. Restores logging to stderr when given nil as | |
+ * an argument. | |
+ * | |
+ * GC.log_file #=> nil | |
+ * GC.log_file "/tmp/gc.log" #=> true | |
+ * GC.log_file #=> "/tmp/gc.log" | |
+ * GC.log_file nil #=> true | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_log_file(int argc, VALUE *argv, VALUE self) | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ VALUE filename = Qnil; | |
+ VALUE mode_str = Qnil; | |
+ FILE* f = NULL; | |
+ const char* mode = "w"; | |
+ | |
+ VALUE current_logfile_name = rb_iv_get(rb_mGC, GC_LOGFILE_IVAR); | |
+ | |
+ if (argc==0) | |
+ return current_logfile_name; | |
+ | |
+ rb_scan_args(argc, argv, "02", &filename, &mode_str); | |
+ | |
+ if (filename == Qnil) { | |
+ /* close current logfile and reset logfile to stderr */ | |
+ if (gc_data_file != stderr) { | |
+ fclose(gc_data_file); | |
+ gc_data_file = stderr; | |
+ rb_iv_set(rb_mGC, GC_LOGFILE_IVAR, Qnil); | |
+ } | |
+ return Qtrue; | |
+ } | |
+ | |
+ /* we have a real logfile name */ | |
+ filename = StringValue(filename); | |
+ | |
+ if (rb_equal(current_logfile_name, filename) == Qtrue) { | |
+ /* do nothing if we get the file name we're already logging to */ | |
+ return Qtrue; | |
+ } | |
+ | |
+ /* get mode for file opening */ | |
+ if (mode_str != Qnil) | |
+ { | |
+ mode = RSTRING_PTR(StringValue(mode_str)); | |
+ } | |
+ | |
+ /* try to open file in given mode */ | |
+ if (f = fopen(RSTRING_PTR(filename), mode)) { | |
+ if (gc_data_file != stderr) { | |
+ fclose(gc_data_file); | |
+ } | |
+ gc_data_file = f; | |
+ rb_iv_set(rb_mGC, GC_LOGFILE_IVAR, filename); | |
+ } else { | |
+ return Qfalse; | |
+ } | |
+ return Qtrue; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.log String => String | |
+ * | |
+ * Logs string to the GC data file and returns it. | |
+ * | |
+ * GC.log "manual GC call" #=> "manual GC call" | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_log(self, original_str) | |
+ VALUE self, original_str; | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ if (original_str == Qnil) { | |
+ fprintf(gc_data_file, "\n"); | |
+ } | |
+ else { | |
+ VALUE str = StringValue(original_str); | |
+ char *p = RSTRING_PTR(str); | |
+ fprintf(gc_data_file, "%s\n", p); | |
+ } | |
+ return original_str; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.dump => nil | |
+ * | |
+ * dumps information about the current GC data structures to the GC log file | |
+ * | |
+ * GC.dump #=> nil | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_dump() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ size_t i; | |
+ | |
+ for (i = 0; i < heaps_used; i++) { | |
+ size_t limit = objspace->heap.sorted[i].slot->limit; | |
+ fprintf(gc_data_file, "HEAP[%2lu]: size=%7lu\n", (unsigned long)i, (unsigned long)limit); | |
+ } | |
+ | |
+ return Qnil; | |
+} | |
+ | |
+static const char* obj_type(VALUE tp); | |
+ | |
+#ifdef GC_DEBUG | |
+/* | |
+ * call-seq: | |
+ * GC.dump_file_and_line_info(String, boolean) => nil | |
+ * | |
+ * dumps information on which currently allocated object was created by which file and on which line | |
+ * | |
+ * GC.dump_file_and_line_info(String, boolean) #=> nil | |
+ * | |
+ * The second parameter specifies whether class names should be included in the dump. | |
+ * Note that including class names will allocate additional string objects on the heap. | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_dump_file_and_line_info(int argc, VALUE *argv) | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ VALUE filename, str, include_classnames = Qnil; | |
+ char *fname = NULL; | |
+ char *klass = NULL; | |
+ FILE* f = NULL; | |
+ size_t i = 0; | |
+ | |
+ rb_scan_args(argc, argv, "11", &filename, &include_classnames); | |
+ | |
+ str = StringValue(filename); | |
+ fname = RSTRING_PTR(str); | |
+ f = fopen(fname, "w"); | |
+ | |
+ for (i = 0; i < heaps_used; i++) { | |
+ RVALUE *p, *pend; | |
+ | |
+ p = objspace->heap.sorted[i].start; pend = objspace->heap.sorted[i].end; | |
+ for (;p < pend; p++) { | |
+ if (p->as.basic.flags) { | |
+ const char *src_filename = (p->file && p->file != Qnil )? RSTRING_PTR(p->file) : ""; | |
+ fprintf(f, "%s:%s:%d", obj_type(p->as.basic.flags & T_MASK), src_filename, (int)p->line); | |
+ // rb_obj_classname will create objects on the heap, we need a better solution | |
+ if (include_classnames == Qtrue) { | |
+ /* write the class */ | |
+ fprintf(f, ":"); | |
+ switch (BUILTIN_TYPE(p)) { | |
+ case T_NONE: | |
+ fprintf(f, "__none__"); | |
+ break; | |
+ case T_UNDEF: | |
+ fprintf(f, "__undef__"); | |
+ break; | |
+ case T_NODE: | |
+ fprintf(f, "__node__"); | |
+ break; | |
+ default: | |
+ if (!p->as.basic.klass) { | |
+ fprintf(f, "__unknown__"); | |
+ } else { | |
+ fprintf(f, "%s", rb_obj_classname((VALUE)p)); | |
+ } | |
+ } | |
+ /* print object size for some known object types */ | |
+ switch (BUILTIN_TYPE(p)) { | |
+ case T_STRING: | |
+ fprintf(f, ":%lu", RSTRING_LEN(p)); | |
+ break; | |
+ case T_ARRAY: | |
+ fprintf(f, ":%lu", RARRAY_LEN(p)); | |
+ break; | |
+ case T_HASH: | |
+ fprintf(f, ":%lu", (long unsigned int)RHASH_SIZE(p)); | |
+ break; | |
+ } | |
+ } | |
+ fprintf(f, "\n"); | |
+ } | |
+ } | |
+ } | |
+ fclose(f); | |
+ return Qnil; | |
+} | |
+#endif | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.heap_slots => Integer | |
+ * | |
+ * Returns the number of heap slots available for object allocations. | |
+ * | |
+ * GC.heap_slots #=> 10000 | |
+ * | |
+ */ | |
+VALUE | |
+rb_gc_heap_slots() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return LONG2NUM(heap_size); | |
+} | |
+ | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.collections => Integer | |
+ * | |
+ * Returns the number of garbage collections performed while GC statistics collection | |
+ * was enabled. | |
+ * | |
+ * GC.collections #=> 35 | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_collections() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return LONG2NUM(gc_collections); | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * GC.time => Integer | |
+ * | |
+ * Returns the time spent during garbage collection while GC statistics collection | |
+ * was enabled (in micro seconds). | |
+ * | |
+ * GC.time #=> 20000 | |
+ * | |
+ */ | |
+ | |
+VALUE | |
+rb_gc_time() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+#if HAVE_LONG_LONG | |
+ return LL2NUM(gc_time_accumulator); | |
+#else | |
+ return LONG2NUM(gc_time_accumulator); | |
+#endif | |
+} | |
+ | |
VALUE rb_mGC; | |
void | |
@@ -1036,6 +1607,12 @@ allocate_sorted_heaps(rb_objspace_t *objspace, size_t next_heaps_length) | |
static void | |
assign_heap_slot(rb_objspace_t *objspace) | |
{ | |
+ /* | |
+ if (gc_statistics & verbose_gc_stats) { | |
+ fprintf(gc_data_file, "assigning heap slot\n"); | |
+ } | |
+ */ | |
+ | |
RVALUE *p, *pend, *membase; | |
struct heaps_slot *slot; | |
size_t hi, lo, mid; | |
@@ -1097,6 +1674,7 @@ assign_heap_slot(rb_objspace_t *objspace) | |
if (lomem == 0 || lomem > p) lomem = p; | |
if (himem < pend) himem = pend; | |
heaps_used++; | |
+ heap_size += objs; | |
while (p < pend) { | |
p->as.free.flags = 0; | |
@@ -1153,7 +1731,7 @@ initial_expand_heap(rb_objspace_t *objspace) | |
static void | |
set_heaps_increment(rb_objspace_t *objspace) | |
{ | |
- size_t next_heaps_length = (size_t)(heaps_used * 1.8); | |
+ size_t next_heaps_length = (size_t)(heaps_used * heap_slots_growth_factor); | |
if (next_heaps_length == heaps_used) { | |
next_heaps_length++; | |
@@ -1186,6 +1764,22 @@ rb_during_gc(void) | |
#define RANY(o) ((RVALUE*)(o)) | |
+#ifdef GC_DEBUG | |
+static VALUE | |
+_rb_sourcefile(void) | |
+{ | |
+ rb_thread_t *th = GET_THREAD(); | |
+ rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp); | |
+ | |
+ if (cfp) { | |
+ return cfp->iseq->filename; | |
+ } | |
+ else { | |
+ return Qnil; | |
+ } | |
+} | |
+#endif | |
+ | |
VALUE | |
rb_newobj(void) | |
{ | |
@@ -1217,9 +1811,11 @@ rb_newobj(void) | |
MEMZERO((void*)obj, RVALUE, 1); | |
#ifdef GC_DEBUG | |
- RANY(obj)->file = rb_sourcefile(); | |
+ RANY(obj)->file = _rb_sourcefile(); | |
RANY(obj)->line = rb_sourceline(); | |
#endif | |
+ live_objects++; | |
+ allocated_objects++; | |
GC_PROF_INC_LIVE_NUM; | |
return obj; | |
@@ -1771,6 +2367,12 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr) | |
{ | |
register RVALUE *obj = RANY(ptr); | |
+#ifdef GC_DEBUG | |
+ if (obj->file && obj->file != Qnil && is_pointer_to_heap(objspace, (void*)obj->file)) { | |
+ gc_mark(objspace, obj->file, lev); | |
+ } | |
+#endif | |
+ | |
goto marking; /* skip */ | |
again: | |
@@ -1781,6 +2383,12 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr) | |
obj->as.basic.flags |= FL_MARK; | |
objspace->heap.live_num++; | |
+#ifdef GC_DEBUG | |
+ if (obj->file && obj->file != Qnil && is_pointer_to_heap(objspace, (void*)obj->file)) { | |
+ gc_mark(objspace, obj->file, lev); | |
+ } | |
+#endif | |
+ | |
marking: | |
if (FL_TEST(obj, FL_EXIVAR)) { | |
rb_mark_generic_ivar(ptr); | |
@@ -2123,6 +2731,25 @@ free_unused_heaps(rb_objspace_t *objspace) | |
} | |
} | |
+static inline unsigned long | |
+elapsed_musecs(struct timeval since) | |
+{ | |
+ struct timeval now; | |
+ struct timeval temp; | |
+ | |
+ gettimeofday(&now, NULL); | |
+ | |
+ if ((now.tv_usec-since.tv_usec)<0) { | |
+ temp.tv_sec = now.tv_sec-since.tv_sec-1; | |
+ temp.tv_usec = 1000000+now.tv_usec-since.tv_usec; | |
+ } else { | |
+ temp.tv_sec = now.tv_sec-since.tv_sec; | |
+ temp.tv_usec = now.tv_usec-since.tv_usec; | |
+ } | |
+ | |
+ return temp.tv_sec*1000000 + temp.tv_usec; | |
+} | |
+ | |
static void | |
slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot) | |
{ | |
@@ -2130,14 +2757,23 @@ slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot) | |
RVALUE *p, *pend; | |
RVALUE *free = freelist, *final = deferred_final_list; | |
int deferred; | |
+ int do_gc_stats = gc_statistics & verbose_gc_stats; | |
+ | |
+ struct timeval tv1; | |
+ if (gc_statistics) gettimeofday(&tv1, NULL); | |
p = sweep_slot->slot; pend = p + sweep_slot->limit; | |
while (p < pend) { | |
if (!(p->as.basic.flags & FL_MARK)) { | |
+ if (do_gc_stats && !p->as.basic.flags) { | |
+ /* slot was free before GC */ | |
+ freelist_size++; | |
+ } | |
if (p->as.basic.flags && | |
((deferred = obj_free(objspace, (VALUE)p)) || | |
(FL_TEST(p, FL_FINALIZE)))) { | |
if (!deferred) { | |
+ if (do_gc_stats) zombies++; | |
p->as.free.flags = T_ZOMBIE; | |
RDATA(p)->dfree = 0; | |
} | |
@@ -2147,6 +2783,10 @@ slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot) | |
final_num++; | |
} | |
else { | |
+ if (do_gc_stats) { | |
+ VALUE obt = p->as.basic.flags & T_MASK; | |
+ if (obt) free_counts[obt]++; | |
+ } | |
add_freelist(objspace, p); | |
free_num++; | |
} | |
@@ -2154,13 +2794,22 @@ slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot) | |
else if (BUILTIN_TYPE(p) == T_ZOMBIE) { | |
/* objects to be finalized */ | |
/* do nothing remain marked */ | |
+ if (do_gc_stats) zombies++; | |
} | |
else { | |
RBASIC(p)->flags &= ~FL_MARK; | |
+ if (do_gc_stats) { | |
+ live_counts[p->as.basic.flags & T_MASK]++; | |
+ } | |
} | |
p++; | |
+ processed++; | |
} | |
- if (final_num + free_num == sweep_slot->limit && | |
+ | |
+ freed_objects += free_num; | |
+ | |
+ if (objspace->heap.freed_blocks < objspace->heap.max_blocks_to_free && | |
+ final_num + free_num == sweep_slot->limit && | |
objspace->heap.free_num > objspace->heap.do_heap_free) { | |
RVALUE *pp; | |
@@ -2171,6 +2820,8 @@ slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot) | |
sweep_slot->limit = final_num; | |
freelist = free; /* cancel this page from freelist */ | |
unlink_heap_slot(objspace, sweep_slot); | |
+ objspace->heap.freed_blocks += 1; | |
+ heap_size -= final_num + free_num; | |
} | |
else { | |
objspace->heap.free_num += free_num; | |
@@ -2183,6 +2834,10 @@ slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot) | |
RUBY_VM_SET_FINALIZER_INTERRUPT(th); | |
} | |
} | |
+ | |
+ if (gc_statistics) { | |
+ gc_time_accumulator += elapsed_musecs(tv1); | |
+ } | |
} | |
static int | |
@@ -2203,6 +2858,21 @@ ready_to_gc(rb_objspace_t *objspace) | |
static void | |
before_gc_sweep(rb_objspace_t *objspace) | |
{ | |
+ if (gc_statistics & verbose_gc_stats) { | |
+ /* | |
+ fprintf(gc_data_file, "Sweep started\n"); | |
+ */ | |
+ freed_objects = 0; | |
+ processed = 0; | |
+ zombies = 0; | |
+ freelist_size = 0; | |
+ MEMZERO((void*)free_counts, unsigned long, T_MASK+1); | |
+ MEMZERO((void*)live_counts, unsigned long, T_MASK+1); | |
+ } | |
+ | |
+ objspace->heap.max_blocks_to_free = heaps_used - (heap_min_slots / HEAP_OBJ_LIMIT); | |
+ objspace->heap.freed_blocks = 0; | |
+ | |
freelist = 0; | |
objspace->heap.do_heap_free = (size_t)((heaps_used * HEAP_OBJ_LIMIT) * 0.65); | |
objspace->heap.free_min = (size_t)((heaps_used * HEAP_OBJ_LIMIT) * 0.2); | |
@@ -2222,8 +2892,13 @@ before_gc_sweep(rb_objspace_t *objspace) | |
static void | |
after_gc_sweep(rb_objspace_t *objspace) | |
{ | |
+ int i; | |
+ struct timeval tv1; | |
+ | |
GC_PROF_SET_MALLOC_INFO; | |
+ if (gc_statistics) gettimeofday(&tv1, NULL); | |
+ | |
if (objspace->heap.free_num < objspace->heap.free_min) { | |
set_heaps_increment(objspace); | |
heaps_increment(objspace); | |
@@ -2236,6 +2911,29 @@ after_gc_sweep(rb_objspace_t *objspace) | |
malloc_increase = 0; | |
free_unused_heaps(objspace); | |
+ | |
+ if (gc_statistics) { | |
+ gc_time_accumulator += elapsed_musecs(tv1); | |
+ | |
+ if (verbose_gc_stats) { | |
+ /* log gc stats if requested */ | |
+ fprintf(gc_data_file, "GC time: %lu musec\n", (unsigned long)(gc_time_accumulator-gc_time_accumulator_before_gc)); | |
+ fprintf(gc_data_file, "objects processed: %7lu\n", (unsigned long)processed); | |
+ fprintf(gc_data_file, "live objects : %7lu\n", (unsigned long)live_after_last_mark_phase); | |
+ fprintf(gc_data_file, "freelist objects : %7lu\n", (unsigned long)freelist_size); | |
+ fprintf(gc_data_file, "freed objects : %7lu\n", (unsigned long)freed_objects); | |
+ fprintf(gc_data_file, "zombies : %7lu\n", (unsigned long)zombies); | |
+ for(i=0; i<T_MASK; i++) { | |
+ if (free_counts[i]>0 || live_counts[i]>0) { | |
+ fprintf(gc_data_file, | |
+ "kept %7lu / freed %7lu objects of type %s\n", | |
+ (unsigned long)live_counts[i], (unsigned long)free_counts[i], obj_type((int)i)); | |
+ } | |
+ } | |
+ rb_gc_dump(); | |
+ fflush(gc_data_file); | |
+ } | |
+ } | |
} | |
static int | |
@@ -2269,9 +2967,11 @@ rest_sweep(rb_objspace_t *objspace) | |
static void gc_marks(rb_objspace_t *objspace); | |
+/* only called from rb_new_obj */ | |
static int | |
gc_lazy_sweep(rb_objspace_t *objspace) | |
{ | |
+ struct timeval gctv1; | |
int res; | |
INIT_GC_PROF_PARAMS; | |
@@ -2293,7 +2993,6 @@ gc_lazy_sweep(rb_objspace_t *objspace) | |
GC_PROF_TIMER_STOP(Qfalse); | |
return res; | |
} | |
- after_gc_sweep(objspace); | |
} | |
else { | |
if (heaps_increment(objspace)) { | |
@@ -2301,6 +3000,18 @@ gc_lazy_sweep(rb_objspace_t *objspace) | |
return TRUE; | |
} | |
} | |
+ after_gc_sweep(objspace); | |
+ | |
+ if (gc_statistics) { | |
+ gc_time_accumulator_before_gc = gc_time_accumulator; | |
+ gc_collections++; | |
+ gettimeofday(&gctv1, NULL); | |
+ /* | |
+ if (verbose_gc_stats) { | |
+ fprintf(gc_data_file, "Garbage collection started (gc_lazy_sweep)\n"); | |
+ } | |
+ */ | |
+ } | |
gc_marks(objspace); | |
@@ -2309,6 +3020,10 @@ gc_lazy_sweep(rb_objspace_t *objspace) | |
set_heaps_increment(objspace); | |
} | |
+ if (gc_statistics) { | |
+ gc_time_accumulator += elapsed_musecs(gctv1); | |
+ } | |
+ | |
GC_PROF_SWEEP_TIMER_START; | |
if(!(res = lazy_sweep(objspace))) { | |
after_gc_sweep(objspace); | |
@@ -2320,6 +3035,7 @@ gc_lazy_sweep(rb_objspace_t *objspace) | |
GC_PROF_SWEEP_TIMER_STOP; | |
GC_PROF_TIMER_STOP(Qtrue); | |
+ | |
return res; | |
} | |
@@ -2546,9 +3262,15 @@ gc_marks(rb_objspace_t *objspace) | |
rb_thread_t *th = GET_THREAD(); | |
GC_PROF_MARK_TIMER_START; | |
+ /* | |
+ if (gc_statistics & verbose_gc_stats) { | |
+ fprintf(gc_data_file, "Marking objects\n"); | |
+ } | |
+ */ | |
+ | |
objspace->heap.live_num = 0; | |
objspace->count++; | |
- | |
+ live_objects = 0; | |
SET_STACK_END; | |
@@ -2580,11 +3302,15 @@ gc_marks(rb_objspace_t *objspace) | |
gc_mark_stacked_objects(objspace); | |
GC_PROF_MARK_TIMER_STOP; | |
+ | |
+ live_after_last_mark_phase = objspace->heap.live_num; | |
} | |
static int | |
garbage_collect(rb_objspace_t *objspace) | |
{ | |
+ struct timeval gctv1; | |
+ | |
INIT_GC_PROF_PARAMS; | |
if (GC_NOTIFY) printf("start garbage_collect()\n"); | |
@@ -2600,15 +3326,31 @@ garbage_collect(rb_objspace_t *objspace) | |
rest_sweep(objspace); | |
+ if (gc_statistics) { | |
+ gc_time_accumulator_before_gc = gc_time_accumulator; | |
+ gc_collections++; | |
+ gettimeofday(&gctv1, NULL); | |
+ /* | |
+ if (verbose_gc_stats) { | |
+ fprintf(gc_data_file, "Garbage collection started (garbage_collect)\n"); | |
+ } | |
+ */ | |
+ } | |
+ | |
during_gc++; | |
gc_marks(objspace); | |
+ if (gc_statistics) { | |
+ gc_time_accumulator += elapsed_musecs(gctv1); | |
+ } | |
+ | |
GC_PROF_SWEEP_TIMER_START; | |
gc_sweep(objspace); | |
GC_PROF_SWEEP_TIMER_STOP; | |
GC_PROF_TIMER_STOP(Qtrue); | |
if (GC_NOTIFY) printf("end garbage_collect()\n"); | |
+ | |
return TRUE; | |
} | |
@@ -3084,6 +3826,39 @@ rb_gc_call_finalizer_at_exit(void) | |
rb_objspace_call_finalizer(&rb_objspace); | |
} | |
+static const char* obj_type(VALUE type) | |
+{ | |
+ switch (type) { | |
+ case T_NIL : return "NIL"; | |
+ case T_OBJECT : return "OBJECT"; | |
+ case T_CLASS : return "CLASS"; | |
+ case T_ICLASS : return "ICLASS"; | |
+ case T_MODULE : return "MODULE"; | |
+ case T_FLOAT : return "FLOAT"; | |
+ case T_COMPLEX: return "COMPLEX"; | |
+ case T_RATIONAL: return "RATIONAL"; | |
+ case T_STRING : return "STRING"; | |
+ case T_REGEXP : return "REGEXP"; | |
+ case T_ARRAY : return "ARRAY"; | |
+ case T_FIXNUM : return "FIXNUM"; | |
+ case T_HASH : return "HASH"; | |
+ case T_STRUCT : return "STRUCT"; | |
+ case T_BIGNUM : return "BIGNUM"; | |
+ case T_FILE : return "FILE"; | |
+ | |
+ case T_TRUE : return "TRUE"; | |
+ case T_FALSE : return "FALSE"; | |
+ case T_DATA : return "DATA"; | |
+ case T_MATCH : return "MATCH"; | |
+ case T_SYMBOL : return "SYMBOL"; | |
+ case T_ZOMBIE : return "ZOMBIE"; | |
+ | |
+ case T_UNDEF : return "UNDEF"; | |
+ case T_NODE : return "NODE"; | |
+ default: return "____"; | |
+ } | |
+} | |
+ | |
static void | |
rb_objspace_call_finalizer(rb_objspace_t *objspace) | |
{ | |
@@ -3395,6 +4170,49 @@ count_objects(int argc, VALUE *argv, VALUE os) | |
return hash; | |
} | |
+/* call-seq: | |
+ * ObjectSpace.live_objects => number | |
+ * | |
+ * Returns the count of objects currently allocated in the system. This goes | |
+ * down after the garbage collector runs. | |
+ */ | |
+static | |
+VALUE os_live_objects(VALUE self) | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return ULONG2NUM(live_objects); | |
+} | |
+ | |
+unsigned long rb_os_live_objects() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return live_objects; | |
+} | |
+ | |
+/* call-seq: | |
+ * ObjectSpace.allocated_objects => number | |
+ * | |
+ * Returns the count of objects allocated since the Ruby interpreter has | |
+ * started. This number can only increase. To know how many objects are | |
+ * currently allocated, use ObjectSpace::live_objects | |
+ */ | |
+static | |
+VALUE os_allocated_objects(VALUE self) | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+#if defined(HAVE_LONG_LONG) | |
+ return ULL2NUM(allocated_objects); | |
+#else | |
+ return ULONG2NUM(allocated_objects); | |
+#endif | |
+} | |
+ | |
+unsigned LONG_LONG rb_os_allocated_objects() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return allocated_objects; | |
+} | |
+ | |
/* | |
* call-seq: | |
* GC.count -> Integer | |
@@ -3687,6 +4505,28 @@ Init_GC(void) | |
rb_define_singleton_method(rb_mGC, "stat", gc_stat, -1); | |
rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0); | |
+ rb_define_singleton_method(rb_mGC, "enable_stats", rb_gc_enable_stats, 0); | |
+ rb_define_singleton_method(rb_mGC, "disable_stats", rb_gc_disable_stats, 0); | |
+ rb_define_singleton_method(rb_mGC, "stats_enabled?", rb_gc_stats_enabled, 0); | |
+ rb_define_singleton_method(rb_mGC, "clear_stats", rb_gc_clear_stats, 0); | |
+ rb_define_singleton_method(rb_mGC, "allocated_size", rb_gc_allocated_size, 0); | |
+ rb_define_singleton_method(rb_mGC, "num_allocations", rb_gc_num_allocations, 0); | |
+ rb_define_singleton_method(rb_mGC, "heap_slots", rb_gc_heap_slots, 0); | |
+ rb_define_const(rb_mGC, "HEAP_SLOT_SIZE", INT2FIX(sizeof(RVALUE))); | |
+ | |
+ rb_define_singleton_method(rb_mGC, "log", rb_gc_log, 1); | |
+ rb_define_singleton_method(rb_mGC, "log_file", rb_gc_log_file, -1); | |
+ rb_define_singleton_method(rb_mGC, "enable_trace", rb_gc_enable_trace, 0); | |
+ rb_define_singleton_method(rb_mGC, "disable_trace", rb_gc_disable_trace, 0); | |
+ rb_define_singleton_method(rb_mGC, "trace_enabled?", rb_gc_trace_enabled, 0); | |
+ | |
+ rb_define_singleton_method(rb_mGC, "collections", rb_gc_collections, 0); | |
+ rb_define_singleton_method(rb_mGC, "time", rb_gc_time, 0); | |
+ rb_define_singleton_method(rb_mGC, "dump", rb_gc_dump, 0); | |
+#ifdef GC_DEBUG | |
+ rb_define_singleton_method(rb_mGC, "dump_file_and_line_info", rb_gc_dump_file_and_line_info, -1); | |
+#endif | |
+ | |
rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler"); | |
rb_define_singleton_method(rb_mProfiler, "enabled?", gc_profile_enable_get, 0); | |
rb_define_singleton_method(rb_mProfiler, "enable", gc_profile_enable, 0); | |
@@ -3700,6 +4540,9 @@ Init_GC(void) | |
rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); | |
rb_define_module_function(rb_mObSpace, "garbage_collect", rb_gc_start, 0); | |
+ rb_define_module_function(rb_mObSpace, "live_objects", os_live_objects, 0); | |
+ rb_define_module_function(rb_mObSpace, "allocated_objects", os_allocated_objects, 0); | |
+ | |
rb_define_module_function(rb_mObSpace, "define_finalizer", define_final, -1); | |
rb_define_module_function(rb_mObSpace, "undefine_finalizer", undefine_final, 1); | |
diff --git a/eval_error.c b/eval_error.c | |
index fd06adf..69c3b48 100644 | |
--- a/eval_error.c | |
+++ b/eval_error.c | |
@@ -164,8 +164,8 @@ error_print(void) | |
int skip = eclass == rb_eSysStackError; | |
#define TRACE_MAX (TRACE_HEAD+TRACE_TAIL+5) | |
-#define TRACE_HEAD 8 | |
-#define TRACE_TAIL 5 | |
+#define TRACE_HEAD 100 | |
+#define TRACE_TAIL 100 | |
for (i = 1; i < len; i++) { | |
if (TYPE(ptr[i]) == T_STRING) { | |
diff --git a/gc.c b/gc.c | |
index 5f1d469..dadb72d 100644 | |
--- a/gc.c | |
+++ b/gc.c | |
@@ -1347,6 +1347,34 @@ rb_gc_log_file(int argc, VALUE *argv, VALUE self) | |
} | |
/* | |
+ * Called from process.c before a fork. Flushes the gc log file to | |
+ * avoid writing the buffered output twice (once in the parent, and | |
+ * once in the child). | |
+ */ | |
+void | |
+rb_gc_before_fork() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ fflush(gc_data_file); | |
+} | |
+ | |
+/* | |
+ * Called from process.c after a fork in the child process. Turns off | |
+ * logging, disables GC stats and resets all gc counters and timing | |
+ * information. | |
+ */ | |
+void | |
+rb_gc_after_fork() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ rb_gc_disable_stats(); | |
+ rb_gc_clear_stats(); | |
+ rb_gc_disable_trace(); | |
+ gc_data_file = stderr; | |
+ rb_iv_set(rb_mGC, GC_LOGFILE_IVAR, Qnil); | |
+} | |
+ | |
+/* | |
* call-seq: | |
* GC.log String => String | |
* | |
diff --git a/include/ruby/intern.h b/include/ruby/intern.h | |
index 7f1b078..41497a0 100644 | |
--- a/include/ruby/intern.h | |
+++ b/include/ruby/intern.h | |
@@ -443,6 +443,8 @@ void rb_gc_call_finalizer_at_exit(void); | |
VALUE rb_gc_enable(void); | |
VALUE rb_gc_disable(void); | |
VALUE rb_gc_start(void); | |
+void rb_gc_before_fork _((void)); | |
+void rb_gc_after_fork _((void)); | |
#define Init_stack(addr) ruby_init_stack(addr) | |
void rb_gc_set_params(void); | |
/* hash.c */ | |
diff --git a/process.c b/process.c | |
index 0c19d58..1599535 100644 | |
--- a/process.c | |
+++ b/process.c | |
@@ -2814,9 +2814,11 @@ rb_f_fork(VALUE obj) | |
rb_pid_t pid; | |
rb_secure(2); | |
+ rb_gc_before_fork(); | |
switch (pid = rb_fork(0, 0, 0, Qnil)) { | |
case 0: | |
+ rb_gc_after_fork(); | |
rb_thread_atfork(); | |
if (rb_block_given_p()) { | |
int status; | |
diff --git a/gc.c b/gc.c | |
index dadb72d..93b2557 100644 | |
--- a/gc.c | |
+++ b/gc.c | |
@@ -281,7 +281,6 @@ getrusage_time(void) | |
#define GC_PROF_DEC_LIVE_NUM | |
#endif | |
- | |
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) | |
#pragma pack(push, 1) /* magic for reducing sizeof(RVALUE): 24 -> 20 */ | |
#endif | |
@@ -1564,6 +1563,24 @@ rb_gc_time() | |
#endif | |
} | |
+/* | |
+ * call-seq: | |
+ * GC.heap_slots_live_after_last_gc => Integer | |
+ * | |
+ * Returns the number of heap slots which were live after the last garbage collection. | |
+ * | |
+ * GC.heap_slots_live_after_last_gc #=> 231223 | |
+ * | |
+ */ | |
+VALUE | |
+rb_gc_heap_slots_live_after_last_gc() | |
+{ | |
+ rb_objspace_t *objspace = &rb_objspace; | |
+ return ULONG2NUM(live_after_last_mark_phase); | |
+} | |
+ | |
+ | |
+ | |
VALUE rb_mGC; | |
void | |
@@ -4540,6 +4557,7 @@ Init_GC(void) | |
rb_define_singleton_method(rb_mGC, "allocated_size", rb_gc_allocated_size, 0); | |
rb_define_singleton_method(rb_mGC, "num_allocations", rb_gc_num_allocations, 0); | |
rb_define_singleton_method(rb_mGC, "heap_slots", rb_gc_heap_slots, 0); | |
+ rb_define_singleton_method(rb_mGC, "heap_slots_live_after_last_gc", rb_gc_heap_slots_live_after_last_gc, 0); | |
rb_define_const(rb_mGC, "HEAP_SLOT_SIZE", INT2FIX(sizeof(RVALUE))); | |
rb_define_singleton_method(rb_mGC, "log", rb_gc_log, 1); | |
diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb | |
index 0d36c07..ce72b3f 100644 | |
--- a/lib/webrick/httpresponse.rb | |
+++ b/lib/webrick/httpresponse.rb | |
@@ -202,7 +202,7 @@ module WEBrick | |
if @header['connection'] == "close" | |
@keep_alive = false | |
elsif keep_alive? | |
- if chunked? || @header['content-length'] | |
+ if chunked? || @header['content-length'] || @status == 304 || @status == 204 | |
@header['connection'] = "Keep-Alive" | |
else | |
msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true" | |
diff --git a/gc.c b/gc.c | |
index 93b2557..3eab6fb 100644 | |
--- a/gc.c | |
+++ b/gc.c | |
@@ -1054,6 +1054,7 @@ ruby_xfree(void *x) | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_enable(void) | |
{ | |
@@ -1076,6 +1077,7 @@ rb_gc_enable(void) | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_disable(void) | |
{ | |
@@ -1098,6 +1100,7 @@ rb_gc_disable(void) | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_enable_stats() | |
{ | |
@@ -1119,6 +1122,7 @@ rb_gc_enable_stats() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_disable_stats() | |
{ | |
@@ -1138,6 +1142,7 @@ rb_gc_disable_stats() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_stats_enabled() | |
{ | |
@@ -1157,6 +1162,7 @@ rb_gc_stats_enabled() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_clear_stats() | |
{ | |
@@ -1180,6 +1186,7 @@ rb_gc_clear_stats() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_allocated_size() | |
{ | |
@@ -1224,6 +1231,7 @@ rb_gc_num_allocations() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_enable_trace() | |
{ | |
@@ -1245,6 +1253,7 @@ rb_gc_enable_trace() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_disable_trace() | |
{ | |
@@ -1264,6 +1273,7 @@ rb_gc_disable_trace() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_trace_enabled() | |
{ | |
@@ -1292,6 +1302,7 @@ const char* GC_LOGFILE_IVAR = "@gc_logfile_name"; | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_log_file(int argc, VALUE *argv, VALUE self) | |
{ | |
@@ -1383,6 +1394,7 @@ rb_gc_after_fork() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_log(self, original_str) | |
VALUE self, original_str; | |
@@ -1409,6 +1421,7 @@ rb_gc_log(self, original_str) | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_dump() | |
{ | |
@@ -1439,6 +1452,7 @@ static const char* obj_type(VALUE tp); | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_dump_file_and_line_info(int argc, VALUE *argv) | |
{ | |
@@ -1534,6 +1548,7 @@ rb_gc_heap_slots() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_collections() | |
{ | |
@@ -1552,6 +1567,7 @@ rb_gc_collections() | |
* | |
*/ | |
+RUBY_FUNC_EXPORTED | |
VALUE | |
rb_gc_time() | |
{ | |
@@ -4252,6 +4268,7 @@ VALUE os_allocated_objects(VALUE self) | |
#endif | |
} | |
+RUBY_FUNC_EXPORTED | |
unsigned LONG_LONG rb_os_allocated_objects() | |
{ | |
rb_objspace_t *objspace = &rb_objspace; | |
diff --git a/NEWS b/NEWS | |
index 30fec33..9f6c172 100644 | |
--- a/NEWS | |
+++ b/NEWS | |
@@ -103,6 +103,16 @@ with all sufficient information, see the ChangeLog file. | |
* String#prepend | |
* String#byteslice | |
+ * Thread | |
+ * added method: | |
+ * added Thread#thread_variable_get for getting thread local variables | |
+ (these are different than Fiber local variables). | |
+ * added Thread#thread_variable_set for setting thread local variables. | |
+ * added Thread#thread_variables for getting a list of the thread local | |
+ variable keys. | |
+ * added Thread#thread_variable? for testing to see if a particular thread | |
+ variable has been set. | |
+ | |
* Time | |
* extended method: | |
* Time#strftime supports %:z and %::z. | |
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb | |
index 4d99053..de73c5a 100644 | |
--- a/test/ruby/test_thread.rb | |
+++ b/test/ruby/test_thread.rb | |
@@ -27,6 +27,79 @@ class TestThread < Test::Unit::TestCase | |
end | |
end | |
+ def test_main_thread_variable_in_enumerator | |
+ assert_equal Thread.main, Thread.current | |
+ | |
+ Thread.current.thread_variable_set :foo, "bar" | |
+ | |
+ thread, value = Fiber.new { | |
+ Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] | |
+ }.resume | |
+ | |
+ assert_equal Thread.current, thread | |
+ assert_equal Thread.current.thread_variable_get(:foo), value | |
+ end | |
+ | |
+ def test_thread_variable_in_enumerator | |
+ Thread.new { | |
+ Thread.current.thread_variable_set :foo, "bar" | |
+ | |
+ thread, value = Fiber.new { | |
+ Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] | |
+ }.resume | |
+ | |
+ assert_equal Thread.current, thread | |
+ assert_equal Thread.current.thread_variable_get(:foo), value | |
+ }.join | |
+ end | |
+ | |
+ def test_thread_variables | |
+ assert_equal [], Thread.new { Thread.current.thread_variables }.join.value | |
+ | |
+ t = Thread.new { | |
+ Thread.current.thread_variable_set(:foo, "bar") | |
+ Thread.current.thread_variables | |
+ } | |
+ assert_equal [:foo], t.join.value | |
+ end | |
+ | |
+ def test_thread_variable? | |
+ refute Thread.new { Thread.current.thread_variable?("foo") }.join.value | |
+ t = Thread.new { | |
+ Thread.current.thread_variable_set("foo", "bar") | |
+ }.join | |
+ | |
+ assert t.thread_variable?("foo") | |
+ assert t.thread_variable?(:foo) | |
+ refute t.thread_variable?(:bar) | |
+ end | |
+ | |
+ def test_thread_variable_strings_and_symbols_are_the_same_key | |
+ t = Thread.new {}.join | |
+ t.thread_variable_set("foo", "bar") | |
+ assert_equal "bar", t.thread_variable_get(:foo) | |
+ end | |
+ | |
+ def test_thread_variable_frozen | |
+ t = Thread.new { }.join | |
+ t.freeze | |
+ assert_raises(RuntimeError) do | |
+ t.thread_variable_set(:foo, "bar") | |
+ end | |
+ end | |
+ | |
+ def test_thread_variable_security | |
+ t = Thread.new { sleep } | |
+ | |
+ assert_raises(SecurityError) do | |
+ Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join | |
+ end | |
+ | |
+ assert_raises(SecurityError) do | |
+ Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join | |
+ end | |
+ end | |
+ | |
def test_mutex_synchronize | |
m = Mutex.new | |
r = 0 | |
diff --git a/thread.c b/thread.c | |
index ba6a940..38106eb 100644 | |
--- a/thread.c | |
+++ b/thread.c | |
@@ -2127,7 +2127,9 @@ rb_thread_local_aset(VALUE thread, ID id, VALUE val) | |
* thr[sym] = obj -> obj | |
* | |
* Attribute Assignment---Sets or creates the value of a thread-local variable, | |
- * using either a symbol or a string. See also <code>Thread#[]</code>. | |
+ * using either a symbol or a string. See also <code>Thread#[]</code>. For | |
+ * thread-local variables, please see <code>Thread#thread_variable_set</code> | |
+ * and <code>Thread#thread_variable_get</code>. | |
*/ | |
static VALUE | |
@@ -2138,6 +2140,80 @@ rb_thread_aset(VALUE self, VALUE id, VALUE val) | |
/* | |
* call-seq: | |
+ * thr.thread_variable_get(key) -> obj or nil | |
+ * | |
+ * Returns the value of a thread local variable that has been set. Note that | |
+ * these are different than fiber local values. For fiber local values, | |
+ * please see Thread#[] and Thread#[]=. | |
+ * | |
+ * Thread local values are carried along with threads, and do not respect | |
+ * fibers. For example: | |
+ * | |
+ * Thread.new { | |
+ * Thread.current.thread_variable_set("foo", "bar") # set a thread local | |
+ * Thread.current["foo"] = "bar" # set a fiber local | |
+ * | |
+ * Fiber.new { | |
+ * Fiber.yield [ | |
+ * Thread.current.thread_variable_get("foo"), # get the thread local | |
+ * Thread.current["foo"], # get the fiber local | |
+ * ] | |
+ * }.resume | |
+ * }.join.value # => ['bar', nil] | |
+ * | |
+ * The value "bar" is returned for the thread local, where nil is returned | |
+ * for the fiber local. The fiber is executed in the same thread, so the | |
+ * thread local values are available. | |
+ * | |
+ * See also Thread#[] | |
+ */ | |
+ | |
+static VALUE | |
+rb_thread_variable_get(VALUE thread, VALUE id) | |
+{ | |
+ VALUE locals; | |
+ rb_thread_t *th; | |
+ | |
+ GetThreadPtr(thread, th); | |
+ | |
+ if (rb_safe_level() >= 4 && th != GET_THREAD()) { | |
+ rb_raise(rb_eSecurityError, "Insecure: can't access thread locals"); | |
+ } | |
+ | |
+ locals = rb_iv_get(thread, "locals"); | |
+ return rb_hash_aref(locals, ID2SYM(rb_to_id(id))); | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * thr.thread_variable_set(key, value) | |
+ * | |
+ * Sets a thread local with +key+ to +value+. Note that these are local to | |
+ * threads, and not to fibers. Please see Thread#thread_variable_get and | |
+ * Thread#[] for more information. | |
+ */ | |
+ | |
+static VALUE | |
+rb_thread_variable_set(VALUE thread, VALUE id, VALUE val) | |
+{ | |
+ VALUE locals; | |
+ rb_thread_t *th; | |
+ | |
+ GetThreadPtr(thread, th); | |
+ | |
+ if (rb_safe_level() >= 4 && th != GET_THREAD()) { | |
+ rb_raise(rb_eSecurityError, "Insecure: can't modify thread locals"); | |
+ } | |
+ if (OBJ_FROZEN(thread)) { | |
+ rb_error_frozen("thread locals"); | |
+ } | |
+ | |
+ locals = rb_iv_get(thread, "locals"); | |
+ return rb_hash_aset(locals, ID2SYM(rb_to_id(id)), val); | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
* thr.key?(sym) -> true or false | |
* | |
* Returns <code>true</code> if the given string (or symbol) exists as a | |
@@ -3008,6 +3084,9 @@ rb_gc_save_machine_context(rb_thread_t *th) | |
/* | |
* | |
+ * For thread-local variables, please see <code>Thread#thread_local_get</code> | |
+ * and <code>Thread#thread_local_set</code>. | |
+ * | |
*/ | |
void | |
@@ -3208,6 +3287,76 @@ thgroup_list_i(st_data_t key, st_data_t val, st_data_t data) | |
return ST_CONTINUE; | |
} | |
+static int | |
+keys_i(VALUE key, VALUE value, VALUE ary) | |
+{ | |
+ rb_ary_push(ary, key); | |
+ return ST_CONTINUE; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * thr.thread_variables -> array | |
+ * | |
+ * Returns an an array of the names of the thread-local variables (as Symbols). | |
+ * | |
+ * thr = Thread.new do | |
+ * Thread.current.thread_variable_set(:cat, 'meow') | |
+ * Thread.current.thread_variable_set("dog", 'woof') | |
+ * end | |
+ * thr.join #=> #<Thread:0x401b3f10 dead> | |
+ * thr.thread_variables #=> [:dog, :cat] | |
+ * | |
+ * Note that these are not fiber local variables. Please see Thread#[] and | |
+ * Thread#thread_variable_get for more details. | |
+ */ | |
+ | |
+static VALUE | |
+rb_thread_variables(VALUE thread) | |
+{ | |
+ VALUE locals; | |
+ VALUE ary; | |
+ | |
+ locals = rb_iv_get(thread, "locals"); | |
+ ary = rb_ary_new(); | |
+ rb_hash_foreach(locals, keys_i, ary); | |
+ | |
+ return ary; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * thr.thread_variable?(key) -> true or false | |
+ * | |
+ * Returns <code>true</code> if the given string (or symbol) exists as a | |
+ * thread-local variable. | |
+ * | |
+ * me = Thread.current | |
+ * me.thread_variable_set(:oliver, "a") | |
+ * me.thread_variable?(:oliver) #=> true | |
+ * me.thread_variable?(:stanley) #=> false | |
+ * | |
+ * Note that these are not fiber local variables. Please see Thread#[] and | |
+ * Thread#thread_variable_get for more details. | |
+ */ | |
+ | |
+static VALUE | |
+rb_thread_variable_p(VALUE thread, VALUE key) | |
+{ | |
+ VALUE locals; | |
+ | |
+ locals = rb_iv_get(thread, "locals"); | |
+ | |
+ if (!RHASH(locals)->ntbl) | |
+ return Qfalse; | |
+ | |
+ if (st_lookup(RHASH(locals)->ntbl, ID2SYM(rb_to_id(key)), 0)) { | |
+ return Qtrue; | |
+ } | |
+ | |
+ return Qfalse; | |
+} | |
+ | |
/* | |
* call-seq: | |
* thgrp.list -> array | |
@@ -4725,6 +4874,10 @@ Init_Thread(void) | |
rb_define_method(rb_cThread, "priority", rb_thread_priority, 0); | |
rb_define_method(rb_cThread, "priority=", rb_thread_priority_set, 1); | |
rb_define_method(rb_cThread, "status", rb_thread_status, 0); | |
+ rb_define_method(rb_cThread, "thread_variable_get", rb_thread_variable_get, 1); | |
+ rb_define_method(rb_cThread, "thread_variable_set", rb_thread_variable_set, 2); | |
+ rb_define_method(rb_cThread, "thread_variables", rb_thread_variables, 0); | |
+ rb_define_method(rb_cThread, "thread_variable?", rb_thread_variable_p, 1); | |
rb_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0); | |
rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0); | |
rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0); | |
diff --git a/vm.c b/vm.c | |
index ab614c3..ae0286f 100644 | |
--- a/vm.c | |
+++ b/vm.c | |
@@ -1914,6 +1914,7 @@ ruby_thread_init(VALUE self) | |
GetThreadPtr(self, th); | |
th_init(th, self); | |
+ rb_iv_set(self, "locals", rb_hash_new()); | |
th->vm = vm; | |
th->top_wrapper = 0; | |
@@ -2184,6 +2185,7 @@ Init_VM(void) | |
/* create main thread */ | |
th_self = th->self = TypedData_Wrap_Struct(rb_cThread, &thread_data_type, th); | |
+ rb_iv_set(th_self, "locals", rb_hash_new()); | |
vm->main_thread = th; | |
vm->running_thread = th; | |
th->vm = vm; | |
diff --git a/array.c b/array.c | |
index 73f7669..78c003a 100644 | |
--- a/array.c | |
+++ b/array.c | |
@@ -295,6 +295,22 @@ rb_ary_frozen_p(VALUE ary) | |
return Qfalse; | |
} | |
+/* This can be used to take a snapshot of an array (with | |
+ e.g. rb_ary_replace) and check later whether the array has been | |
+ modified from the snapshot. The snapshot is cheap, though if | |
+ something does modify the array it will pay the cost of copying | |
+ it. */ | |
+VALUE | |
+rb_ary_shared_with_p(VALUE ary1, VALUE ary2) | |
+{ | |
+ if (!ARY_EMBED_P(ary1) && ARY_SHARED_P(ary1) | |
+ && !ARY_EMBED_P(ary2) && ARY_SHARED_P(ary2) | |
+ && RARRAY(ary1)->as.heap.aux.shared == RARRAY(ary2)->as.heap.aux.shared) { | |
+ return Qtrue; | |
+ } | |
+ return Qfalse; | |
+} | |
+ | |
static VALUE | |
ary_alloc(VALUE klass) | |
{ | |
diff --git a/file.c b/file.c | |
index e7e0885..257d6f3 100644 | |
--- a/file.c | |
+++ b/file.c | |
@@ -153,23 +153,32 @@ file_path_convert(VALUE name) | |
return name; | |
} | |
-static VALUE | |
-rb_get_path_check(VALUE obj, int level) | |
+VALUE | |
+rb_get_path_check_to_string(VALUE obj, int level) | |
{ | |
VALUE tmp; | |
ID to_path; | |
- rb_encoding *enc; | |
if (insecure_obj_p(obj, level)) { | |
rb_insecure_operation(); | |
} | |
+ if (RB_TYPE_P(obj, T_STRING)) { | |
+ return obj; | |
+ } | |
CONST_ID(to_path, "to_path"); | |
tmp = rb_check_funcall(obj, to_path, 0, 0); | |
if (tmp == Qundef) { | |
tmp = obj; | |
} | |
StringValue(tmp); | |
+ return tmp; | |
+} | |
+ | |
+VALUE | |
+rb_get_path_check_convert(VALUE obj, VALUE tmp, int level) | |
+{ | |
+ rb_encoding *enc; | |
tmp = file_path_convert(tmp); | |
if (obj != tmp && insecure_obj_p(tmp, level)) { | |
@@ -187,6 +196,13 @@ rb_get_path_check(VALUE obj, int level) | |
return rb_str_new4(tmp); | |
} | |
+static VALUE | |
+rb_get_path_check(VALUE obj, int level) | |
+{ | |
+ VALUE tmp = rb_get_path_check_to_string(obj, level); | |
+ return rb_get_path_check_convert(obj, tmp, level); | |
+} | |
+ | |
VALUE | |
rb_get_path_no_checksafe(VALUE obj) | |
{ | |
@@ -3282,7 +3298,6 @@ rb_file_expand_path(VALUE fname, VALUE dname) | |
VALUE | |
rb_file_expand_path_fast(VALUE fname, VALUE dname) | |
{ | |
- check_expand_path_args(fname, dname); | |
return rb_file_expand_path_internal(fname, dname, 0, 0, EXPAND_PATH_BUFFER()); | |
} | |
@@ -5287,7 +5302,7 @@ rb_find_file_ext_safe(VALUE *filep, const char *const *ext, int safe_level) | |
rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f); | |
} | |
- RB_GC_GUARD(load_path) = rb_get_load_path(); | |
+ RB_GC_GUARD(load_path) = rb_get_expanded_load_path(); | |
if (!load_path) return 0; | |
fname = rb_str_dup(*filep); | |
@@ -5352,7 +5367,7 @@ rb_find_file_safe(VALUE path, int safe_level) | |
rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f); | |
} | |
- RB_GC_GUARD(load_path) = rb_get_load_path(); | |
+ RB_GC_GUARD(load_path) = rb_get_expanded_load_path(); | |
if (load_path) { | |
long i; | |
diff --git a/hash.c b/hash.c | |
index 3111b25..dcc2cdc 100644 | |
--- a/hash.c | |
+++ b/hash.c | |
@@ -1089,7 +1089,7 @@ clear_i(VALUE key, VALUE value, VALUE dummy) | |
* | |
*/ | |
-static VALUE | |
+VALUE | |
rb_hash_clear(VALUE hash) | |
{ | |
rb_hash_modify_check(hash); | |
diff --git a/include/ruby/intern.h b/include/ruby/intern.h | |
index 41497a0..9601c4d 100644 | |
--- a/include/ruby/intern.h | |
+++ b/include/ruby/intern.h | |
@@ -65,6 +65,7 @@ VALUE rb_ary_tmp_new(long); | |
void rb_ary_free(VALUE); | |
void rb_ary_modify(VALUE); | |
VALUE rb_ary_freeze(VALUE); | |
+VALUE rb_ary_shared_with_p(VALUE, VALUE); | |
VALUE rb_ary_aref(int, VALUE*, VALUE); | |
VALUE rb_ary_subseq(VALUE, long, long); | |
void rb_ary_store(VALUE, long, VALUE); | |
@@ -460,6 +461,7 @@ VALUE rb_hash_lookup(VALUE, VALUE); | |
VALUE rb_hash_lookup2(VALUE, VALUE, VALUE); | |
VALUE rb_hash_fetch(VALUE, VALUE); | |
VALUE rb_hash_aset(VALUE, VALUE, VALUE); | |
+VALUE rb_hash_clear(VALUE); | |
VALUE rb_hash_delete_if(VALUE); | |
VALUE rb_hash_delete(VALUE,VALUE); | |
typedef VALUE rb_hash_update_func(VALUE newkey, VALUE oldkey, VALUE value); | |
diff --git a/internal.h b/internal.h | |
index 6afea9c..513b412 100644 | |
--- a/internal.h | |
+++ b/internal.h | |
@@ -94,6 +94,8 @@ VALUE rb_home_dir(const char *user, VALUE result); | |
VALUE rb_realpath_internal(VALUE basedir, VALUE path, int strict); | |
VALUE rb_file_expand_path_fast(VALUE, VALUE); | |
VALUE rb_file_expand_path_internal(VALUE, VALUE, int, int, VALUE); | |
+VALUE rb_get_path_check_to_string(VALUE, int); | |
+VALUE rb_get_path_check_convert(VALUE, VALUE, int); | |
void Init_File(void); | |
#ifdef _WIN32 | |
@@ -119,6 +121,7 @@ VALUE rb_iseq_clone(VALUE iseqval, VALUE newcbase); | |
/* load.c */ | |
VALUE rb_get_load_path(void); | |
+VALUE rb_get_expanded_load_path(void); | |
/* math.c */ | |
VALUE rb_math_atan2(VALUE, VALUE); | |
diff --git a/load.c b/load.c | |
index 163ec4c..e766880 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -18,7 +18,6 @@ VALUE ruby_dln_librefs; | |
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) | |
#endif | |
- | |
static const char *const loadable_ext[] = { | |
".rb", DLEXT, | |
#ifdef DLEXT2 | |
@@ -34,21 +33,120 @@ rb_get_load_path(void) | |
return load_path; | |
} | |
-VALUE | |
-rb_get_expanded_load_path(void) | |
+enum expand_type { | |
+ EXPAND_ALL, | |
+ EXPAND_RELATIVE, | |
+ EXPAND_HOME, | |
+ EXPAND_NON_CACHE | |
+}; | |
+ | |
+/* Construct expanded load path and store it to cache. | |
+ We rebuild load path partially if the cache is invalid. | |
+ We don't cache non string object and expand it every times. We ensure that | |
+ string objects in $LOAD_PATH are frozen. | |
+ */ | |
+static void | |
+rb_construct_expanded_load_path(int type, int *has_relative, int *has_non_cache) | |
{ | |
- VALUE load_path = rb_get_load_path(); | |
+ rb_vm_t *vm = GET_VM(); | |
+ VALUE load_path = vm->load_path; | |
+ VALUE expanded_load_path = vm->expanded_load_path; | |
VALUE ary; | |
long i; | |
+ int level = rb_safe_level(); | |
ary = rb_ary_new2(RARRAY_LEN(load_path)); | |
for (i = 0; i < RARRAY_LEN(load_path); ++i) { | |
- VALUE path = rb_file_expand_path_fast(RARRAY_PTR(load_path)[i], Qnil); | |
- rb_str_freeze(path); | |
- rb_ary_push(ary, path); | |
+ VALUE path, as_str, expanded_path; | |
+ int is_string, non_cache; | |
+ char *as_cstr; | |
+ as_str = path = RARRAY_PTR(load_path)[i]; | |
+ is_string = RB_TYPE_P(path, T_STRING) ? 1 : 0; | |
+ non_cache = !is_string ? 1 : 0; | |
+ as_str = rb_get_path_check_to_string(path, level); | |
+ as_cstr = RSTRING_PTR(as_str); | |
+ | |
+ if (!non_cache) { | |
+ if ((type == EXPAND_RELATIVE && | |
+ rb_is_absolute_path(as_cstr)) || | |
+ (type == EXPAND_HOME && | |
+ (!as_cstr[0] || as_cstr[0] != '~')) || | |
+ (type == EXPAND_NON_CACHE)) { | |
+ /* Use cached expanded path. */ | |
+ rb_ary_push(ary, RARRAY_PTR(expanded_load_path)[i]); | |
+ continue; | |
+ } | |
+ } | |
+ if (!*has_relative && !rb_is_absolute_path(as_cstr)) | |
+ *has_relative = 1; | |
+ if (!*has_non_cache && non_cache) | |
+ *has_non_cache = 1; | |
+ /* Freeze only string object. We expand other objects every times. */ | |
+ if (is_string) | |
+ rb_str_freeze(path); | |
+ as_str = rb_get_path_check_convert(path, as_str, level); | |
+ expanded_path = rb_file_expand_path_fast(as_str, Qnil); | |
+ rb_str_freeze(expanded_path); | |
+ rb_ary_push(ary, expanded_path); | |
} | |
rb_obj_freeze(ary); | |
- return ary; | |
+ vm->expanded_load_path = ary; | |
+ rb_ary_replace(vm->load_path_snapshot, vm->load_path); | |
+} | |
+ | |
+static VALUE | |
+load_path_getcwd(void) | |
+{ | |
+ char *cwd = my_getcwd(); | |
+ VALUE cwd_str = rb_filesystem_str_new_cstr(cwd); | |
+ xfree(cwd); | |
+ return cwd_str; | |
+} | |
+ | |
+VALUE | |
+rb_get_expanded_load_path(void) | |
+{ | |
+ rb_vm_t *vm = GET_VM(); | |
+ const VALUE non_cache = Qtrue; | |
+ | |
+ if (!rb_ary_shared_with_p(vm->load_path_snapshot, vm->load_path)) { | |
+ /* The load path was modified. Rebuild the expanded load path. */ | |
+ int has_relative = 0, has_non_cache = 0; | |
+ rb_construct_expanded_load_path(EXPAND_ALL, &has_relative, &has_non_cache); | |
+ if (has_relative) { | |
+ vm->load_path_check_cache = load_path_getcwd(); | |
+ } | |
+ else if (has_non_cache) { | |
+ /* Non string object. */ | |
+ vm->load_path_check_cache = non_cache; | |
+ } | |
+ else { | |
+ vm->load_path_check_cache = 0; | |
+ } | |
+ } | |
+ else if (vm->load_path_check_cache == non_cache) { | |
+ int has_relative = 1, has_non_cache = 1; | |
+ /* Expand only non-cacheable objects. */ | |
+ rb_construct_expanded_load_path(EXPAND_NON_CACHE, | |
+ &has_relative, &has_non_cache); | |
+ } | |
+ else if (vm->load_path_check_cache) { | |
+ int has_relative = 1, has_non_cache = 1; | |
+ VALUE cwd = load_path_getcwd(); | |
+ if (!rb_str_equal(vm->load_path_check_cache, cwd)) { | |
+ /* Current working directory or filesystem encoding was changed. | |
+ Expand relative load path and non-cacheable objects again. */ | |
+ vm->load_path_check_cache = cwd; | |
+ rb_construct_expanded_load_path(EXPAND_RELATIVE, | |
+ &has_relative, &has_non_cache); | |
+ } | |
+ else { | |
+ /* Expand only tilde (User HOME) and non-cacheable objects. */ | |
+ rb_construct_expanded_load_path(EXPAND_HOME, | |
+ &has_relative, &has_non_cache); | |
+ } | |
+ } | |
+ return vm->expanded_load_path; | |
} | |
static VALUE | |
@@ -63,12 +161,121 @@ get_loaded_features(void) | |
return GET_VM()->loaded_features; | |
} | |
+static void | |
+reset_loaded_features_snapshot(void) | |
+{ | |
+ rb_vm_t *vm = GET_VM(); | |
+ rb_ary_replace(vm->loaded_features_snapshot, vm->loaded_features); | |
+} | |
+ | |
+static VALUE | |
+get_loaded_features_index_raw(void) | |
+{ | |
+ return GET_VM()->loaded_features_index; | |
+} | |
+ | |
static st_table * | |
get_loading_table(void) | |
{ | |
return GET_VM()->loading_table; | |
} | |
+static void | |
+features_index_add_single(VALUE short_feature, VALUE offset) | |
+{ | |
+ VALUE features_index, this_feature_index; | |
+ features_index = get_loaded_features_index_raw(); | |
+ if ((this_feature_index = rb_hash_lookup(features_index, short_feature)) == Qnil) { | |
+ this_feature_index = rb_ary_new(); | |
+ rb_hash_aset(features_index, short_feature, this_feature_index); | |
+ } | |
+ rb_ary_push(this_feature_index, offset); | |
+} | |
+ | |
+/* Add to the loaded-features index all the required entries for | |
+ `feature`, located at `offset` in $LOADED_FEATURES. We add an | |
+ index entry at each string `short_feature` for which | |
+ feature == "#{prefix}#{short_feature}#{e}" | |
+ where `e` is empty or matches %r{^\.[^./]*$}, and `prefix` is empty | |
+ or ends in '/'. This maintains the invariant that `rb_feature_p()` | |
+ relies on for its fast lookup. | |
+*/ | |
+static void | |
+features_index_add(VALUE feature, VALUE offset) | |
+{ | |
+ VALUE short_feature; | |
+ const char *feature_str, *feature_end, *ext, *p; | |
+ | |
+ feature_str = StringValuePtr(feature); | |
+ feature_end = feature_str + RSTRING_LEN(feature); | |
+ | |
+ for (ext = feature_end; ext > feature_str; ext--) | |
+ if (*ext == '.' || *ext == '/') | |
+ break; | |
+ if (*ext != '.') | |
+ ext = NULL; | |
+ /* Now `ext` points to the only string matching %r{^\.[^./]*$} that is | |
+ at the end of `feature`, or is NULL if there is no such string. */ | |
+ | |
+ p = ext ? ext : feature_end; | |
+ while (1) { | |
+ p--; | |
+ while (p >= feature_str && *p != '/') | |
+ p--; | |
+ if (p < feature_str) | |
+ break; | |
+ /* Now *p == '/'. We reach this point for every '/' in `feature`. */ | |
+ short_feature = rb_str_substr(feature, p + 1 - feature_str, feature_end - p - 1); | |
+ features_index_add_single(short_feature, offset); | |
+ if (ext) { | |
+ short_feature = rb_str_substr(feature, p + 1 - feature_str, ext - p - 1); | |
+ features_index_add_single(short_feature, offset); | |
+ } | |
+ } | |
+ features_index_add_single(feature, offset); | |
+ if (ext) { | |
+ short_feature = rb_str_substr(feature, 0, ext - feature_str); | |
+ features_index_add_single(short_feature, offset); | |
+ } | |
+} | |
+ | |
+static VALUE | |
+get_loaded_features_index(void) | |
+{ | |
+ VALUE features; | |
+ int i; | |
+ rb_vm_t *vm = GET_VM(); | |
+ | |
+ if (!rb_ary_shared_with_p(vm->loaded_features_snapshot, vm->loaded_features)) { | |
+ /* The sharing was broken; something (other than us in rb_provide_feature()) | |
+ modified loaded_features. Rebuild the index. */ | |
+ rb_hash_clear(vm->loaded_features_index); | |
+ features = vm->loaded_features; | |
+ for (i = 0; i < RARRAY_LEN(features); i++) { | |
+ VALUE entry, as_str; | |
+ as_str = entry = rb_ary_entry(features, i); | |
+ StringValue(as_str); | |
+ if (as_str != entry) | |
+ rb_ary_store(features, i, as_str); | |
+ rb_str_freeze(as_str); | |
+ features_index_add(as_str, INT2FIX(i)); | |
+ } | |
+ reset_loaded_features_snapshot(); | |
+ } | |
+ return vm->loaded_features_index; | |
+} | |
+ | |
+/* This searches `load_path` for a value such that | |
+ name == "#{load_path[i]}/#{feature}" | |
+ if `feature` is a suffix of `name`, or otherwise | |
+ name == "#{load_path[i]}/#{feature}#{ext}" | |
+ for an acceptable string `ext`. It returns | |
+ `load_path[i].to_str` if found, else 0. | |
+ | |
+ If type is 's', then `ext` is acceptable only if IS_DLEXT(ext); | |
+ if 'r', then only if IS_RBEXT(ext); otherwise `ext` may be absent | |
+ or have any value matching `%r{^\.[^./]*$}`. | |
+*/ | |
static VALUE | |
loaded_feature_path(const char *name, long vlen, const char *feature, long len, | |
int type, VALUE load_path) | |
@@ -77,7 +284,7 @@ loaded_feature_path(const char *name, long vlen, const char *feature, long len, | |
long plen; | |
const char *e; | |
- if(vlen < len) return 0; | |
+ if (vlen < len+1) return 0; | |
if (!strncmp(name+(vlen-len),feature,len)){ | |
plen = vlen - len - 1; | |
} else { | |
@@ -88,23 +295,22 @@ loaded_feature_path(const char *name, long vlen, const char *feature, long len, | |
return 0; | |
plen = e - name - len - 1; | |
} | |
+ if (type == 's' && !IS_DLEXT(&name[plen+len+1]) | |
+ || type == 'r' && !IS_RBEXT(&name[plen+len+1]) | |
+ || name[plen] != '/') { | |
+ return 0; | |
+ } | |
+ /* Now name == "#{prefix}/#{feature}#{ext}" where ext is acceptable | |
+ (possibly empty) and prefix is some string of length plen. */ | |
+ | |
for (i = 0; i < RARRAY_LEN(load_path); ++i) { | |
VALUE p = RARRAY_PTR(load_path)[i]; | |
const char *s = StringValuePtr(p); | |
long n = RSTRING_LEN(p); | |
if (n != plen ) continue; | |
- if (n && (strncmp(name, s, n) || name[n] != '/')) continue; | |
- switch (type) { | |
- case 's': | |
- if (IS_DLEXT(&name[n+len+1])) return p; | |
- break; | |
- case 'r': | |
- if (IS_RBEXT(&name[n+len+1])) return p; | |
- break; | |
- default: | |
- return p; | |
- } | |
+ if (n && strncmp(name, s, n)) continue; | |
+ return p; | |
} | |
return 0; | |
} | |
@@ -132,7 +338,7 @@ loaded_feature_path_i(st_data_t v, st_data_t b, st_data_t f) | |
static int | |
rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const char **fn) | |
{ | |
- VALUE v, features, p, load_path = 0; | |
+ VALUE features, features_index, feature_val, this_feature_index, v, p, load_path = 0; | |
const char *f, *e; | |
long i, len, elen, n; | |
st_table *loading_tbl; | |
@@ -151,8 +357,39 @@ rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const c | |
type = 0; | |
} | |
features = get_loaded_features(); | |
- for (i = 0; i < RARRAY_LEN(features); ++i) { | |
- v = RARRAY_PTR(features)[i]; | |
+ features_index = get_loaded_features_index(); | |
+ | |
+ feature_val = rb_str_new(feature, len); | |
+ this_feature_index = rb_hash_lookup(features_index, feature_val); | |
+ /* We search `features` for an entry such that either | |
+ "#{features[i]}" == "#{load_path[j]}/#{feature}#{e}" | |
+ for some j, or | |
+ "#{features[i]}" == "#{feature}#{e}" | |
+ Here `e` is an "allowed" extension -- either empty or one | |
+ of the extensions accepted by IS_RBEXT, IS_SOEXT, or | |
+ IS_DLEXT. Further, if `ext && rb` then `IS_RBEXT(e)`, | |
+ and if `ext && !rb` then `IS_SOEXT(e) || IS_DLEXT(e)`. | |
+ | |
+ If `expanded`, then only the latter form (without load_path[j]) | |
+ is accepted. Otherwise either form is accepted, *unless* `ext` | |
+ is false and an otherwise-matching entry of the first form is | |
+ preceded by an entry of the form | |
+ "#{features[i2]}" == "#{load_path[j2]}/#{feature}#{e2}" | |
+ where `e2` matches %r{^\.[^./]*$} but is not an allowed extension. | |
+ After a "distractor" entry of this form, only entries of the | |
+ form "#{feature}#{e}" are accepted. | |
+ | |
+ In `rb_provide_feature()` and `get_loaded_features_index()` we | |
+ maintain an invariant that the array `this_feature_index` will | |
+ point to every entry in `features` which has the form | |
+ "#{prefix}#{feature}#{e}" | |
+ where `e` is empty or matches %r{^\.[^./]*$}, and `prefix` is empty | |
+ or ends in '/'. This includes both match forms above, as well | |
+ as any distractors, so we may ignore all other entries in `features`. | |
+ */ | |
+ for (i = 0; this_feature_index != Qnil && i < RARRAY_LEN(this_feature_index); i++) { | |
+ long index = FIX2LONG(rb_ary_entry(this_feature_index, i)); | |
+ v = RARRAY_PTR(features)[index]; | |
f = StringValuePtr(v); | |
if ((n = RSTRING_LEN(v)) < len) continue; | |
if (strncmp(f, feature, len) != 0) { | |
@@ -175,6 +412,7 @@ rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const c | |
return 'r'; | |
} | |
} | |
+ | |
loading_tbl = get_loading_table(); | |
if (loading_tbl) { | |
f = 0; | |
@@ -183,7 +421,7 @@ rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const c | |
fs.name = feature; | |
fs.len = len; | |
fs.type = type; | |
- fs.load_path = load_path ? load_path : rb_get_load_path(); | |
+ fs.load_path = load_path ? load_path : rb_get_expanded_load_path(); | |
fs.result = 0; | |
st_foreach(loading_tbl, loaded_feature_path_i, (st_data_t)&fs); | |
if ((f = fs.result) != 0) { | |
@@ -233,7 +471,7 @@ rb_feature_provided(const char *feature, const char **loading) | |
if (*feature == '.' && | |
(feature[1] == '/' || strncmp(feature+1, "./", 2) == 0)) { | |
- fullpath = rb_file_expand_path_fast(rb_str_new2(feature), Qnil); | |
+ fullpath = rb_file_expand_path_fast(rb_get_path(rb_str_new2(feature)), Qnil); | |
feature = RSTRING_PTR(fullpath); | |
} | |
if (ext && !strchr(ext, '/')) { | |
@@ -254,11 +492,18 @@ rb_feature_provided(const char *feature, const char **loading) | |
static void | |
rb_provide_feature(VALUE feature) | |
{ | |
- if (OBJ_FROZEN(get_loaded_features())) { | |
+ VALUE features; | |
+ | |
+ features = get_loaded_features(); | |
+ if (OBJ_FROZEN(features)) { | |
rb_raise(rb_eRuntimeError, | |
"$LOADED_FEATURES is frozen; cannot append feature"); | |
} | |
- rb_ary_push(get_loaded_features(), feature); | |
+ rb_str_freeze(feature); | |
+ | |
+ rb_ary_push(features, feature); | |
+ features_index_add(feature, INT2FIX(RARRAY_LEN(features)-1)); | |
+ reset_loaded_features_snapshot(); | |
} | |
void | |
@@ -774,10 +1019,15 @@ Init_load() | |
rb_alias_variable(rb_intern("$-I"), id_load_path); | |
rb_alias_variable(rb_intern("$LOAD_PATH"), id_load_path); | |
vm->load_path = rb_ary_new(); | |
+ vm->expanded_load_path = rb_ary_new(); | |
+ vm->load_path_snapshot = rb_ary_new(); | |
+ vm->load_path_check_cache = 0; | |
rb_define_virtual_variable("$\"", get_loaded_features, 0); | |
rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0); | |
vm->loaded_features = rb_ary_new(); | |
+ vm->loaded_features_snapshot = rb_ary_new(); | |
+ vm->loaded_features_index = rb_hash_new(); | |
rb_define_global_function("load", rb_f_load, -1); | |
rb_define_global_function("require", rb_f_require, 1); | |
diff --git a/ruby.c b/ruby.c | |
index 3ddd96c..7ffc78e 100644 | |
--- a/ruby.c | |
+++ b/ruby.c | |
@@ -1366,7 +1366,8 @@ process_options(int argc, char **argv, struct cmdline_options *opt) | |
long i; | |
VALUE load_path = GET_VM()->load_path; | |
for (i = 0; i < RARRAY_LEN(load_path); ++i) { | |
- rb_enc_associate(RARRAY_PTR(load_path)[i], lenc); | |
+ RARRAY_PTR(load_path)[i] = | |
+ rb_enc_associate(rb_str_dup(RARRAY_PTR(load_path)[i]), lenc); | |
} | |
} | |
if (!(opt->disable & DISABLE_BIT(gems))) { | |
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb | |
index 58a9ee2..ec75096 100644 | |
--- a/test/ruby/test_require.rb | |
+++ b/test/ruby/test_require.rb | |
@@ -356,4 +356,114 @@ class TestRequire < Test::Unit::TestCase | |
$:.replace(loadpath) | |
$".replace(features) | |
end | |
+ | |
+ def test_require_changed_current_dir | |
+ bug7158 = '[ruby-core:47970]' | |
+ Dir.mktmpdir {|tmp| | |
+ Dir.chdir(tmp) { | |
+ Dir.mkdir("a") | |
+ Dir.mkdir("b") | |
+ open(File.join("a", "foo.rb"), "w") {} | |
+ open(File.join("b", "bar.rb"), "w") {|f| | |
+ f.puts "p :ok" | |
+ } | |
+ assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) | |
+ $: << "." | |
+ Dir.chdir("a") | |
+ require "foo" | |
+ Dir.chdir("../b") | |
+ p :ng unless require "bar" | |
+ Dir.chdir("..") | |
+ p :ng if require "b/bar" | |
+ INPUT | |
+ } | |
+ } | |
+ end | |
+ | |
+ def test_require_not_modified_load_path | |
+ bug7158 = '[ruby-core:47970]' | |
+ Dir.mktmpdir {|tmp| | |
+ Dir.chdir(tmp) { | |
+ open("foo.rb", "w") {} | |
+ assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) | |
+ a = Object.new | |
+ def a.to_str | |
+ "#{tmp}" | |
+ end | |
+ $: << a | |
+ require "foo" | |
+ last_path = $:.pop | |
+ p :ok if last_path == a && last_path.class == Object | |
+ INPUT | |
+ } | |
+ } | |
+ end | |
+ | |
+ def test_require_changed_home | |
+ bug7158 = '[ruby-core:47970]' | |
+ Dir.mktmpdir {|tmp| | |
+ Dir.chdir(tmp) { | |
+ open("foo.rb", "w") {} | |
+ Dir.mkdir("a") | |
+ open(File.join("a", "bar.rb"), "w") {} | |
+ assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) | |
+ $: << '~' | |
+ ENV['HOME'] = "#{tmp}" | |
+ require "foo" | |
+ ENV['HOME'] = "#{tmp}/a" | |
+ p :ok if require "bar" | |
+ INPUT | |
+ } | |
+ } | |
+ end | |
+ | |
+ def test_require_to_path_redefined_in_load_path | |
+ bug7158 = '[ruby-core:47970]' | |
+ Dir.mktmpdir {|tmp| | |
+ Dir.chdir(tmp) { | |
+ open("foo.rb", "w") {} | |
+ assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158) | |
+ a = Object.new | |
+ def a.to_path | |
+ "bar" | |
+ end | |
+ $: << a | |
+ begin | |
+ require "foo" | |
+ p :ng | |
+ rescue LoadError | |
+ end | |
+ def a.to_path | |
+ "#{tmp}" | |
+ end | |
+ p :ok if require "foo" | |
+ INPUT | |
+ } | |
+ } | |
+ end | |
+ | |
+ def test_require_to_str_redefined_in_load_path | |
+ bug7158 = '[ruby-core:47970]' | |
+ Dir.mktmpdir {|tmp| | |
+ Dir.chdir(tmp) { | |
+ open("foo.rb", "w") {} | |
+ assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158) | |
+ a = Object.new | |
+ def a.to_str | |
+ "foo" | |
+ end | |
+ $: << a | |
+ begin | |
+ require "foo" | |
+ p :ng | |
+ rescue LoadError | |
+ end | |
+ def a.to_str | |
+ "#{tmp}" | |
+ end | |
+ p :ok if require "foo" | |
+ INPUT | |
+ } | |
+ } | |
+ end | |
end | |
diff --git a/vm.c b/vm.c | |
index ae0286f..adc60d8 100644 | |
--- a/vm.c | |
+++ b/vm.c | |
@@ -1592,7 +1592,12 @@ rb_vm_mark(void *ptr) | |
RUBY_MARK_UNLESS_NULL(vm->thgroup_default); | |
RUBY_MARK_UNLESS_NULL(vm->mark_object_ary); | |
RUBY_MARK_UNLESS_NULL(vm->load_path); | |
+ RUBY_MARK_UNLESS_NULL(vm->load_path_snapshot); | |
+ RUBY_MARK_UNLESS_NULL(vm->load_path_check_cache); | |
+ RUBY_MARK_UNLESS_NULL(vm->expanded_load_path); | |
RUBY_MARK_UNLESS_NULL(vm->loaded_features); | |
+ RUBY_MARK_UNLESS_NULL(vm->loaded_features_snapshot); | |
+ RUBY_MARK_UNLESS_NULL(vm->loaded_features_index); | |
RUBY_MARK_UNLESS_NULL(vm->top_self); | |
RUBY_MARK_UNLESS_NULL(vm->coverages); | |
rb_gc_mark_locations(vm->special_exceptions, vm->special_exceptions + ruby_special_error_count); | |
diff --git a/vm_core.h b/vm_core.h | |
index dfc0e3c..70c5f5c 100644 | |
--- a/vm_core.h | |
+++ b/vm_core.h | |
@@ -299,7 +299,12 @@ typedef struct rb_vm_struct { | |
/* load */ | |
VALUE top_self; | |
VALUE load_path; | |
+ VALUE load_path_snapshot; | |
+ VALUE load_path_check_cache; | |
+ VALUE expanded_load_path; | |
VALUE loaded_features; | |
+ VALUE loaded_features_snapshot; | |
+ VALUE loaded_features_index; | |
struct st_table *loading_table; | |
/* signal */ | |
diff --git a/common.mk b/common.mk | |
index 2e57492..b2a2e71 100644 | |
--- a/common.mk | |
+++ b/common.mk | |
@@ -639,7 +639,8 @@ file.$(OBJEXT): {$(VPATH)}file.c $(RUBY_H_INCLUDES) {$(VPATH)}io.h \ | |
gc.$(OBJEXT): {$(VPATH)}gc.c $(RUBY_H_INCLUDES) {$(VPATH)}re.h \ | |
{$(VPATH)}regex.h $(ENCODING_H_INCLUDES) $(VM_CORE_H_INCLUDES) \ | |
{$(VPATH)}gc.h {$(VPATH)}io.h {$(VPATH)}eval_intern.h {$(VPATH)}util.h \ | |
- {$(VPATH)}debug.h {$(VPATH)}internal.h {$(VPATH)}constant.h | |
+ {$(VPATH)}debug.h {$(VPATH)}internal.h {$(VPATH)}constant.h \ | |
+ {$(VPATH)}pool_alloc.inc.h {$(VPATH)}pool_alloc.h | |
hash.$(OBJEXT): {$(VPATH)}hash.c $(RUBY_H_INCLUDES) {$(VPATH)}util.h \ | |
$(ENCODING_H_INCLUDES) | |
inits.$(OBJEXT): {$(VPATH)}inits.c $(RUBY_H_INCLUDES) \ | |
@@ -703,7 +704,7 @@ signal.$(OBJEXT): {$(VPATH)}signal.c $(RUBY_H_INCLUDES) \ | |
$(VM_CORE_H_INCLUDES) {$(VPATH)}debug.h | |
sprintf.$(OBJEXT): {$(VPATH)}sprintf.c $(RUBY_H_INCLUDES) {$(VPATH)}re.h \ | |
{$(VPATH)}regex.h {$(VPATH)}vsnprintf.c $(ENCODING_H_INCLUDES) | |
-st.$(OBJEXT): {$(VPATH)}st.c $(RUBY_H_INCLUDES) | |
+st.$(OBJEXT): {$(VPATH)}st.c $(RUBY_H_INCLUDES) {$(VPATH)}pool_alloc.h | |
strftime.$(OBJEXT): {$(VPATH)}strftime.c $(RUBY_H_INCLUDES) \ | |
{$(VPATH)}timev.h | |
string.$(OBJEXT): {$(VPATH)}string.c $(RUBY_H_INCLUDES) {$(VPATH)}re.h \ | |
diff --git a/configure.in b/configure.in | |
index 1a31b16..1a2b42e 100644 | |
--- a/configure.in | |
+++ b/configure.in | |
@@ -1330,6 +1330,30 @@ if test $rb_cv_stack_end_address != no; then | |
AC_DEFINE_UNQUOTED(STACK_END_ADDRESS, $rb_cv_stack_end_address) | |
fi | |
+AS_CASE(["$target_os"], | |
+[openbsd*], [ | |
+ AC_CACHE_CHECK(for heap align log on openbsd, rb_cv_page_size_log, | |
+ [rb_cv_page_size_log=no | |
+ for page_log in 12 13; do | |
+ AC_TRY_RUN([ | |
+#include <math.h> | |
+#include <unistd.h> | |
+ | |
+int | |
+main() { | |
+ if ((int)log2((double)sysconf(_SC_PAGESIZE)) != $page_log) return 1; | |
+ return 0; | |
+} | |
+ ], | |
+ rb_cv_page_size_log="$page_log"; break) | |
+ done]) | |
+ if test $rb_cv_page_size_log != no; then | |
+ AC_DEFINE_UNQUOTED(HEAP_ALIGN_LOG, $rb_cv_page_size_log) | |
+ else | |
+ AC_DEFINE_UNQUOTED(HEAP_ALIGN_LOG, 12) | |
+ fi | |
+]) | |
+ | |
dnl Checks for library functions. | |
AC_TYPE_GETGROUPS | |
AC_TYPE_SIGNAL | |
@@ -1430,7 +1454,8 @@ AC_CHECK_FUNCS(fmod killpg wait4 waitpid fork spawnv syscall __syscall chroot ge | |
setsid telldir seekdir fchmod cosh sinh tanh log2 round\ | |
setuid setgid daemon select_large_fdset setenv unsetenv\ | |
mktime timegm gmtime_r clock_gettime gettimeofday poll ppoll\ | |
- pread sendfile shutdown sigaltstack dl_iterate_phdr) | |
+ pread sendfile shutdown sigaltstack dl_iterate_phdr\ | |
+ posix_memalign memalign) | |
AC_CACHE_CHECK(for unsetenv returns a value, rb_cv_unsetenv_return_value, | |
[AC_TRY_COMPILE([ | |
diff --git a/ext/-test-/st/numhash/numhash.c b/ext/-test-/st/numhash/numhash.c | |
index e186cd4..53d9e1b 100644 | |
--- a/ext/-test-/st/numhash/numhash.c | |
+++ b/ext/-test-/st/numhash/numhash.c | |
@@ -54,7 +54,7 @@ numhash_i(st_data_t key, st_data_t value, st_data_t arg, int error) | |
static VALUE | |
numhash_each(VALUE self) | |
{ | |
- return st_foreach((st_table *)DATA_PTR(self), numhash_i, self) ? Qtrue : Qfalse; | |
+ return st_foreach_check((st_table *)DATA_PTR(self), numhash_i, self, 0) ? Qtrue : Qfalse; | |
} | |
void | |
diff --git a/gc.c b/gc.c | |
index 3eab6fb..1f155d0 100644 | |
--- a/gc.c | |
+++ b/gc.c | |
@@ -20,6 +20,7 @@ | |
#include "vm_core.h" | |
#include "internal.h" | |
#include "gc.h" | |
+#include "pool_alloc.h" | |
#include "constant.h" | |
#include "ruby_atomic.h" | |
#include <stdio.h> | |
@@ -37,7 +38,12 @@ | |
#if defined _WIN32 || defined __CYGWIN__ | |
#include <windows.h> | |
+#elif defined(HAVE_POSIX_MEMALIGN) | |
+#elif defined(HAVE_MEMALIGN) | |
+#include <malloc.h> | |
#endif | |
+static void aligned_free(void *); | |
+static void *aligned_malloc(size_t alignment, size_t size); | |
#ifdef HAVE_VALGRIND_MEMCHECK_H | |
# include <valgrind/memcheck.h> | |
@@ -356,6 +362,24 @@ typedef struct mark_stack { | |
#define CALC_EXACT_MALLOC_SIZE 0 | |
+#ifdef POOL_ALLOC_API | |
+/* POOL ALLOC API */ | |
+#define POOL_ALLOC_PART 1 | |
+#include "pool_alloc.inc.h" | |
+#undef POOL_ALLOC_PART | |
+ | |
+typedef struct pool_layout_t pool_layout_t; | |
+struct pool_layout_t { | |
+ pool_header | |
+ p6, /* st_table && st_table_entry */ | |
+ p11; /* st_table.bins init size */ | |
+} pool_layout = { | |
+ INIT_POOL(void*[6]), | |
+ INIT_POOL(void*[11]) | |
+}; | |
+static void pool_finalize_header(pool_header *header); | |
+#endif | |
+ | |
typedef struct rb_objspace { | |
struct { | |
size_t limit; | |
@@ -365,6 +389,9 @@ typedef struct rb_objspace { | |
size_t allocations; | |
#endif | |
} malloc_params; | |
+#ifdef POOL_ALLOC_API | |
+ pool_layout_t *pool_headers; | |
+#endif | |
struct { | |
size_t increment; | |
struct heaps_slot *ptr; | |
@@ -431,7 +458,11 @@ typedef struct rb_objspace { | |
#define ruby_initial_gc_stress initial_params.gc_stress | |
int *ruby_initial_gc_stress_ptr = &ruby_initial_gc_stress; | |
#else | |
+# ifdef POOL_ALLOC_API | |
+static rb_objspace_t rb_objspace = {{GC_MALLOC_LIMIT}, &pool_layout, {HEAP_MIN_SLOTS}}; | |
+# else | |
static rb_objspace_t rb_objspace = {{GC_MALLOC_LIMIT}, {HEAP_MIN_SLOTS}}; | |
+# endif | |
int *ruby_initial_gc_stress_ptr = &rb_objspace.gc_stress; | |
#endif | |
#define malloc_limit objspace->malloc_params.limit | |
@@ -483,6 +514,10 @@ rb_objspace_alloc(void) | |
memset(objspace, 0, sizeof(*objspace)); | |
malloc_limit = initial_malloc_limit; | |
ruby_gc_stress = ruby_initial_gc_stress; | |
+#ifdef POOL_ALLOC_API | |
+ objspace->pool_headers = (pool_layout_t*) malloc(sizeof(pool_layout)); | |
+ memcpy(objspace->pool_headers, &pool_layout, sizeof(pool_layout)); | |
+#endif | |
return objspace; | |
} | |
@@ -626,6 +661,13 @@ rb_objspace_free(rb_objspace_t *objspace) | |
heaps = 0; | |
} | |
free_stack_chunks(&objspace->mark_stack); | |
+#ifdef POOL_ALLOC_API | |
+ if (objspace->pool_headers) { | |
+ pool_finalize_header(&objspace->pool_headers->p6); | |
+ pool_finalize_header(&objspace->pool_headers->p11); | |
+ free(objspace->pool_headers); | |
+ } | |
+#endif | |
free(objspace); | |
} | |
#endif | |
@@ -1150,6 +1192,27 @@ rb_gc_stats_enabled() | |
return gc_statistics ? Qtrue : Qfalse; | |
} | |
+#ifdef POOL_ALLOC_API | |
+/* POOL ALLOC API */ | |
+#define POOL_ALLOC_PART 2 | |
+#include "pool_alloc.inc.h" | |
+#undef POOL_ALLOC_PART | |
+ | |
+void | |
+ruby_xpool_free(void *ptr) | |
+{ | |
+ pool_free_entry((void**)ptr); | |
+} | |
+ | |
+#define CONCRET_POOL_MALLOC(pnts) \ | |
+void * ruby_xpool_malloc_##pnts##p () { \ | |
+ return pool_alloc_entry(&rb_objspace.pool_headers->p##pnts ); \ | |
+} | |
+CONCRET_POOL_MALLOC(6) | |
+CONCRET_POOL_MALLOC(11) | |
+#undef CONCRET_POOL_MALLOC | |
+ | |
+#endif | |
/* | |
* call-seq: | |
@@ -1665,6 +1728,55 @@ allocate_sorted_heaps(rb_objspace_t *objspace, size_t next_heaps_length) | |
heaps_length = next_heaps_length; | |
} | |
+static void * | |
+aligned_malloc(size_t alignment, size_t size) | |
+{ | |
+ void *res; | |
+ | |
+#if defined __MINGW32__ | |
+ res = __mingw_aligned_malloc(size, alignment); | |
+#elif defined _WIN32 && !defined __CYGWIN__ | |
+ res = _aligned_malloc(size, alignment); | |
+#elif defined(HAVE_POSIX_MEMALIGN) | |
+ if (posix_memalign(&res, alignment, size) == 0) { | |
+ return res; | |
+ } | |
+ else { | |
+ return NULL; | |
+ } | |
+#elif defined(HAVE_MEMALIGN) | |
+ res = memalign(alignment, size); | |
+#else | |
+ char* aligned; | |
+ res = malloc(alignment + size + sizeof(void*)); | |
+ aligned = (char*)res + alignment + sizeof(void*); | |
+ aligned -= ((VALUE)aligned & (alignment - 1)); | |
+ ((void**)aligned)[-1] = res; | |
+ res = (void*)aligned; | |
+#endif | |
+ | |
+#if defined(_DEBUG) || defined(GC_DEBUG) | |
+ /* alignment must be a power of 2 */ | |
+ assert((alignment - 1) & alignment == 0); | |
+ assert(alignment % sizeof(void*) == 0); | |
+#endif | |
+ return res; | |
+} | |
+ | |
+static void | |
+aligned_free(void *ptr) | |
+{ | |
+#if defined __MINGW32__ | |
+ __mingw_aligned_free(ptr); | |
+#elif defined _WIN32 && !defined __CYGWIN__ | |
+ _aligned_free(ptr); | |
+#elif defined(HAVE_MEMALIGN) || defined(HAVE_POSIX_MEMALIGN) | |
+ free(ptr); | |
+#else | |
+ free(((void**)ptr)[-1]); | |
+#endif | |
+} | |
+ | |
static void | |
assign_heap_slot(rb_objspace_t *objspace) | |
{ | |
diff --git a/hash.c b/hash.c | |
index dcc2cdc..7cdd0c4 100644 | |
--- a/hash.c | |
+++ b/hash.c | |
@@ -44,7 +44,7 @@ rb_any_cmp(VALUE a, VALUE b) | |
if (FIXNUM_P(a) && FIXNUM_P(b)) { | |
return a != b; | |
} | |
- if (TYPE(a) == T_STRING && RBASIC(a)->klass == rb_cString && | |
+ if (RB_TYPE_P(a, T_STRING) && RBASIC(a)->klass == rb_cString && | |
TYPE(b) == T_STRING && RBASIC(b)->klass == rb_cString) { | |
return rb_str_hash_cmp(a, b); | |
} | |
@@ -80,20 +80,14 @@ rb_any_hash(VALUE a) | |
VALUE hval; | |
st_index_t hnum; | |
- switch (TYPE(a)) { | |
- case T_FIXNUM: | |
- case T_SYMBOL: | |
- case T_NIL: | |
- case T_FALSE: | |
- case T_TRUE: | |
- hnum = rb_hash_end(rb_hash_start((unsigned int)a)); | |
- break; | |
- | |
- case T_STRING: | |
+ if (SPECIAL_CONST_P(a)) { | |
+ if (a == Qundef) return 0; | |
+ hnum = rb_hash_end(rb_hash_start((st_index_t)a)); | |
+ } | |
+ else if (BUILTIN_TYPE(a) == T_STRING) { | |
hnum = rb_str_hash(a); | |
- break; | |
- | |
- default: | |
+ } | |
+ else { | |
hval = rb_hash(a); | |
hnum = FIX2LONG(hval); | |
} | |
@@ -106,10 +100,8 @@ static const struct st_hash_type objhash = { | |
rb_any_hash, | |
}; | |
-static const struct st_hash_type identhash = { | |
- st_numcmp, | |
- st_numhash, | |
-}; | |
+extern const struct st_hash_type st_hashtype_num; | |
+#define identhash st_hashtype_num | |
typedef int st_foreach_func(st_data_t, st_data_t, st_data_t); | |
@@ -124,7 +116,6 @@ foreach_safe_i(st_data_t key, st_data_t value, struct foreach_safe_arg *arg) | |
{ | |
int status; | |
- if (key == Qundef) return ST_CONTINUE; | |
status = (*arg->func)(key, value, arg->arg); | |
if (status == ST_CONTINUE) { | |
return ST_CHECK; | |
@@ -140,7 +131,7 @@ st_foreach_safe(st_table *table, int (*func)(ANYARGS), st_data_t a) | |
arg.tbl = table; | |
arg.func = (st_foreach_func *)func; | |
arg.arg = a; | |
- if (st_foreach(table, foreach_safe_i, (st_data_t)&arg)) { | |
+ if (st_foreach_check(table, foreach_safe_i, (st_data_t)&arg, 0)) { | |
rb_raise(rb_eRuntimeError, "hash modified during iteration"); | |
} | |
} | |
@@ -154,21 +145,21 @@ struct hash_foreach_arg { | |
}; | |
static int | |
-hash_foreach_iter(st_data_t key, st_data_t value, struct hash_foreach_arg *arg) | |
+hash_foreach_iter(st_data_t key, st_data_t value, st_data_t argp) | |
{ | |
+ struct hash_foreach_arg *arg = (struct hash_foreach_arg *)argp; | |
int status; | |
st_table *tbl; | |
tbl = RHASH(arg->hash)->ntbl; | |
- if ((VALUE)key == Qundef) return ST_CONTINUE; | |
status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg); | |
if (RHASH(arg->hash)->ntbl != tbl) { | |
rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); | |
} | |
switch (status) { | |
case ST_DELETE: | |
- st_delete_safe(tbl, &key, 0, Qundef); | |
FL_SET(arg->hash, HASH_DELETED); | |
+ return ST_DELETE; | |
case ST_CONTINUE: | |
break; | |
case ST_STOP: | |
@@ -184,7 +175,7 @@ hash_foreach_ensure(VALUE hash) | |
if (RHASH(hash)->iter_lev == 0) { | |
if (FL_TEST(hash, HASH_DELETED)) { | |
- st_cleanup_safe(RHASH(hash)->ntbl, Qundef); | |
+ st_cleanup_safe(RHASH(hash)->ntbl, (st_data_t)Qundef); | |
FL_UNSET(hash, HASH_DELETED); | |
} | |
} | |
@@ -192,9 +183,10 @@ hash_foreach_ensure(VALUE hash) | |
} | |
static VALUE | |
-hash_foreach_call(struct hash_foreach_arg *arg) | |
+hash_foreach_call(VALUE arg) | |
{ | |
- if (st_foreach(RHASH(arg->hash)->ntbl, hash_foreach_iter, (st_data_t)arg)) { | |
+ VALUE hash = ((struct hash_foreach_arg *)arg)->hash; | |
+ if (st_foreach_check(RHASH(hash)->ntbl, hash_foreach_iter, (st_data_t)arg, (st_data_t)Qundef)) { | |
rb_raise(rb_eRuntimeError, "hash modified during iteration"); | |
} | |
return Qnil; | |
@@ -452,7 +444,7 @@ rb_hash_rehash_i(VALUE key, VALUE value, VALUE arg) | |
{ | |
st_table *tbl = (st_table *)arg; | |
- if (key != Qundef) st_insert(tbl, key, value); | |
+ st_insert(tbl, (st_data_t)key, (st_data_t)value); | |
return ST_CONTINUE; | |
} | |
@@ -500,6 +492,20 @@ rb_hash_rehash(VALUE hash) | |
return hash; | |
} | |
+static VALUE | |
+hash_default_value(VALUE hash, VALUE key) | |
+{ | |
+ if (rb_method_basic_definition_p(CLASS_OF(hash), id_default)) { | |
+ VALUE ifnone = RHASH_IFNONE(hash); | |
+ if (!FL_TEST(hash, HASH_PROC_DEFAULT)) return ifnone; | |
+ if (key == Qundef) return Qnil; | |
+ return rb_funcall(ifnone, id_yield, 2, hash, key); | |
+ } | |
+ else { | |
+ return rb_funcall(hash, id_default, 1, key); | |
+ } | |
+} | |
+ | |
/* | |
* call-seq: | |
* hsh[key] -> value | |
@@ -520,13 +526,7 @@ rb_hash_aref(VALUE hash, VALUE key) | |
st_data_t val; | |
if (!RHASH(hash)->ntbl || !st_lookup(RHASH(hash)->ntbl, key, &val)) { | |
- if (!FL_TEST(hash, HASH_PROC_DEFAULT) && | |
- rb_method_basic_definition_p(CLASS_OF(hash), id_default)) { | |
- return RHASH_IFNONE(hash); | |
- } | |
- else { | |
- return rb_funcall(hash, id_default, 1, key); | |
- } | |
+ return hash_default_value(hash, key); | |
} | |
return (VALUE)val; | |
} | |
@@ -669,7 +669,7 @@ rb_hash_default(int argc, VALUE *argv, VALUE hash) | |
static VALUE | |
rb_hash_set_default(VALUE hash, VALUE ifnone) | |
{ | |
- rb_hash_modify(hash); | |
+ rb_hash_modify_check(hash); | |
RHASH_IFNONE(hash) = ifnone; | |
FL_UNSET(hash, HASH_PROC_DEFAULT); | |
return ifnone; | |
@@ -717,7 +717,7 @@ rb_hash_set_default_proc(VALUE hash, VALUE proc) | |
{ | |
VALUE b; | |
- rb_hash_modify(hash); | |
+ rb_hash_modify_check(hash); | |
b = rb_check_convert_type(proc, T_DATA, "Proc", "to_proc"); | |
if (NIL_P(b) || !rb_obj_is_proc(b)) { | |
rb_raise(rb_eTypeError, | |
@@ -786,7 +786,7 @@ rb_hash_delete_key(VALUE hash, VALUE key) | |
if (!RHASH(hash)->ntbl) | |
return Qundef; | |
if (RHASH(hash)->iter_lev > 0) { | |
- if (st_delete_safe(RHASH(hash)->ntbl, &ktmp, &val, Qundef)) { | |
+ if (st_delete_safe(RHASH(hash)->ntbl, &ktmp, &val, (st_data_t)Qundef)) { | |
FL_SET(hash, HASH_DELETED); | |
return (VALUE)val; | |
} | |
@@ -819,7 +819,7 @@ rb_hash_delete(VALUE hash, VALUE key) | |
{ | |
VALUE val; | |
- rb_hash_modify(hash); | |
+ rb_hash_modify_check(hash); | |
val = rb_hash_delete_key(hash, key); | |
if (val != Qundef) return val; | |
if (rb_block_given_p()) { | |
@@ -838,7 +838,6 @@ shift_i_safe(VALUE key, VALUE value, VALUE arg) | |
{ | |
struct shift_var *var = (struct shift_var *)arg; | |
- if (key == Qundef) return ST_CONTINUE; | |
var->key = key; | |
var->val = value; | |
return ST_STOP; | |
@@ -862,33 +861,29 @@ rb_hash_shift(VALUE hash) | |
{ | |
struct shift_var var; | |
- rb_hash_modify(hash); | |
- var.key = Qundef; | |
- if (RHASH(hash)->iter_lev == 0) { | |
- if (st_shift(RHASH(hash)->ntbl, &var.key, &var.val)) { | |
- return rb_assoc_new(var.key, var.val); | |
- } | |
- } | |
- else { | |
- rb_hash_foreach(hash, shift_i_safe, (VALUE)&var); | |
- | |
- if (var.key != Qundef) { | |
- rb_hash_delete_key(hash, var.key); | |
- return rb_assoc_new(var.key, var.val); | |
+ rb_hash_modify_check(hash); | |
+ if (RHASH(hash)->ntbl) { | |
+ var.key = Qundef; | |
+ if (RHASH(hash)->iter_lev == 0) { | |
+ if (st_shift(RHASH(hash)->ntbl, &var.key, &var.val)) { | |
+ return rb_assoc_new(var.key, var.val); | |
+ } | |
+ } | |
+ else { | |
+ rb_hash_foreach(hash, shift_i_safe, (VALUE)&var); | |
+ | |
+ if (var.key != Qundef) { | |
+ rb_hash_delete_key(hash, var.key); | |
+ return rb_assoc_new(var.key, var.val); | |
+ } | |
} | |
} | |
- if (FL_TEST(hash, HASH_PROC_DEFAULT)) { | |
- return rb_funcall(RHASH_IFNONE(hash), id_yield, 2, hash, Qnil); | |
- } | |
- else { | |
- return RHASH_IFNONE(hash); | |
- } | |
+ return hash_default_value(hash, Qnil); | |
} | |
static int | |
delete_if_i(VALUE key, VALUE value, VALUE hash) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
if (RTEST(rb_yield_values(2, key, value))) { | |
rb_hash_delete_key(hash, key); | |
} | |
@@ -914,8 +909,9 @@ VALUE | |
rb_hash_delete_if(VALUE hash) | |
{ | |
RETURN_ENUMERATOR(hash, 0, 0); | |
- rb_hash_modify(hash); | |
- rb_hash_foreach(hash, delete_if_i, hash); | |
+ rb_hash_modify_check(hash); | |
+ if (RHASH(hash)->ntbl) | |
+ rb_hash_foreach(hash, delete_if_i, hash); | |
return hash; | |
} | |
@@ -986,7 +982,6 @@ rb_hash_values_at(int argc, VALUE *argv, VALUE hash) | |
static int | |
select_i(VALUE key, VALUE value, VALUE result) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
if (RTEST(rb_yield_values(2, key, value))) | |
rb_hash_aset(result, key, value); | |
return ST_CONTINUE; | |
@@ -1020,7 +1015,6 @@ rb_hash_select(VALUE hash) | |
static int | |
keep_if_i(VALUE key, VALUE value, VALUE hash) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
if (!RTEST(rb_yield_values(2, key, value))) { | |
return ST_DELETE; | |
} | |
@@ -1042,7 +1036,7 @@ rb_hash_select_bang(VALUE hash) | |
st_index_t n; | |
RETURN_ENUMERATOR(hash, 0, 0); | |
- rb_hash_modify(hash); | |
+ rb_hash_modify_check(hash); | |
if (!RHASH(hash)->ntbl) | |
return Qnil; | |
n = RHASH(hash)->ntbl->num_entries; | |
@@ -1067,8 +1061,9 @@ VALUE | |
rb_hash_keep_if(VALUE hash) | |
{ | |
RETURN_ENUMERATOR(hash, 0, 0); | |
- rb_hash_modify(hash); | |
- rb_hash_foreach(hash, keep_if_i, hash); | |
+ rb_hash_modify_check(hash); | |
+ if (RHASH(hash)->ntbl) | |
+ rb_hash_foreach(hash, keep_if_i, hash); | |
return hash; | |
} | |
@@ -1146,9 +1141,7 @@ rb_hash_aset(VALUE hash, VALUE key, VALUE val) | |
static int | |
replace_i(VALUE key, VALUE val, VALUE hash) | |
{ | |
- if (key != Qundef) { | |
- rb_hash_aset(hash, key, val); | |
- } | |
+ rb_hash_aset(hash, key, val); | |
return ST_CONTINUE; | |
} | |
@@ -1229,7 +1222,6 @@ rb_hash_empty_p(VALUE hash) | |
static int | |
each_value_i(VALUE key, VALUE value) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
rb_yield(value); | |
return ST_CONTINUE; | |
} | |
@@ -1264,7 +1256,6 @@ rb_hash_each_value(VALUE hash) | |
static int | |
each_key_i(VALUE key, VALUE value) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
rb_yield(key); | |
return ST_CONTINUE; | |
} | |
@@ -1298,7 +1289,6 @@ rb_hash_each_key(VALUE hash) | |
static int | |
each_pair_i(VALUE key, VALUE value) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
rb_yield(rb_assoc_new(key, value)); | |
return ST_CONTINUE; | |
} | |
@@ -1336,7 +1326,6 @@ rb_hash_each_pair(VALUE hash) | |
static int | |
to_a_i(VALUE key, VALUE value, VALUE ary) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
rb_ary_push(ary, rb_assoc_new(key, value)); | |
return ST_CONTINUE; | |
} | |
@@ -1369,7 +1358,6 @@ inspect_i(VALUE key, VALUE value, VALUE str) | |
{ | |
VALUE str2; | |
- if (key == Qundef) return ST_CONTINUE; | |
str2 = rb_inspect(key); | |
if (RSTRING_LEN(str) > 1) { | |
rb_str_cat2(str, ", "); | |
@@ -1436,7 +1424,6 @@ rb_hash_to_hash(VALUE hash) | |
static int | |
keys_i(VALUE key, VALUE value, VALUE ary) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
rb_ary_push(ary, key); | |
return ST_CONTINUE; | |
} | |
@@ -1467,7 +1454,6 @@ rb_hash_keys(VALUE hash) | |
static int | |
values_i(VALUE key, VALUE value, VALUE ary) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
rb_ary_push(ary, value); | |
return ST_CONTINUE; | |
} | |
@@ -1526,7 +1512,6 @@ rb_hash_search_value(VALUE key, VALUE value, VALUE arg) | |
{ | |
VALUE *data = (VALUE *)arg; | |
- if (key == Qundef) return ST_CONTINUE; | |
if (rb_equal(value, data[1])) { | |
data[0] = Qtrue; | |
return ST_STOP; | |
@@ -1570,7 +1555,6 @@ eql_i(VALUE key, VALUE val1, VALUE arg) | |
struct equal_data *data = (struct equal_data *)arg; | |
st_data_t val2; | |
- if (key == Qundef) return ST_CONTINUE; | |
if (!st_lookup(data->tbl, key, &val2)) { | |
data->result = Qfalse; | |
return ST_STOP; | |
@@ -1601,7 +1585,7 @@ hash_equal(VALUE hash1, VALUE hash2, int eql) | |
struct equal_data data; | |
if (hash1 == hash2) return Qtrue; | |
- if (TYPE(hash2) != T_HASH) { | |
+ if (!RB_TYPE_P(hash2, T_HASH)) { | |
if (!rb_respond_to(hash2, rb_intern("to_hash"))) { | |
return Qfalse; | |
} | |
@@ -1672,7 +1656,6 @@ hash_i(VALUE key, VALUE val, VALUE arg) | |
st_index_t *hval = (st_index_t *)arg; | |
st_index_t hdata[2]; | |
- if (key == Qundef) return ST_CONTINUE; | |
hdata[0] = rb_hash(key); | |
hdata[1] = rb_hash(val); | |
*hval ^= st_hash(hdata, sizeof(hdata), 0); | |
@@ -1713,7 +1696,6 @@ rb_hash_hash(VALUE hash) | |
static int | |
rb_hash_invert_i(VALUE key, VALUE value, VALUE hash) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
rb_hash_aset(hash, value, key); | |
return ST_CONTINUE; | |
} | |
@@ -1742,7 +1724,6 @@ rb_hash_invert(VALUE hash) | |
static int | |
rb_hash_update_i(VALUE key, VALUE value, VALUE hash) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
hash_update(hash, key); | |
st_insert(RHASH(hash)->ntbl, key, value); | |
return ST_CONTINUE; | |
@@ -1751,7 +1732,6 @@ rb_hash_update_i(VALUE key, VALUE value, VALUE hash) | |
static int | |
rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash) | |
{ | |
- if (key == Qundef) return ST_CONTINUE; | |
if (rb_hash_has_key(hash, key)) { | |
value = rb_yield_values(3, key, rb_hash_aref(hash, key), value); | |
} | |
@@ -1808,7 +1788,6 @@ rb_hash_update_func_i(VALUE key, VALUE value, VALUE arg0) | |
struct update_arg *arg = (struct update_arg *)arg0; | |
VALUE hash = arg->hash; | |
- if (key == Qundef) return ST_CONTINUE; | |
if (rb_hash_has_key(hash, key)) { | |
value = (*arg->func)(key, rb_hash_aref(hash, key), value); | |
} | |
@@ -1865,7 +1844,6 @@ assoc_i(VALUE key, VALUE val, VALUE arg) | |
{ | |
VALUE *args = (VALUE *)arg; | |
- if (key == Qundef) return ST_CONTINUE; | |
if (RTEST(rb_equal(args[0], key))) { | |
args[1] = rb_assoc_new(key, val); | |
return ST_STOP; | |
@@ -1903,7 +1881,6 @@ rassoc_i(VALUE key, VALUE val, VALUE arg) | |
{ | |
VALUE *args = (VALUE *)arg; | |
- if (key == Qundef) return ST_CONTINUE; | |
if (RTEST(rb_equal(args[0], val))) { | |
args[1] = rb_assoc_new(key, val); | |
return ST_STOP; | |
@@ -2200,7 +2177,7 @@ rb_env_path_tainted(void) | |
} | |
#if defined(_WIN32) || (defined(HAVE_SETENV) && defined(HAVE_UNSETENV)) | |
-#elif defined __sun__ | |
+#elif defined __sun | |
static int | |
in_origenv(const char *str) | |
{ | |
@@ -2288,7 +2265,7 @@ ruby_setenv(const char *name, const char *value) | |
rb_sys_fail("unsetenv"); | |
#endif | |
} | |
-#elif defined __sun__ | |
+#elif defined __sun | |
size_t len; | |
char **env_ptr, *str; | |
if (strchr(name, '=')) { | |
@@ -3086,11 +3063,9 @@ env_invert(void) | |
static int | |
env_replace_i(VALUE key, VALUE val, VALUE keys) | |
{ | |
- if (key != Qundef) { | |
- env_aset(Qnil, key, val); | |
- if (rb_ary_includes(keys, key)) { | |
- rb_ary_delete(keys, key); | |
- } | |
+ env_aset(Qnil, key, val); | |
+ if (rb_ary_includes(keys, key)) { | |
+ rb_ary_delete(keys, key); | |
} | |
return ST_CONTINUE; | |
} | |
@@ -3122,12 +3097,10 @@ env_replace(VALUE env, VALUE hash) | |
static int | |
env_update_i(VALUE key, VALUE val) | |
{ | |
- if (key != Qundef) { | |
- if (rb_block_given_p()) { | |
- val = rb_yield_values(3, key, rb_f_getenv(Qnil, key), val); | |
- } | |
- env_aset(Qnil, key, val); | |
+ if (rb_block_given_p()) { | |
+ val = rb_yield_values(3, key, rb_f_getenv(Qnil, key), val); | |
} | |
+ env_aset(Qnil, key, val); | |
return ST_CONTINUE; | |
} | |
@@ -3152,15 +3125,116 @@ env_update(VALUE env, VALUE hash) | |
} | |
/* | |
- * A <code>Hash</code> is a collection of key-value pairs. It is | |
- * similar to an <code>Array</code>, except that indexing is done via | |
- * arbitrary keys of any object type, not an integer index. Hashes enumerate | |
- * their values in the order that the corresponding keys were inserted. | |
+ * A Hash is a dictionary-like collection of unique keys and their values. | |
+ * Also called associative arrays, they are similar to Arrays, but where an | |
+ * Array uses integers as its index, a Hash allows you to use any object | |
+ * type. | |
+ * | |
+ * Hashes enumerate their values in the order that the corresponding keys | |
+ * were inserted. | |
+ * | |
+ * A Hash can be easily created by using its implicit form: | |
+ * | |
+ * grades = { "Jane Doe" => 10, "Jim Doe" => 6 } | |
+ * | |
+ * Hashes allow an alternate syntax form when your keys are always symbols. | |
+ * Instead of | |
+ * | |
+ * options = { :font_size => 10, :font_family => "Arial" } | |
+ * | |
+ * You could write it as: | |
+ * | |
+ * options = { font_size: 10, font_family: "Arial" } | |
+ * | |
+ * Each named key is a symbol you can access in hash: | |
+ * | |
+ * options[:font_size] # => 10 | |
+ * | |
+ * A Hash can also be created through its ::new method: | |
+ * | |
+ * grades = Hash.new | |
+ * grades["Dorothy Doe"] = 9 | |
* | |
* Hashes have a <em>default value</em> that is returned when accessing | |
- * keys that do not exist in the hash. By default, that value is | |
- * <code>nil</code>. | |
+ * keys that do not exist in the hash. If no default is set +nil+ is used. | |
+ * You can set the default value by sending it as an argument to Hash.new: | |
+ * | |
+ * grades = Hash.new(0) | |
+ * | |
+ * Or by using the #default= method: | |
+ * | |
+ * grades = {"Timmy Doe" => 8} | |
+ * grades.default = 0 | |
+ * | |
+ * Accessing a value in a Hash requires using its key: | |
+ * | |
+ * puts grades["Jane Doe"] # => 10 | |
+ * | |
+ * === Common Uses | |
+ * | |
+ * Hashes are an easy way to represent data structures, such as | |
+ * | |
+ * books = {} | |
+ * books[:matz] = "The Ruby Language" | |
+ * books[:black] = "The Well-Grounded Rubyist" | |
+ * | |
+ * Hashes are also commonly used as a way to have named parameters in | |
+ * functions. Note that no brackets are used below. If a hash is the last | |
+ * argument on a method call, no braces are needed, thus creating a really | |
+ * clean interface: | |
+ * | |
+ * Person.create(name: "John Doe", age: 27) | |
+ * | |
+ * def self.create(params) | |
+ * @name = params[:name] | |
+ * @age = params[:age] | |
+ * end | |
+ * | |
+ * === Hash Keys | |
+ * | |
+ * Two objects refer to the same hash key when their <code>hash</code> value | |
+ * is identical and the two objects are <code>eql?</code> to each other. | |
+ * | |
+ * A user-defined class may be used as a hash key if the <code>hash</code> | |
+ * and <code>eql?</code> methods are overridden to provide meaningful | |
+ * behavior. By default, separate instances refer to separate hash keys. | |
+ * | |
+ * A typical implementation of <code>hash</code> is based on the | |
+ * object's data while <code>eql?</code> is usually aliased to the overridden | |
+ * <code>==</code> method: | |
+ * | |
+ * class Book | |
+ * attr_reader :author, :title | |
+ * | |
+ * def initialize(author, title) | |
+ * @author = author | |
+ * @title = title | |
+ * end | |
+ * | |
+ * def ==(other) | |
+ * self.class === other and | |
+ * other.author == @author and | |
+ * other.title == @title | |
+ * end | |
+ * | |
+ * alias eql? == | |
+ * | |
+ * def hash | |
+ * @author.hash ^ @title.hash # XOR | |
+ * end | |
+ * end | |
+ * | |
+ * book1 = Book.new 'matz', 'Ruby in a Nutshell' | |
+ * book2 = Book.new 'matz', 'Ruby in a Nutshell' | |
+ * | |
+ * reviews = {} | |
+ * | |
+ * reviews[book1] = 'Great reference!' | |
+ * reviews[book2] = 'Nice and compact!' | |
+ * | |
+ * reviews.length #=> 1 | |
* | |
+ * See also Object#hash and Object#eql? | |
*/ | |
void | |
diff --git a/include/ruby/st.h b/include/ruby/st.h | |
index 68cc511..94646b6 100644 | |
--- a/include/ruby/st.h | |
+++ b/include/ruby/st.h | |
@@ -36,7 +36,7 @@ typedef unsigned long st_data_t; | |
#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP | |
typedef unsigned LONG_LONG st_data_t; | |
#else | |
-# error ---->> st.c requires sizeof(void*) == sizeof(long) to be compiled. <<---- | |
+# error ---->> st.c requires sizeof(void*) == sizeof(long) or sizeof(LONG_LONG) to be compiled. <<---- | |
#endif | |
#define ST_DATA_T_DEFINED | |
@@ -74,6 +74,11 @@ struct st_hash_type { | |
#define ST_INDEX_BITS (sizeof(st_index_t) * CHAR_BIT) | |
+typedef struct st_packed_entry { | |
+ st_index_t hash; | |
+ st_data_t key, val; | |
+} st_packed_entry; | |
+ | |
struct st_table { | |
const struct st_hash_type *type; | |
st_index_t num_bins; | |
@@ -91,8 +96,17 @@ struct st_table { | |
__extension__ | |
#endif | |
st_index_t num_entries : ST_INDEX_BITS - 1; | |
- struct st_table_entry **bins; | |
- struct st_table_entry *head, *tail; | |
+ union { | |
+ struct { | |
+ struct st_table_entry **bins; | |
+ struct st_table_entry *head, *tail; | |
+ } big; | |
+ struct { | |
+ struct st_packed_entry *entries; | |
+ st_index_t real_entries; | |
+ } packed; | |
+ st_packed_entry upacked; | |
+ } as; | |
}; | |
#define st_is_member(table,key) st_lookup((table),(key),(st_data_t *)0) | |
@@ -115,6 +129,7 @@ int st_insert2(st_table *, st_data_t, st_data_t, st_data_t (*)(st_data_t)); | |
int st_lookup(st_table *, st_data_t, st_data_t *); | |
int st_get_key(st_table *, st_data_t, st_data_t *); | |
int st_foreach(st_table *, int (*)(ANYARGS), st_data_t); | |
+int st_foreach_check(st_table *, int (*)(ANYARGS), st_data_t, st_data_t); | |
int st_reverse_foreach(st_table *, int (*)(ANYARGS), st_data_t); | |
void st_add_direct(st_table *, st_data_t, st_data_t); | |
void st_free_table(st_table *); | |
diff --git a/pool_alloc.h b/pool_alloc.h | |
new file mode 100644 | |
index 0000000..957708e | |
--- /dev/null | |
+++ b/pool_alloc.h | |
@@ -0,0 +1,11 @@ | |
+#ifndef POOL_ALLOC_H | |
+#define POOL_ALLOC_H | |
+ | |
+#define POOL_ALLOC_API | |
+#ifdef POOL_ALLOC_API | |
+void ruby_xpool_free(void *ptr); | |
+void *ruby_xpool_malloc_6p(); | |
+void *ruby_xpool_malloc_11p(); | |
+#endif | |
+ | |
+#endif | |
diff --git a/pool_alloc.inc.h b/pool_alloc.inc.h | |
new file mode 100644 | |
index 0000000..a7879ab | |
--- /dev/null | |
+++ b/pool_alloc.inc.h | |
@@ -0,0 +1,156 @@ | |
+/* | |
+ * this is generic pool allocator | |
+ * you should define following macroses: | |
+ * ITEM_NAME - unique identifier, which allows to hold functions in a namespace | |
+ * ITEM_TYPEDEF(name) - passed to typedef to localize item type | |
+ * free_entry - desired name of function for free entry | |
+ * alloc_entry - defired name of function for allocate entry | |
+ */ | |
+ | |
+#if POOL_ALLOC_PART == 1 | |
+#ifdef HEAP_ALIGN_LOG | |
+#define DEFAULT_POOL_SIZE (1 << HEAP_ALIGN_LOG) | |
+#else | |
+#define DEFAULT_POOL_SIZE (sizeof(void*) * 2048) | |
+#endif | |
+typedef unsigned int pool_holder_counter; | |
+ | |
+typedef struct pool_entry_list pool_entry_list; | |
+typedef struct pool_holder pool_holder; | |
+ | |
+typedef struct pool_header { | |
+ pool_holder *first; | |
+ pool_holder *_black_magick; | |
+ pool_holder_counter size; // size of entry in sizeof(void*) items | |
+ pool_holder_counter total; // size of entry in sizeof(void*) items | |
+} pool_header; | |
+ | |
+struct pool_holder { | |
+ pool_holder_counter free, total; | |
+ pool_header *header; | |
+ void *freep; | |
+ pool_holder *fore, *back; | |
+ void *data[1]; | |
+}; | |
+#define POOL_DATA_SIZE(pool_size) (((pool_size) - sizeof(void*) * 6 - offsetof(pool_holder, data)) / sizeof(void*)) | |
+#define POOL_ENTRY_SIZE(item_type) ((sizeof(item_type) - 1) / sizeof(void*) + 1) | |
+#define POOL_HOLDER_COUNT(pool_size, item_type) (POOL_DATA_SIZE(pool_size)/POOL_ENTRY_SIZE(item_type)) | |
+#define INIT_POOL(item_type) {NULL, NULL, POOL_ENTRY_SIZE(item_type), POOL_HOLDER_COUNT(DEFAULT_POOL_SIZE, item_type)} | |
+ | |
+#elif POOL_ALLOC_PART == 2 | |
+static pool_holder * | |
+pool_holder_alloc(pool_header *header) | |
+{ | |
+ pool_holder *holder; | |
+ pool_holder_counter i, size, count; | |
+ register void **ptr; | |
+ | |
+ size_t sz = offsetof(pool_holder, data) + | |
+ header->size * header->total * sizeof(void*); | |
+#define objspace (&rb_objspace) | |
+ vm_malloc_prepare(objspace, DEFAULT_POOL_SIZE); | |
+ if (header->first != NULL) return header->first; | |
+ TRY_WITH_GC(holder = (pool_holder*) aligned_malloc(DEFAULT_POOL_SIZE, sz)); | |
+ malloc_increase += DEFAULT_POOL_SIZE; | |
+#if CALC_EXACT_MALLOC_SIZE | |
+ objspace->malloc_params.allocated_size += DEFAULT_POOL_SIZE; | |
+ objspace->malloc_params.allocations++; | |
+#endif | |
+#undef objspace | |
+ | |
+ size = header->size; | |
+ count = header->total; | |
+ holder->free = count; | |
+ holder->total = count; | |
+ holder->header = header; | |
+ holder->fore = NULL; | |
+ holder->back = NULL; | |
+ holder->freep = &holder->data; | |
+ ptr = holder->data; | |
+ for(i = count - 1; i; i-- ) { | |
+ ptr = *ptr = ptr + size; | |
+ } | |
+ *ptr = NULL; | |
+ header->first = holder; | |
+ return holder; | |
+} | |
+ | |
+static inline void | |
+pool_holder_unchaing(pool_header *header, pool_holder *holder) | |
+{ | |
+ register pool_holder *fore = holder->fore, *back = holder->back; | |
+ holder->fore = NULL; | |
+ holder->back = NULL; | |
+ if (fore != NULL) fore->back = back; | |
+ else header->_black_magick = back; | |
+ if (back != NULL) back->fore = fore; | |
+ else header->first = fore; | |
+} | |
+ | |
+static inline pool_holder * | |
+entry_holder(void **entry) | |
+{ | |
+ return (pool_holder*)(((uintptr_t)entry) & ~(DEFAULT_POOL_SIZE - 1)); | |
+} | |
+ | |
+static inline void | |
+pool_free_entry(void **entry) | |
+{ | |
+ pool_holder *holder = entry_holder(entry); | |
+ pool_header *header = holder->header; | |
+ | |
+ if (holder->free++ == 0) { | |
+ register pool_holder *first = header->first; | |
+ if (first == NULL) { | |
+ header->first = holder; | |
+ } else { | |
+ holder->back = first; | |
+ holder->fore = first->fore; | |
+ first->fore = holder; | |
+ if (holder->fore) | |
+ holder->fore->back = holder; | |
+ else | |
+ header->_black_magick = holder; | |
+ } | |
+ } else if (holder->free == holder->total && header->first != holder ) { | |
+ pool_holder_unchaing(header, holder); | |
+ aligned_free(holder); | |
+#if CALC_EXACT_MALLOC_SIZE | |
+ rb_objspace.malloc_params.allocated_size -= DEFAULT_POOL_SIZE; | |
+ rb_objspace.malloc_params.allocations--; | |
+#endif | |
+ return; | |
+ } | |
+ | |
+ *entry = holder->freep; | |
+ holder->freep = entry; | |
+} | |
+ | |
+static inline void* | |
+pool_alloc_entry(pool_header *header) | |
+{ | |
+ pool_holder *holder = header->first; | |
+ void **result; | |
+ if (holder == NULL) { | |
+ holder = pool_holder_alloc(header); | |
+ } | |
+ | |
+ result = holder->freep; | |
+ holder->freep = *result; | |
+ | |
+ if (--holder->free == 0) { | |
+ pool_holder_unchaing(header, holder); | |
+ } | |
+ | |
+ return result; | |
+} | |
+ | |
+static void | |
+pool_finalize_header(pool_header *header) | |
+{ | |
+ if (header->first) { | |
+ aligned_free(header->first); | |
+ header->first = NULL; | |
+ } | |
+} | |
+#endif | |
diff --git a/st.c b/st.c | |
index 8c238c3..5a682cd 100644 | |
--- a/st.c | |
+++ b/st.c | |
@@ -7,6 +7,7 @@ | |
#include "st.h" | |
#else | |
#include "ruby/ruby.h" | |
+#include "pool_alloc.h" | |
#endif | |
#include <stdio.h> | |
@@ -25,8 +26,17 @@ struct st_table_entry { | |
st_table_entry *fore, *back; | |
}; | |
-#define ST_DEFAULT_MAX_DENSITY 5 | |
+#define STATIC_ASSERT(name, expr) typedef int static_assert_##name##_check[(expr) ? 1 : -1]; | |
+ | |
+#define ST_DEFAULT_MAX_DENSITY 2 | |
#define ST_DEFAULT_INIT_TABLE_SIZE 11 | |
+#define ST_DEFAULT_SECOND_TABLE_SIZE 19 | |
+#define ST_DEFAULT_PACKED_TABLE_SIZE 19 | |
+#define PACKED_UNIT (int)(sizeof(st_packed_entry) / sizeof(st_table_entry*)) | |
+#define MAX_PACKED_HASH (int)(ST_DEFAULT_PACKED_TABLE_SIZE * sizeof(st_table_entry*) / sizeof(st_packed_entry)) | |
+ | |
+STATIC_ASSERT(st_packed_entry, sizeof(st_packed_entry) == sizeof(st_table_entry*[PACKED_UNIT])) | |
+STATIC_ASSERT(st_packed_bins, sizeof(st_packed_entry[MAX_PACKED_HASH]) <= sizeof(st_table_entry*[ST_DEFAULT_PACKED_TABLE_SIZE])) | |
/* | |
* DEFAULT_MAX_DENSITY is the default for the largest we allow the | |
@@ -38,7 +48,8 @@ struct st_table_entry { | |
* | |
*/ | |
-static const struct st_hash_type type_numhash = { | |
+#define type_numhash st_hashtype_num | |
+const struct st_hash_type st_hashtype_num = { | |
st_numcmp, | |
st_numhash, | |
}; | |
@@ -61,20 +72,128 @@ static void rehash(st_table *); | |
#ifdef RUBY | |
#define malloc xmalloc | |
#define calloc xcalloc | |
+#define realloc xrealloc | |
#define free(x) xfree(x) | |
#endif | |
#define numberof(array) (int)(sizeof(array) / sizeof((array)[0])) | |
-#define alloc(type) (type*)malloc((size_t)sizeof(type)) | |
-#define Calloc(n,s) (char*)calloc((n),(s)) | |
- | |
#define EQUAL(table,x,y) ((x)==(y) || (*(table)->type->compare)((x),(y)) == 0) | |
-/* remove cast to unsigned int in the future */ | |
-#define do_hash(key,table) (unsigned int)(st_index_t)(*(table)->type->hash)((key)) | |
+#define do_hash(key,table) (st_index_t)(*(table)->type->hash)((key)) | |
#define do_hash_bin(key,table) (do_hash((key), (table))%(table)->num_bins) | |
+/* preparation for possible allocation improvements */ | |
+#ifdef POOL_ALLOC_API | |
+#define st_alloc_entry() (st_table_entry *)ruby_xpool_malloc_6p() | |
+#define st_free_entry(entry) ruby_xpool_free(entry) | |
+#define st_alloc_table() (st_table *)ruby_xpool_malloc_6p() | |
+#define st_dealloc_table(table) ruby_xpool_free(table) | |
+static inline st_table_entry ** | |
+st_alloc_bins(st_index_t size) | |
+{ | |
+ st_table_entry **result; | |
+ if (size == 11) { | |
+ result = (st_table_entry **) ruby_xpool_malloc_11p(); | |
+ memset(result, 0, 11 * sizeof(st_table_entry *)); | |
+ } | |
+ else | |
+ result = (st_table_entry **) ruby_xcalloc(size, sizeof(st_table_entry*)); | |
+ return result; | |
+} | |
+static inline void | |
+st_free_bins(st_table_entry **bins, st_index_t size) | |
+{ | |
+ if (size == 11) | |
+ ruby_xpool_free(bins); | |
+ else | |
+ ruby_xfree(bins); | |
+} | |
+static inline st_table_entry** | |
+st_realloc_bins(st_table_entry **bins, st_index_t newsize, st_index_t oldsize) | |
+{ | |
+ st_table_entry **new_bins = st_alloc_bins(newsize); | |
+ st_free_bins(bins, oldsize); | |
+ return new_bins; | |
+} | |
+#else | |
+#define st_alloc_entry() (st_table_entry *)malloc(sizeof(st_table_entry)) | |
+#define st_free_entry(entry) free(entry) | |
+#define st_alloc_table() (st_table *)malloc(sizeof(st_table)) | |
+#define st_dealloc_table(table) free(table) | |
+#define st_alloc_bins(size) (st_table_entry **)calloc(size, sizeof(st_table_entry *)) | |
+#define st_free_bins(bins, size) free(bins) | |
+static inline st_table_entry** | |
+st_realloc_bins(st_table_entry **bins, st_index_t newsize, st_index_t oldsize) | |
+{ | |
+ bins = (st_table_entry **)realloc(bins, newsize * sizeof(st_table_entry *)); | |
+ MEMZERO(bins, st_table_entry*, newsize); | |
+ return bins; | |
+} | |
+#endif | |
+ | |
+/* Shortage */ | |
+#define bins as.big.bins | |
+#define head as.big.head | |
+#define tail as.big.tail | |
+#define real_entries as.packed.real_entries | |
+ | |
+/* preparation for possible packing improvements */ | |
+#define PACKED_BINS(table) ((table)->as.packed.entries) | |
+#define PACKED_ENT(table, i) PACKED_BINS(table)[i] | |
+#define PKEY(table, i) PACKED_ENT((table), (i)).key | |
+#define PVAL(table, i) PACKED_ENT((table), (i)).val | |
+#define PHASH(table, i) PACKED_ENT((table), (i)).hash | |
+#define PKEY_SET(table, i, v) (PKEY((table), (i)) = (v)) | |
+#define PVAL_SET(table, i, v) (PVAL((table), (i)) = (v)) | |
+#define PHASH_SET(table, i, v) (PHASH((table), (i)) = (v)) | |
+ | |
+/* this function depends much on packed layout, so that it placed here */ | |
+static inline void | |
+remove_packed_entry(st_table *table, st_index_t i) | |
+{ | |
+ table->real_entries--; | |
+ table->num_entries--; | |
+ if (i < table->real_entries) { | |
+ MEMMOVE(&PACKED_ENT(table, i), &PACKED_ENT(table, i+1), | |
+ st_packed_entry, table->real_entries - i); | |
+ } | |
+} | |
+ | |
+static inline void | |
+remove_safe_packed_entry(st_table *table, st_index_t i, st_data_t never) | |
+{ | |
+ table->num_entries--; | |
+ PKEY_SET(table, i, never); | |
+ PVAL_SET(table, i, never); | |
+ PHASH_SET(table, i, 0); | |
+} | |
+ | |
+/* ultrapacking */ | |
+#define real_upacked num_bins | |
+#define MAX_UPACKED_HASH 1 | |
+#define ULTRAPACKED(table) ((table)->real_upacked <= 1) | |
+#define UPACKED_ENT(table) ((table)->as.upacked) | |
+#define UPKEY(table) UPACKED_ENT(table).key | |
+#define UPVAL(table) UPACKED_ENT(table).val | |
+#define UPHASH(table) UPACKED_ENT(table).hash | |
+#define UPKEY_SET(table, v) (UPACKED_ENT(table).key = (v)) | |
+#define UPVAL_SET(table, v) (UPACKED_ENT(table).val = (v)) | |
+#define UPHASH_SET(table, v) (UPACKED_ENT(table).hash = (v)) | |
+static inline void | |
+remove_upacked_entry(st_table *table) | |
+{ | |
+ table->real_upacked = table->num_entries = 0; | |
+} | |
+ | |
+static inline void | |
+remove_safe_upacked_entry(st_table *table, st_data_t never) | |
+{ | |
+ table->num_entries = 0; | |
+ UPKEY_SET(table, never); | |
+ UPVAL_SET(table, never); | |
+ UPHASH_SET(table, 0); | |
+} | |
/* | |
* MINSIZE is the minimum size of a dictionary. | |
*/ | |
@@ -85,8 +204,8 @@ static void rehash(st_table *); | |
Table of prime numbers 2^n+a, 2<=n<=30. | |
*/ | |
static const unsigned int primes[] = { | |
- 8 + 3, | |
- 16 + 3, | |
+ ST_DEFAULT_INIT_TABLE_SIZE, | |
+ ST_DEFAULT_SECOND_TABLE_SIZE, | |
32 + 5, | |
64 + 3, | |
128 + 3, | |
@@ -161,8 +280,6 @@ stat_col(void) | |
} | |
#endif | |
-#define MAX_PACKED_NUMHASH (ST_DEFAULT_INIT_TABLE_SIZE/2) | |
- | |
st_table* | |
st_init_table_with_size(const struct st_hash_type *type, st_index_t size) | |
{ | |
@@ -181,14 +298,19 @@ st_init_table_with_size(const struct st_hash_type *type, st_index_t size) | |
} | |
#endif | |
- size = new_size(size); /* round up to prime number */ | |
- tbl = alloc(st_table); | |
+ tbl = st_alloc_table(); | |
tbl->type = type; | |
tbl->num_entries = 0; | |
- tbl->entries_packed = type == &type_numhash && size/2 <= MAX_PACKED_NUMHASH; | |
+ tbl->entries_packed = size <= MAX_PACKED_HASH; | |
+ if (tbl->entries_packed) { | |
+ size = size <= MAX_UPACKED_HASH ? 0 : ST_DEFAULT_PACKED_TABLE_SIZE; | |
+ } | |
+ else { | |
+ size = new_size(size); /* round up to prime number */ | |
+ } | |
tbl->num_bins = size; | |
- tbl->bins = (st_table_entry **)Calloc(size, sizeof(st_table_entry*)); | |
+ tbl->bins = size ? st_alloc_bins(size) : 0; | |
tbl->head = 0; | |
tbl->tail = 0; | |
@@ -243,17 +365,23 @@ st_clear(st_table *table) | |
register st_table_entry *ptr, *next; | |
st_index_t i; | |
+ if (ULTRAPACKED(table)) { | |
+ remove_upacked_entry(table); | |
+ return; | |
+ } | |
+ | |
if (table->entries_packed) { | |
table->num_entries = 0; | |
+ table->real_entries = 0; | |
return; | |
} | |
- for(i = 0; i < table->num_bins; i++) { | |
+ for (i = 0; i < table->num_bins; i++) { | |
ptr = table->bins[i]; | |
table->bins[i] = 0; | |
while (ptr != 0) { | |
next = ptr->next; | |
- free(ptr); | |
+ st_free_entry(ptr); | |
ptr = next; | |
} | |
} | |
@@ -266,14 +394,19 @@ void | |
st_free_table(st_table *table) | |
{ | |
st_clear(table); | |
- free(table->bins); | |
- free(table); | |
+ if (!ULTRAPACKED(table)) { | |
+ st_free_bins(table->bins, table->num_bins); | |
+ } | |
+ st_dealloc_table(table); | |
} | |
size_t | |
st_memsize(const st_table *table) | |
{ | |
- if (table->entries_packed) { | |
+ if (ULTRAPACKED(table)) { | |
+ return sizeof(st_table); | |
+ } | |
+ else if (table->entries_packed) { | |
return table->num_bins * sizeof (void *) + sizeof(st_table); | |
} | |
else { | |
@@ -306,46 +439,77 @@ count_collision(const struct st_hash_type *type) | |
#define FOUND_ENTRY | |
#endif | |
-#define FIND_ENTRY(table, ptr, hash_val, bin_pos) do {\ | |
- (bin_pos) = (hash_val)%(table)->num_bins;\ | |
- (ptr) = (table)->bins[(bin_pos)];\ | |
- FOUND_ENTRY;\ | |
- if (PTR_NOT_EQUAL((table), (ptr), (hash_val), key)) {\ | |
- COLLISION;\ | |
- while (PTR_NOT_EQUAL((table), (ptr)->next, (hash_val), key)) {\ | |
- (ptr) = (ptr)->next;\ | |
- }\ | |
- (ptr) = (ptr)->next;\ | |
- }\ | |
-} while (0) | |
+#define FIND_ENTRY(table, ptr, hash_val, bin_pos) \ | |
+ ((ptr) = find_entry((table), key, (hash_val), ((bin_pos) = (hash_val)%(table)->num_bins))) | |
+ | |
+static st_table_entry * | |
+find_entry(st_table *table, st_data_t key, st_index_t hash_val, st_index_t bin_pos) | |
+{ | |
+ register st_table_entry *ptr = table->bins[bin_pos]; | |
+ FOUND_ENTRY; | |
+ if (PTR_NOT_EQUAL(table, ptr, hash_val, key)) { | |
+ COLLISION; | |
+ while (PTR_NOT_EQUAL(table, ptr->next, hash_val, key)) { | |
+ ptr = ptr->next; | |
+ } | |
+ ptr = ptr->next; | |
+ } | |
+ return ptr; | |
+} | |
+ | |
+static inline st_index_t | |
+find_packed_index(st_table *table, st_index_t hash_val, st_data_t key) | |
+{ | |
+ st_index_t i = 0; | |
+ while (i < table->real_entries && | |
+ (PHASH(table, i) != hash_val || !EQUAL(table, key, PKEY(table, i)))) { | |
+ i++; | |
+ } | |
+ return i; | |
+} | |
+ | |
+static inline int | |
+check_upacked(st_table *table, st_index_t hash_val, st_data_t key) | |
+{ | |
+ return table->num_entries && | |
+ UPHASH(table) == hash_val && | |
+ EQUAL(table, key, UPKEY(table)); | |
+} | |
#define collision_check 0 | |
int | |
st_lookup(st_table *table, register st_data_t key, st_data_t *value) | |
{ | |
- st_index_t hash_val, bin_pos; | |
+ st_index_t hash_val; | |
register st_table_entry *ptr; | |
+ hash_val = do_hash(key, table); | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ if (check_upacked(table, hash_val, key)) { | |
+ if (value != 0) *value = UPVAL(table); | |
+ return 1; | |
+ } | |
+ return 0; | |
+ } | |
+ | |
if (table->entries_packed) { | |
- st_index_t i; | |
- for (i = 0; i < table->num_entries; i++) { | |
- if ((st_data_t)table->bins[i*2] == key) { | |
- if (value !=0) *value = (st_data_t)table->bins[i*2+1]; | |
- return 1; | |
- } | |
- } | |
+ st_index_t i = find_packed_index(table, hash_val, key); | |
+ if (i < table->real_entries) { | |
+ if (value != 0) *value = PVAL(table, i); | |
+ return 1; | |
+ } | |
return 0; | |
} | |
- hash_val = do_hash(key, table); | |
- FIND_ENTRY(table, ptr, hash_val, bin_pos); | |
+ ptr = find_entry(table, key, hash_val, hash_val % table->num_bins); | |
if (ptr == 0) { | |
return 0; | |
} | |
else { | |
- if (value != 0) *value = ptr->record; | |
+ if (value != 0) *value = ptr->record; | |
return 1; | |
} | |
} | |
@@ -353,22 +517,29 @@ st_lookup(st_table *table, register st_data_t key, st_data_t *value) | |
int | |
st_get_key(st_table *table, register st_data_t key, st_data_t *result) | |
{ | |
- st_index_t hash_val, bin_pos; | |
+ st_index_t hash_val; | |
register st_table_entry *ptr; | |
+ hash_val = do_hash(key, table); | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ if (check_upacked(table, hash_val, key)) { | |
+ if (result != 0) *result = UPKEY(table); | |
+ return 1; | |
+ } | |
+ return 0; | |
+ } | |
+ | |
if (table->entries_packed) { | |
- st_index_t i; | |
- for (i = 0; i < table->num_entries; i++) { | |
- if ((st_data_t)table->bins[i*2] == key) { | |
- if (result !=0) *result = (st_data_t)table->bins[i*2]; | |
- return 1; | |
- } | |
- } | |
+ st_index_t i = find_packed_index(table, hash_val, key); | |
+ if (i < table->real_entries) { | |
+ if (result != 0) *result = PKEY(table, i); | |
+ return 1; | |
+ } | |
return 0; | |
} | |
- hash_val = do_hash(key, table); | |
- FIND_ENTRY(table, ptr, hash_val, bin_pos); | |
+ ptr = find_entry(table, key, hash_val, hash_val % table->num_bins); | |
if (ptr == 0) { | |
return 0; | |
@@ -382,85 +553,151 @@ st_get_key(st_table *table, register st_data_t key, st_data_t *result) | |
#undef collision_check | |
#define collision_check 1 | |
-#define MORE_PACKABLE_P(table) \ | |
- ((st_index_t)((table)->num_entries+1) * 2 <= (table)->num_bins && \ | |
- (table)->num_entries+1 <= MAX_PACKED_NUMHASH) | |
- | |
-#define ADD_DIRECT(table, key, value, hash_val, bin_pos)\ | |
-do {\ | |
- st_table_entry *entry;\ | |
- if ((table)->num_entries > ST_DEFAULT_MAX_DENSITY * (table)->num_bins) {\ | |
- rehash(table);\ | |
- (bin_pos) = (hash_val) % (table)->num_bins;\ | |
- }\ | |
- \ | |
- entry = alloc(st_table_entry);\ | |
- \ | |
- entry->hash = (hash_val);\ | |
- entry->key = (key);\ | |
- entry->record = (value);\ | |
- entry->next = (table)->bins[(bin_pos)];\ | |
- if ((table)->head != 0) {\ | |
- entry->fore = 0;\ | |
- (entry->back = (table)->tail)->fore = entry;\ | |
- (table)->tail = entry;\ | |
- }\ | |
- else {\ | |
- (table)->head = (table)->tail = entry;\ | |
- entry->fore = entry->back = 0;\ | |
- }\ | |
- (table)->bins[(bin_pos)] = entry;\ | |
- (table)->num_entries++;\ | |
-} while (0) | |
+static inline st_table_entry * | |
+new_entry(st_table * table, st_data_t key, st_data_t value, | |
+ st_index_t hash_val, register st_index_t bin_pos) | |
+{ | |
+ register st_table_entry *entry = st_alloc_entry(); | |
+ | |
+ entry->next = table->bins[bin_pos]; | |
+ table->bins[bin_pos] = entry; | |
+ entry->hash = hash_val; | |
+ entry->key = key; | |
+ entry->record = value; | |
+ | |
+ return entry; | |
+} | |
+ | |
+static inline void | |
+add_direct(st_table *table, st_data_t key, st_data_t value, | |
+ st_index_t hash_val, register st_index_t bin_pos) | |
+{ | |
+ register st_table_entry *entry; | |
+ if (table->num_entries > ST_DEFAULT_MAX_DENSITY * table->num_bins) { | |
+ rehash(table); | |
+ bin_pos = hash_val % table->num_bins; | |
+ } | |
+ | |
+ entry = new_entry(table, key, value, hash_val, bin_pos); | |
+ | |
+ if (table->head != 0) { | |
+ entry->fore = 0; | |
+ (entry->back = table->tail)->fore = entry; | |
+ table->tail = entry; | |
+ } | |
+ else { | |
+ table->head = table->tail = entry; | |
+ entry->fore = entry->back = 0; | |
+ } | |
+ table->num_entries++; | |
+} | |
static void | |
unpack_entries(register st_table *table) | |
{ | |
st_index_t i; | |
- struct st_table_entry *packed_bins[MAX_PACKED_NUMHASH*2]; | |
+ st_packed_entry packed_bins[MAX_PACKED_HASH]; | |
+ register st_table_entry *entry, *preventry = 0, **chain; | |
st_table tmp_table = *table; | |
- memcpy(packed_bins, table->bins, sizeof(struct st_table_entry *) * table->num_entries*2); | |
- table->bins = packed_bins; | |
+ MEMCPY(packed_bins, PACKED_BINS(table), st_packed_entry, MAX_PACKED_HASH); | |
+ table->as.packed.entries = packed_bins; | |
tmp_table.entries_packed = 0; | |
- tmp_table.num_entries = 0; | |
- memset(tmp_table.bins, 0, sizeof(struct st_table_entry *) * tmp_table.num_bins); | |
- for (i = 0; i < table->num_entries; i++) { | |
- st_insert(&tmp_table, (st_data_t)packed_bins[i*2], (st_data_t)packed_bins[i*2+1]); | |
- } | |
+#if ST_DEFAULT_INIT_TABLE_SIZE == ST_DEFAULT_PACKED_TABLE_SIZE | |
+ MEMZERO(tmp_table.bins, st_table_entry*, tmp_table.num_bins); | |
+#else | |
+ tmp_table.bins = st_realloc_bins(tmp_table.bins, ST_DEFAULT_INIT_TABLE_SIZE, tmp_table.num_bins); | |
+ tmp_table.num_bins = ST_DEFAULT_INIT_TABLE_SIZE; | |
+#endif | |
+ i = 0; | |
+ chain = &tmp_table.head; | |
+ do { | |
+ st_data_t key = packed_bins[i].key; | |
+ st_data_t val = packed_bins[i].val; | |
+ st_index_t hash = packed_bins[i].hash; | |
+ entry = new_entry(&tmp_table, key, val, hash, | |
+ hash % ST_DEFAULT_INIT_TABLE_SIZE); | |
+ *chain = entry; | |
+ entry->back = preventry; | |
+ preventry = entry; | |
+ chain = &entry->fore; | |
+ } while (++i < MAX_PACKED_HASH); | |
+ *chain = NULL; | |
+ tmp_table.tail = entry; | |
*table = tmp_table; | |
} | |
+static void | |
+add_packed_direct(st_table *table, st_data_t key, st_data_t value, st_index_t hash_val) | |
+{ | |
+ if (table->real_entries < MAX_PACKED_HASH) { | |
+ st_index_t i = table->real_entries++; | |
+ PKEY_SET(table, i, key); | |
+ PVAL_SET(table, i, value); | |
+ PHASH_SET(table, i, hash_val); | |
+ table->num_entries++; | |
+ } | |
+ else { | |
+ unpack_entries(table); | |
+ add_direct(table, key, value, hash_val, hash_val % table->num_bins); | |
+ } | |
+} | |
+ | |
+static void | |
+add_upacked_direct(register st_table *table, register st_data_t key, st_data_t value, st_index_t hash_val) | |
+{ | |
+ if (table->real_upacked) { | |
+ st_packed_entry *entries = (st_packed_entry *) st_alloc_bins(ST_DEFAULT_PACKED_TABLE_SIZE); | |
+ entries[0] = UPACKED_ENT(table); | |
+ entries[1].hash = hash_val; | |
+ entries[1].key = key; | |
+ entries[1].val = value; | |
+ table->num_bins = ST_DEFAULT_PACKED_TABLE_SIZE; | |
+ table->real_entries = 2; | |
+ table->num_entries++; | |
+ table->as.packed.entries = entries; | |
+ } | |
+ else { | |
+ table->real_upacked = 1; | |
+ table->num_entries = 1; | |
+ UPHASH_SET(table, hash_val); | |
+ UPKEY_SET(table, key); | |
+ UPVAL_SET(table, value); | |
+ } | |
+} | |
+ | |
int | |
st_insert(register st_table *table, register st_data_t key, st_data_t value) | |
{ | |
- st_index_t hash_val, bin_pos; | |
+ st_index_t hash_val; | |
+ register st_index_t bin_pos; | |
register st_table_entry *ptr; | |
+ hash_val = do_hash(key, table); | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ if (check_upacked(table, hash_val, key)) { | |
+ UPVAL_SET(table, value); | |
+ return 1; | |
+ } | |
+ add_upacked_direct(table, key, value, hash_val); | |
+ return 0; | |
+ } | |
+ | |
if (table->entries_packed) { | |
- st_index_t i; | |
- for (i = 0; i < table->num_entries; i++) { | |
- if ((st_data_t)table->bins[i*2] == key) { | |
- table->bins[i*2+1] = (struct st_table_entry*)value; | |
- return 1; | |
- } | |
- } | |
- if (MORE_PACKABLE_P(table)) { | |
- i = table->num_entries++; | |
- table->bins[i*2] = (struct st_table_entry*)key; | |
- table->bins[i*2+1] = (struct st_table_entry*)value; | |
- return 0; | |
- } | |
- else { | |
- unpack_entries(table); | |
+ st_index_t i = find_packed_index(table, hash_val, key); | |
+ if (i < table->real_entries) { | |
+ PVAL_SET(table, i, value); | |
+ return 1; | |
} | |
+ add_packed_direct(table, key, value, hash_val); | |
+ return 0; | |
} | |
- hash_val = do_hash(key, table); | |
FIND_ENTRY(table, ptr, hash_val, bin_pos); | |
if (ptr == 0) { | |
- ADD_DIRECT(table, key, value, hash_val, bin_pos); | |
+ add_direct(table, key, value, hash_val, bin_pos); | |
return 0; | |
} | |
else { | |
@@ -473,34 +710,38 @@ int | |
st_insert2(register st_table *table, register st_data_t key, st_data_t value, | |
st_data_t (*func)(st_data_t)) | |
{ | |
- st_index_t hash_val, bin_pos; | |
+ st_index_t hash_val; | |
+ register st_index_t bin_pos; | |
register st_table_entry *ptr; | |
+ hash_val = do_hash(key, table); | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ if (check_upacked(table, hash_val, key)) { | |
+ UPVAL_SET(table, value); | |
+ return 1; | |
+ } | |
+ key = (*func)(key); | |
+ add_upacked_direct(table, key, value, hash_val); | |
+ return 0; | |
+ } | |
+ | |
if (table->entries_packed) { | |
- st_index_t i; | |
- for (i = 0; i < table->num_entries; i++) { | |
- if ((st_data_t)table->bins[i*2] == key) { | |
- table->bins[i*2+1] = (struct st_table_entry*)value; | |
- return 1; | |
- } | |
- } | |
- if (MORE_PACKABLE_P(table)) { | |
- i = table->num_entries++; | |
- table->bins[i*2] = (struct st_table_entry*)key; | |
- table->bins[i*2+1] = (struct st_table_entry*)value; | |
- return 0; | |
- } | |
- else { | |
- unpack_entries(table); | |
- } | |
+ st_index_t i = find_packed_index(table, hash_val, key); | |
+ if (i < table->real_entries) { | |
+ PVAL_SET(table, i, value); | |
+ return 1; | |
+ } | |
+ key = (*func)(key); | |
+ add_packed_direct(table, key, value, hash_val); | |
+ return 0; | |
} | |
- hash_val = do_hash(key, table); | |
FIND_ENTRY(table, ptr, hash_val, bin_pos); | |
if (ptr == 0) { | |
key = (*func)(key); | |
- ADD_DIRECT(table, key, value, hash_val, bin_pos); | |
+ add_direct(table, key, value, hash_val, bin_pos); | |
return 0; | |
} | |
else { | |
@@ -512,36 +753,30 @@ st_insert2(register st_table *table, register st_data_t key, st_data_t value, | |
void | |
st_add_direct(st_table *table, st_data_t key, st_data_t value) | |
{ | |
- st_index_t hash_val, bin_pos; | |
+ st_index_t hash_val; | |
+ | |
+ hash_val = do_hash(key, table); | |
+ if (ULTRAPACKED(table)) { | |
+ add_upacked_direct(table, key, value, hash_val); | |
+ return; | |
+ } | |
if (table->entries_packed) { | |
- st_index_t i; | |
- if (MORE_PACKABLE_P(table)) { | |
- i = table->num_entries++; | |
- table->bins[i*2] = (struct st_table_entry*)key; | |
- table->bins[i*2+1] = (struct st_table_entry*)value; | |
- return; | |
- } | |
- else { | |
- unpack_entries(table); | |
- } | |
+ add_packed_direct(table, key, value, hash_val); | |
+ return; | |
} | |
- hash_val = do_hash(key, table); | |
- bin_pos = hash_val % table->num_bins; | |
- ADD_DIRECT(table, key, value, hash_val, bin_pos); | |
+ add_direct(table, key, value, hash_val, hash_val % table->num_bins); | |
} | |
static void | |
rehash(register st_table *table) | |
{ | |
register st_table_entry *ptr, **new_bins; | |
- st_index_t i, new_num_bins, hash_val; | |
+ st_index_t new_num_bins, hash_val; | |
new_num_bins = new_size(table->num_bins+1); | |
- new_bins = (st_table_entry**) | |
- xrealloc(table->bins, new_num_bins * sizeof(st_table_entry*)); | |
- for (i = 0; i < new_num_bins; ++i) new_bins[i] = 0; | |
+ new_bins = st_realloc_bins(table->bins, new_num_bins, table->num_bins); | |
table->num_bins = new_num_bins; | |
table->bins = new_bins; | |
@@ -558,34 +793,37 @@ st_table* | |
st_copy(st_table *old_table) | |
{ | |
st_table *new_table; | |
- st_table_entry *ptr, *entry, *prev, **tail; | |
+ st_table_entry *ptr, *entry, *prev, **tailp; | |
st_index_t num_bins = old_table->num_bins; | |
st_index_t hash_val; | |
- new_table = alloc(st_table); | |
+ new_table = st_alloc_table(); | |
if (new_table == 0) { | |
return 0; | |
} | |
*new_table = *old_table; | |
- new_table->bins = (st_table_entry**) | |
- Calloc((unsigned)num_bins, sizeof(st_table_entry*)); | |
+ if (ULTRAPACKED(old_table)) { | |
+ return new_table; | |
+ } | |
+ | |
+ new_table->bins = st_alloc_bins(num_bins); | |
if (new_table->bins == 0) { | |
- free(new_table); | |
+ st_dealloc_table(new_table); | |
return 0; | |
} | |
if (old_table->entries_packed) { | |
- memcpy(new_table->bins, old_table->bins, sizeof(struct st_table_entry *) * old_table->num_bins); | |
+ MEMCPY(new_table->bins, old_table->bins, st_table_entry*, old_table->num_bins); | |
return new_table; | |
} | |
if ((ptr = old_table->head) != 0) { | |
prev = 0; | |
- tail = &new_table->head; | |
+ tailp = &new_table->head; | |
do { | |
- entry = alloc(st_table_entry); | |
+ entry = st_alloc_entry(); | |
if (entry == 0) { | |
st_free_table(new_table); | |
return 0; | |
@@ -595,8 +833,8 @@ st_copy(st_table *old_table) | |
entry->next = new_table->bins[hash_val]; | |
new_table->bins[hash_val] = entry; | |
entry->back = prev; | |
- *tail = prev = entry; | |
- tail = &entry->fore; | |
+ *tailp = prev = entry; | |
+ tailp = &entry->fore; | |
} while ((ptr = ptr->fore) != 0); | |
new_table->tail = prev; | |
} | |
@@ -604,21 +842,22 @@ st_copy(st_table *old_table) | |
return new_table; | |
} | |
-#define REMOVE_ENTRY(table, ptr) do \ | |
- { \ | |
- if ((ptr)->fore == 0 && (ptr)->back == 0) { \ | |
- (table)->head = 0; \ | |
- (table)->tail = 0; \ | |
- } \ | |
- else { \ | |
- st_table_entry *fore = (ptr)->fore, *back = (ptr)->back; \ | |
- if (fore) fore->back = back; \ | |
- if (back) back->fore = fore; \ | |
- if ((ptr) == (table)->head) (table)->head = fore; \ | |
- if ((ptr) == (table)->tail) (table)->tail = back; \ | |
- } \ | |
- (table)->num_entries--; \ | |
- } while (0) | |
+static inline void | |
+remove_entry(st_table *table, st_table_entry *ptr) | |
+{ | |
+ if (ptr->fore == 0 && ptr->back == 0) { | |
+ table->head = 0; | |
+ table->tail = 0; | |
+ } | |
+ else { | |
+ st_table_entry *fore = ptr->fore, *back = ptr->back; | |
+ if (fore) fore->back = back; | |
+ if (back) back->fore = fore; | |
+ if (ptr == table->head) table->head = fore; | |
+ if (ptr == table->tail) table->tail = back; | |
+ } | |
+ table->num_entries--; | |
+} | |
int | |
st_delete(register st_table *table, register st_data_t *key, st_data_t *value) | |
@@ -627,30 +866,38 @@ st_delete(register st_table *table, register st_data_t *key, st_data_t *value) | |
st_table_entry **prev; | |
register st_table_entry *ptr; | |
+ hash_val = do_hash(*key, table); | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ if (check_upacked(table, hash_val, *key)) { | |
+ if (value != 0) *value = UPVAL(table); | |
+ *key = UPKEY(table); | |
+ remove_upacked_entry(table); | |
+ return 1; | |
+ } | |
+ return 0; | |
+ } | |
+ | |
if (table->entries_packed) { | |
- st_index_t i; | |
- for (i = 0; i < table->num_entries; i++) { | |
- if ((st_data_t)table->bins[i*2] == *key) { | |
- if (value != 0) *value = (st_data_t)table->bins[i*2+1]; | |
- table->num_entries--; | |
- memmove(&table->bins[i*2], &table->bins[(i+1)*2], | |
- sizeof(struct st_table_entry*) * 2*(table->num_entries-i)); | |
- return 1; | |
- } | |
+ st_index_t i = find_packed_index(table, hash_val, *key); | |
+ if (i < table->real_entries) { | |
+ if (value != 0) *value = PVAL(table, i); | |
+ *key = PKEY(table, i); | |
+ remove_packed_entry(table, i); | |
+ return 1; | |
} | |
if (value != 0) *value = 0; | |
return 0; | |
} | |
- hash_val = do_hash_bin(*key, table); | |
- | |
- for (prev = &table->bins[hash_val]; (ptr = *prev) != 0; prev = &ptr->next) { | |
+ prev = &table->bins[hash_val % table->num_bins]; | |
+ for (;(ptr = *prev) != 0; prev = &ptr->next) { | |
if (EQUAL(table, *key, ptr->key)) { | |
*prev = ptr->next; | |
- REMOVE_ENTRY(table, ptr); | |
+ remove_entry(table, ptr); | |
if (value != 0) *value = ptr->record; | |
*key = ptr->key; | |
- free(ptr); | |
+ st_free_entry(ptr); | |
return 1; | |
} | |
} | |
@@ -665,25 +912,36 @@ st_delete_safe(register st_table *table, register st_data_t *key, st_data_t *val | |
st_index_t hash_val; | |
register st_table_entry *ptr; | |
+ hash_val = do_hash(*key, table); | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ if (check_upacked(table, hash_val, *key)) { | |
+ if (value != 0) *value = UPVAL(table); | |
+ *key = UPKEY(table); | |
+ remove_safe_upacked_entry(table, never); | |
+ return 1; | |
+ } | |
+ if (value != 0) *value = 0; | |
+ return 0; | |
+ } | |
+ | |
if (table->entries_packed) { | |
- st_index_t i; | |
- for (i = 0; i < table->num_entries; i++) { | |
- if ((st_data_t)table->bins[i*2] == *key) { | |
- if (value != 0) *value = (st_data_t)table->bins[i*2+1]; | |
- table->bins[i*2] = (void *)never; | |
- return 1; | |
- } | |
+ st_index_t i = find_packed_index(table, hash_val, *key); | |
+ if (i < table->real_entries) { | |
+ if (value != 0) *value = PVAL(table, i); | |
+ *key = PKEY(table, i); | |
+ remove_safe_packed_entry(table, i, never); | |
+ return 1; | |
} | |
if (value != 0) *value = 0; | |
return 0; | |
} | |
- hash_val = do_hash_bin(*key, table); | |
- ptr = table->bins[hash_val]; | |
+ ptr = table->bins[hash_val % table->num_bins]; | |
for (; ptr != 0; ptr = ptr->next) { | |
if ((ptr->key != never) && EQUAL(table, ptr->key, *key)) { | |
- REMOVE_ENTRY(table, ptr); | |
+ remove_entry(table, ptr); | |
*key = ptr->key; | |
if (value != 0) *value = ptr->record; | |
ptr->key = ptr->record = never; | |
@@ -707,24 +965,29 @@ st_shift(register st_table *table, register st_data_t *key, st_data_t *value) | |
return 0; | |
} | |
- if (table->entries_packed) { | |
- if (value != 0) *value = (st_data_t)table->bins[1]; | |
- *key = (st_data_t)table->bins[0]; | |
- table->num_entries--; | |
- memmove(&table->bins[0], &table->bins[2], | |
- sizeof(struct st_table_entry*) * 2*table->num_entries); | |
+ if (ULTRAPACKED(table)) { | |
+ if (value != 0) *value = UPVAL(table); | |
+ *key = UPKEY(table); | |
+ remove_upacked_entry(table); | |
return 1; | |
} | |
+ if (table->entries_packed) { | |
+ if (value != 0) *value = PVAL(table, 0); | |
+ *key = PKEY(table, 0); | |
+ remove_packed_entry(table, 0); | |
+ return 1; | |
+ } | |
+ | |
hash_val = do_hash_bin(table->head->key, table); | |
prev = &table->bins[hash_val]; | |
for (;(ptr = *prev) != 0; prev = &ptr->next) { | |
if (ptr == table->head) { | |
*prev = ptr->next; | |
- REMOVE_ENTRY(table, ptr); | |
+ remove_entry(table, ptr); | |
if (value != 0) *value = ptr->record; | |
*key = ptr->key; | |
- free(ptr); | |
+ st_free_entry(ptr); | |
return 1; | |
} | |
} | |
@@ -740,17 +1003,23 @@ st_cleanup_safe(st_table *table, st_data_t never) | |
st_table_entry *ptr, **last, *tmp; | |
st_index_t i; | |
+ if (ULTRAPACKED(table)) { | |
+ table->real_upacked = table->num_entries; | |
+ return; | |
+ } | |
+ | |
if (table->entries_packed) { | |
st_index_t i = 0, j = 0; | |
- while ((st_data_t)table->bins[i*2] != never) { | |
- if (i++ == table->num_entries) return; | |
+ while (PKEY(table, i) != never) { | |
+ if (i++ == table->real_entries) return; | |
} | |
- for (j = i; ++i < table->num_entries;) { | |
- if ((st_data_t)table->bins[i*2] == never) continue; | |
- table->bins[j*2] = table->bins[i*2]; | |
- table->bins[j*2+1] = table->bins[i*2+1]; | |
+ for (j = i; ++i < table->real_entries;) { | |
+ if (PKEY(table, i) == never) continue; | |
+ PACKED_ENT(table, j) = PACKED_ENT(table, i); | |
j++; | |
} | |
+ table->real_entries = j; | |
+ /* table->num_entries really should be equal j at this moment, but let set it anyway */ | |
table->num_entries = j; | |
return; | |
} | |
@@ -761,7 +1030,7 @@ st_cleanup_safe(st_table *table, st_data_t never) | |
if (ptr->key == never) { | |
tmp = ptr; | |
*last = ptr = ptr->next; | |
- free(tmp); | |
+ st_free_entry(tmp); | |
} | |
else { | |
ptr = *(last = &ptr->next); | |
@@ -771,50 +1040,78 @@ st_cleanup_safe(st_table *table, st_data_t never) | |
} | |
int | |
-st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) | |
+st_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg, st_data_t never) | |
{ | |
st_table_entry *ptr, **last, *tmp; | |
enum st_retval retval; | |
- st_index_t i; | |
+ st_data_t key, val; | |
+ st_index_t hash, i = 0; | |
+ | |
+ if (table->num_entries == 0) { | |
+ return 0; | |
+ } | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ key = UPKEY(table); | |
+ val = UPVAL(table); | |
+ hash = UPHASH(table); | |
+ if (key == never) return 0; | |
+ retval = (*func)(key, val, arg); | |
+ if (!ULTRAPACKED(table)) { | |
+ goto packed; | |
+ } | |
+ switch(retval) { | |
+ case ST_CHECK: | |
+ if (UPHASH(table) == 0 && UPKEY(table) == never) | |
+ break; | |
+ if (check_upacked(table, hash, key)) | |
+ break; | |
+ goto deleted; | |
+ case ST_DELETE: | |
+ remove_safe_upacked_entry(table, never); | |
+ case ST_CONTINUE: | |
+ case ST_STOP: | |
+ break; | |
+ } | |
+ return 0; | |
+ } | |
if (table->entries_packed) { | |
- for (i = 0; i < table->num_entries; i++) { | |
- st_index_t j; | |
- st_data_t key, val; | |
- key = (st_data_t)table->bins[i*2]; | |
- val = (st_data_t)table->bins[i*2+1]; | |
- retval = (*func)(key, val, arg); | |
+ for (i = 0; i < table->real_entries; i++) { | |
+ key = PKEY(table, i); | |
+ val = PVAL(table, i); | |
+ hash = PHASH(table, i); | |
+ if (key == never) continue; | |
+ retval = (*func)(key, val, arg); | |
+ packed: | |
if (!table->entries_packed) { | |
- FIND_ENTRY(table, ptr, key, i); | |
+ FIND_ENTRY(table, ptr, hash, i); | |
if (retval == ST_CHECK) { | |
if (!ptr) goto deleted; | |
goto unpacked_continue; | |
} | |
goto unpacked; | |
} | |
- switch (retval) { | |
+ switch (retval) { | |
case ST_CHECK: /* check if hash is modified during iteration */ | |
- for (j = 0; j < table->num_entries; j++) { | |
- if ((st_data_t)table->bins[j*2] == key) | |
- break; | |
- } | |
- if (j == table->num_entries) { | |
+ if (PHASH(table, i) == 0 && PKEY(table, i) == never) { | |
+ break; | |
+ } | |
+ i = find_packed_index(table, hash, key); | |
+ if (i == table->real_entries) { | |
goto deleted; | |
- } | |
+ } | |
/* fall through */ | |
case ST_CONTINUE: | |
break; | |
case ST_STOP: | |
return 0; | |
case ST_DELETE: | |
- table->num_entries--; | |
- memmove(&table->bins[i*2], &table->bins[(i+1)*2], | |
- sizeof(struct st_table_entry*) * 2*(table->num_entries-i)); | |
- i--; | |
- break; | |
- } | |
- } | |
- return 0; | |
+ remove_safe_packed_entry(table, i, never); | |
+ break; | |
+ } | |
+ } | |
+ return 0; | |
} | |
else { | |
ptr = table->head; | |
@@ -822,6 +1119,8 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) | |
if (ptr != 0) { | |
do { | |
+ if (ptr->key == never) | |
+ goto unpacked_continue; | |
i = ptr->hash % table->num_bins; | |
retval = (*func)(ptr->key, ptr->record, arg); | |
unpacked: | |
@@ -847,10 +1146,100 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) | |
for (; (tmp = *last) != 0; last = &tmp->next) { | |
if (ptr == tmp) { | |
tmp = ptr->fore; | |
+ remove_entry(table, ptr); | |
+ ptr->key = ptr->record = never; | |
+ ptr->hash = 0; | |
+ ptr = tmp; | |
+ break; | |
+ } | |
+ } | |
+ } | |
+ } while (ptr && table->head); | |
+ } | |
+ return 0; | |
+} | |
+ | |
+int | |
+st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) | |
+{ | |
+ st_table_entry *ptr, **last, *tmp; | |
+ enum st_retval retval; | |
+ st_data_t key, val; | |
+ st_index_t hash, i = 0; | |
+ | |
+ if (table->num_entries == 0) { | |
+ return 0; | |
+ } | |
+ | |
+ if (ULTRAPACKED(table)) { | |
+ key = UPKEY(table); | |
+ val = UPVAL(table); | |
+ hash = UPHASH(table); | |
+ retval = (*func)(key, val, arg); | |
+ if (!ULTRAPACKED(table)) { | |
+ goto packed; | |
+ } | |
+ switch (retval) { | |
+ case ST_DELETE: | |
+ remove_upacked_entry(table); | |
+ case ST_CONTINUE: | |
+ case ST_CHECK: | |
+ case ST_STOP: | |
+ break; | |
+ } | |
+ return 0; | |
+ } | |
+ | |
+ if (table->entries_packed) { | |
+ for (i = 0; i < table->real_entries; i++) { | |
+ key = PKEY(table, i); | |
+ val = PVAL(table, i); | |
+ hash = PHASH(table, i); | |
+ retval = (*func)(key, val, arg); | |
+ packed: | |
+ if (!table->entries_packed) { | |
+ FIND_ENTRY(table, ptr, hash, i); | |
+ if (!ptr) return 0; | |
+ goto unpacked; | |
+ } | |
+ switch (retval) { | |
+ case ST_CONTINUE: | |
+ break; | |
+ case ST_CHECK: | |
+ case ST_STOP: | |
+ return 0; | |
+ case ST_DELETE: | |
+ remove_packed_entry(table, i); | |
+ i--; | |
+ break; | |
+ } | |
+ } | |
+ return 0; | |
+ } | |
+ else { | |
+ ptr = table->head; | |
+ } | |
+ | |
+ if (ptr != 0) { | |
+ do { | |
+ i = ptr->hash % table->num_bins; | |
+ retval = (*func)(ptr->key, ptr->record, arg); | |
+ unpacked: | |
+ switch (retval) { | |
+ case ST_CONTINUE: | |
+ ptr = ptr->fore; | |
+ break; | |
+ case ST_CHECK: | |
+ case ST_STOP: | |
+ return 0; | |
+ case ST_DELETE: | |
+ last = &table->bins[ptr->hash % table->num_bins]; | |
+ for (; (tmp = *last) != 0; last = &tmp->next) { | |
+ if (ptr == tmp) { | |
+ tmp = ptr->fore; | |
*last = ptr->next; | |
- REMOVE_ENTRY(table, ptr); | |
- free(ptr); | |
- if (ptr == tmp) return 0; | |
+ remove_entry(table, ptr); | |
+ st_free_entry(ptr); | |
ptr = tmp; | |
break; | |
} | |
@@ -873,13 +1262,13 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) | |
for (i = table->num_entries-1; 0 <= i; i--) { | |
int j; | |
st_data_t key, val; | |
- key = (st_data_t)table->bins[i*2]; | |
- val = (st_data_t)table->bins[i*2+1]; | |
+ key = PKEY(table, i); | |
+ val = PVAL(table, i); | |
retval = (*func)(key, val, arg); | |
switch (retval) { | |
case ST_CHECK: /* check if hash is modified during iteration */ | |
for (j = 0; j < table->num_entries; j++) { | |
- if ((st_data_t)table->bins[j*2] == key) | |
+ if (PKEY(table, j) == key) | |
break; | |
} | |
if (j == table->num_entries) { | |
@@ -893,9 +1282,7 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) | |
case ST_STOP: | |
return 0; | |
case ST_DELETE: | |
- table->num_entries--; | |
- memmove(&table->bins[i*2], &table->bins[(i+1)*2], | |
- sizeof(struct st_table_entry*) * 2*(table->num_entries-i)); | |
+ remove_packed_entry(table, i); | |
break; | |
} | |
} | |
@@ -928,8 +1315,8 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) | |
if (ptr == tmp) { | |
tmp = ptr->back; | |
*last = ptr->next; | |
- REMOVE_ENTRY(table, ptr); | |
- free(ptr); | |
+ remove_entry(table, ptr); | |
+ st_free_entry(ptr); | |
ptr = tmp; | |
break; | |
} | |
diff --git a/class.c b/class.c | |
index ca02989..cf135c1 100644 | |
--- a/class.c | |
+++ b/class.c | |
@@ -31,7 +31,7 @@ | |
#include "internal.h" | |
#include <ctype.h> | |
-extern st_table *rb_class_tbl; | |
+extern sa_table rb_class_tbl; | |
static ID id_attached; | |
/** | |
@@ -53,11 +53,15 @@ class_alloc(VALUE flags, VALUE klass) | |
NEWOBJ(obj, struct RClass); | |
OBJSETUP(obj, klass, flags); | |
obj->ptr = ext; | |
- RCLASS_IV_TBL(obj) = 0; | |
- RCLASS_CONST_TBL(obj) = 0; | |
- RCLASS_M_TBL(obj) = 0; | |
- RCLASS_SUPER(obj) = 0; | |
- RCLASS_IV_INDEX_TBL(obj) = 0; | |
+ MEMZERO(ext, struct rb_classext_struct, 1); | |
+ return (VALUE)obj; | |
+} | |
+ | |
+static VALUE | |
+iclass_alloc() | |
+{ | |
+ NEWOBJ(obj, struct RClass); | |
+ OBJSETUP(obj, rb_cClass, T_ICLASS); | |
return (VALUE)obj; | |
} | |
@@ -77,7 +81,6 @@ rb_class_boot(VALUE super) | |
VALUE klass = class_alloc(T_CLASS, rb_cClass); | |
RCLASS_SUPER(klass) = super; | |
- RCLASS_M_TBL(klass) = st_init_numtable(); | |
OBJ_INFECT(klass, super); | |
return (VALUE)klass; | |
@@ -120,43 +123,30 @@ rb_class_new(VALUE super) | |
return rb_class_boot(super); | |
} | |
-struct clone_method_data { | |
- st_table *tbl; | |
- VALUE klass; | |
-}; | |
- | |
VALUE rb_iseq_clone(VALUE iseqval, VALUE newcbase); | |
-static int | |
-clone_method(ID mid, const rb_method_entry_t *me, struct clone_method_data *data) | |
+static void | |
+clone_method(ID mid, const rb_method_entry_t *me, VALUE klass) | |
{ | |
VALUE newiseqval; | |
if (me->def && me->def->type == VM_METHOD_TYPE_ISEQ) { | |
rb_iseq_t *iseq; | |
- newiseqval = rb_iseq_clone(me->def->body.iseq->self, data->klass); | |
+ newiseqval = rb_iseq_clone(me->def->body.iseq->self, klass); | |
GetISeqPtr(newiseqval, iseq); | |
- rb_add_method(data->klass, mid, VM_METHOD_TYPE_ISEQ, iseq, me->flag); | |
+ rb_add_method(klass, mid, VM_METHOD_TYPE_ISEQ, iseq, me->flag); | |
RB_GC_GUARD(newiseqval); | |
} | |
else { | |
- rb_method_entry_set(data->klass, mid, me, me->flag); | |
+ rb_method_entry_set(klass, mid, me, me->flag); | |
} | |
- return ST_CONTINUE; | |
} | |
-static int | |
-clone_const(ID key, const rb_const_entry_t *ce, st_table *tbl) | |
+static void | |
+clone_const(sa_index_t key, st_data_t ce, sa_table *tbl) | |
{ | |
rb_const_entry_t *nce = ALLOC(rb_const_entry_t); | |
- *nce = *ce; | |
- st_insert(tbl, key, (st_data_t)nce); | |
- return ST_CONTINUE; | |
-} | |
- | |
-static int | |
-clone_const_i(st_data_t key, st_data_t value, st_data_t data) | |
-{ | |
- return clone_const((ID)key, (const rb_const_entry_t *)value, (st_table *)data); | |
+ *nce = *(const rb_const_entry_t*)ce; | |
+ sa_insert(tbl, (sa_index_t)key, (st_data_t)nce); | |
} | |
static void | |
@@ -177,6 +167,7 @@ class_init_copy_check(VALUE clone, VALUE orig) | |
VALUE | |
rb_mod_init_copy(VALUE clone, VALUE orig) | |
{ | |
+ ID id; | |
if (RB_TYPE_P(clone, T_CLASS)) { | |
class_init_copy_check(clone, orig); | |
} | |
@@ -187,36 +178,22 @@ rb_mod_init_copy(VALUE clone, VALUE orig) | |
rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone); | |
} | |
RCLASS_SUPER(clone) = RCLASS_SUPER(orig); | |
- if (RCLASS_IV_TBL(orig)) { | |
- st_data_t id; | |
- if (RCLASS_IV_TBL(clone)) { | |
- st_free_table(RCLASS_IV_TBL(clone)); | |
- } | |
- RCLASS_IV_TBL(clone) = st_copy(RCLASS_IV_TBL(orig)); | |
- CONST_ID(id, "__classpath__"); | |
- st_delete(RCLASS_IV_TBL(clone), &id, 0); | |
- CONST_ID(id, "__classid__"); | |
- st_delete(RCLASS_IV_TBL(clone), &id, 0); | |
- } | |
- if (RCLASS_CONST_TBL(orig)) { | |
- if (RCLASS_CONST_TBL(clone)) { | |
- rb_free_const_table(RCLASS_CONST_TBL(clone)); | |
- } | |
- RCLASS_CONST_TBL(clone) = st_init_numtable(); | |
- st_foreach(RCLASS_CONST_TBL(orig), clone_const_i, (st_data_t)RCLASS_CONST_TBL(clone)); | |
- } | |
- if (RCLASS_M_TBL(orig)) { | |
- struct clone_method_data data; | |
+ sa_copy_to(RCLASS_IV_TBL(orig), RCLASS_IV_TBL(clone)); | |
+ CONST_ID(id, "__classpath__"); | |
+ sa_delete(RCLASS_IV_TBL(clone), (sa_index_t)id, 0); | |
+ CONST_ID(id, "__classid__"); | |
+ sa_delete(RCLASS_IV_TBL(clone), (sa_index_t)id, 0); | |
- if (RCLASS_M_TBL(clone)) { | |
- rb_free_m_table(RCLASS_M_TBL(clone)); | |
- } | |
- data.tbl = RCLASS_M_TBL(clone) = st_init_numtable(); | |
- data.klass = clone; | |
- st_foreach(RCLASS_M_TBL(orig), clone_method, | |
- (st_data_t)&data); | |
- } | |
+ sa_clear(RCLASS_CONST_TBL(clone)); | |
+ SA_FOREACH_START(RCLASS_CONST_TBL(orig)); | |
+ clone_const(entry->key, value, RCLASS_CONST_TBL(clone)); | |
+ SA_FOREACH_END(); | |
+ | |
+ rb_free_m_table(RCLASS_M_TBL(clone)); | |
+ SA_FOREACH_START(RCLASS_M_TBL(orig)); | |
+ clone_method(entry->key, (const rb_method_entry_t *)value, clone); | |
+ SA_FOREACH_END(); | |
return clone; | |
} | |
@@ -229,7 +206,6 @@ rb_singleton_class_clone(VALUE obj) | |
if (!FL_TEST(klass, FL_SINGLETON)) | |
return klass; | |
else { | |
- struct clone_method_data data; | |
/* copy singleton(unnamed) class */ | |
VALUE clone = class_alloc((RBASIC(klass)->flags & ~(FL_MARK)), 0); | |
@@ -241,18 +217,16 @@ rb_singleton_class_clone(VALUE obj) | |
} | |
RCLASS_SUPER(clone) = RCLASS_SUPER(klass); | |
- if (RCLASS_IV_TBL(klass)) { | |
- RCLASS_IV_TBL(clone) = st_copy(RCLASS_IV_TBL(klass)); | |
- } | |
- if (RCLASS_CONST_TBL(klass)) { | |
- RCLASS_CONST_TBL(clone) = st_init_numtable(); | |
- st_foreach(RCLASS_CONST_TBL(klass), clone_const_i, (st_data_t)RCLASS_CONST_TBL(clone)); | |
- } | |
- RCLASS_M_TBL(clone) = st_init_numtable(); | |
- data.tbl = RCLASS_M_TBL(clone); | |
- data.klass = (VALUE)clone; | |
- st_foreach(RCLASS_M_TBL(klass), clone_method, | |
- (st_data_t)&data); | |
+ sa_copy_to(RCLASS_IV_TBL(klass), RCLASS_IV_TBL(clone)); | |
+ | |
+ SA_FOREACH_START(RCLASS_CONST_TBL(klass)); | |
+ clone_const(entry->key, value, RCLASS_CONST_TBL(clone)); | |
+ SA_FOREACH_END(); | |
+ | |
+ SA_FOREACH_START(RCLASS_M_TBL(klass)); | |
+ clone_method(entry->key, (const rb_method_entry_t*)value, clone); | |
+ SA_FOREACH_END(); | |
+ | |
rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone); | |
FL_SET(clone, FL_SINGLETON); | |
return (VALUE)clone; | |
@@ -267,10 +241,7 @@ void | |
rb_singleton_class_attached(VALUE klass, VALUE obj) | |
{ | |
if (FL_TEST(klass, FL_SINGLETON)) { | |
- if (!RCLASS_IV_TBL(klass)) { | |
- RCLASS_IV_TBL(klass) = st_init_numtable(); | |
- } | |
- st_insert(RCLASS_IV_TBL(klass), id_attached, obj); | |
+ sa_insert(RCLASS_IV_TBL(klass), (sa_index_t)id_attached, obj); | |
} | |
} | |
@@ -357,12 +328,12 @@ make_singleton_class(VALUE obj) | |
static VALUE | |
boot_defclass(const char *name, VALUE super) | |
{ | |
- extern st_table *rb_class_tbl; | |
+ extern sa_table rb_class_tbl; | |
VALUE obj = rb_class_boot(super); | |
ID id = rb_intern(name); | |
rb_name_class(obj, id); | |
- st_add_direct(rb_class_tbl, id, obj); | |
+ sa_insert(&rb_class_tbl, (sa_index_t)id, obj); | |
rb_const_set((rb_cObject ? rb_cObject : obj), id, obj); | |
return obj; | |
} | |
@@ -486,7 +457,7 @@ rb_define_class(const char *name, VALUE super) | |
rb_warn("no super class for `%s', Object assumed", name); | |
} | |
klass = rb_define_class_id(id, super); | |
- st_add_direct(rb_class_tbl, id, klass); | |
+ sa_insert(&rb_class_tbl, (sa_index_t)id, klass); | |
rb_name_class(klass, id); | |
rb_const_set(rb_cObject, id, klass); | |
rb_class_inherited(super, klass); | |
@@ -567,8 +538,6 @@ rb_module_new(void) | |
{ | |
VALUE mdl = class_alloc(T_MODULE, rb_cModule); | |
- RCLASS_M_TBL(mdl) = st_init_numtable(); | |
- | |
return (VALUE)mdl; | |
} | |
@@ -597,7 +566,7 @@ rb_define_module(const char *name) | |
rb_raise(rb_eTypeError, "%s is not a module", rb_obj_classname(module)); | |
} | |
module = rb_define_module_id(id); | |
- st_add_direct(rb_class_tbl, id, module); | |
+ sa_insert(&rb_class_tbl, (sa_index_t)id, module); | |
rb_const_set(rb_cObject, id, module); | |
return module; | |
@@ -632,27 +601,15 @@ rb_define_module_id_under(VALUE outer, ID id) | |
static VALUE | |
include_class_new(VALUE module, VALUE super) | |
{ | |
- VALUE klass = class_alloc(T_ICLASS, rb_cClass); | |
+ VALUE klass; | |
if (BUILTIN_TYPE(module) == T_ICLASS) { | |
module = RBASIC(module)->klass; | |
} | |
- if (!RCLASS_IV_TBL(module)) { | |
- RCLASS_IV_TBL(module) = st_init_numtable(); | |
- } | |
- if (!RCLASS_CONST_TBL(module)) { | |
- RCLASS_CONST_TBL(module) = st_init_numtable(); | |
- } | |
- RCLASS_IV_TBL(klass) = RCLASS_IV_TBL(module); | |
- RCLASS_CONST_TBL(klass) = RCLASS_CONST_TBL(module); | |
- RCLASS_M_TBL(klass) = RCLASS_M_TBL(module); | |
+ klass = iclass_alloc(); | |
+ RBASIC(klass)->klass = module; | |
+ RCLASS_EXT(klass) = RCLASS_EXT(module); | |
RCLASS_SUPER(klass) = super; | |
- if (TYPE(module) == T_ICLASS) { | |
- RBASIC(klass)->klass = RBASIC(module)->klass; | |
- } | |
- else { | |
- RBASIC(klass)->klass = module; | |
- } | |
OBJ_INFECT(klass, module); | |
OBJ_INFECT(klass, super); | |
@@ -679,13 +636,13 @@ rb_include_module(VALUE klass, VALUE module) | |
while (module) { | |
int superclass_seen = FALSE; | |
- if (RCLASS_M_TBL(klass) == RCLASS_M_TBL(module)) | |
+ if (RCLASS_EXT(klass) == RCLASS_EXT(module)) | |
rb_raise(rb_eArgError, "cyclic include detected"); | |
/* ignore if the module included already in superclasses */ | |
for (p = RCLASS_SUPER(klass); p; p = RCLASS_SUPER(p)) { | |
switch (BUILTIN_TYPE(p)) { | |
case T_ICLASS: | |
- if (RCLASS_M_TBL(p) == RCLASS_M_TBL(module)) { | |
+ if (RCLASS_EXT(p) == RCLASS_EXT(module)) { | |
if (!superclass_seen) { | |
c = p; /* move insertion point */ | |
} | |
@@ -698,7 +655,7 @@ rb_include_module(VALUE klass, VALUE module) | |
} | |
} | |
c = RCLASS_SUPER(c) = include_class_new(module, RCLASS_SUPER(c)); | |
- if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries) | |
+ if (RMODULE_M_TBL(module)->num_entries) | |
changed = 1; | |
if (RMODULE_CONST_TBL(module) && RMODULE_CONST_TBL(module)->num_entries) | |
changed = 1; | |
@@ -831,58 +788,58 @@ ins_methods_push(ID name, long type, VALUE ary, long visi) | |
} | |
static int | |
-ins_methods_i(st_data_t name, st_data_t type, st_data_t ary) | |
+ins_methods_i(sa_index_t name, st_data_t type, st_data_t ary) | |
{ | |
return ins_methods_push((ID)name, (long)type, (VALUE)ary, -1); /* everything but private */ | |
} | |
static int | |
-ins_methods_prot_i(st_data_t name, st_data_t type, st_data_t ary) | |
+ins_methods_prot_i(sa_index_t name, st_data_t type, st_data_t ary) | |
{ | |
return ins_methods_push((ID)name, (long)type, (VALUE)ary, NOEX_PROTECTED); | |
} | |
static int | |
-ins_methods_priv_i(st_data_t name, st_data_t type, st_data_t ary) | |
+ins_methods_priv_i(sa_index_t name, st_data_t type, st_data_t ary) | |
{ | |
return ins_methods_push((ID)name, (long)type, (VALUE)ary, NOEX_PRIVATE); | |
} | |
static int | |
-ins_methods_pub_i(st_data_t name, st_data_t type, st_data_t ary) | |
+ins_methods_pub_i(sa_index_t name, st_data_t type, st_data_t ary) | |
{ | |
return ins_methods_push((ID)name, (long)type, (VALUE)ary, NOEX_PUBLIC); | |
} | |
static int | |
-method_entry_i(st_data_t key, st_data_t value, st_data_t data) | |
+method_entry_i(sa_index_t key, st_data_t value, st_data_t data) | |
{ | |
const rb_method_entry_t *me = (const rb_method_entry_t *)value; | |
- st_table *list = (st_table *)data; | |
+ sa_table *list = (sa_table *)data; | |
long type; | |
if ((ID)key == ID_ALLOCATOR) { | |
return ST_CONTINUE; | |
} | |
- if (!st_lookup(list, key, 0)) { | |
+ if (!sa_lookup(list, key, 0)) { | |
if (UNDEFINED_METHOD_ENTRY_P(me)) { | |
type = -1; /* none */ | |
} | |
else { | |
type = VISI(me->flag); | |
} | |
- st_add_direct(list, key, type); | |
+ sa_insert(list, key, type); | |
} | |
return ST_CONTINUE; | |
} | |
static VALUE | |
-class_instance_method_list(int argc, VALUE *argv, VALUE mod, int obj, int (*func) (st_data_t, st_data_t, st_data_t)) | |
+class_instance_method_list(int argc, VALUE *argv, VALUE mod, int obj, int (*func) (sa_index_t, st_data_t, st_data_t)) | |
{ | |
VALUE ary; | |
int recur; | |
- st_table *list; | |
+ sa_table list = SA_EMPTY_TABLE; | |
if (argc == 0) { | |
recur = TRUE; | |
@@ -893,16 +850,15 @@ class_instance_method_list(int argc, VALUE *argv, VALUE mod, int obj, int (*func | |
recur = RTEST(r); | |
} | |
- list = st_init_numtable(); | |
for (; mod; mod = RCLASS_SUPER(mod)) { | |
- st_foreach(RCLASS_M_TBL(mod), method_entry_i, (st_data_t)list); | |
+ sa_foreach(RCLASS_M_TBL(mod), method_entry_i, (st_data_t)&list); | |
if (BUILTIN_TYPE(mod) == T_ICLASS) continue; | |
if (obj && FL_TEST(mod, FL_SINGLETON)) continue; | |
if (!recur) break; | |
} | |
ary = rb_ary_new(); | |
- st_foreach(list, func, ary); | |
- st_free_table(list); | |
+ sa_foreach(&list, func, ary); | |
+ sa_clear(&list); | |
return ary; | |
} | |
@@ -1116,7 +1072,7 @@ VALUE | |
rb_obj_singleton_methods(int argc, VALUE *argv, VALUE obj) | |
{ | |
VALUE recur, ary, klass; | |
- st_table *list; | |
+ sa_table list = SA_EMPTY_TABLE; | |
if (argc == 0) { | |
recur = Qtrue; | |
@@ -1125,20 +1081,19 @@ rb_obj_singleton_methods(int argc, VALUE *argv, VALUE obj) | |
rb_scan_args(argc, argv, "01", &recur); | |
} | |
klass = CLASS_OF(obj); | |
- list = st_init_numtable(); | |
if (klass && FL_TEST(klass, FL_SINGLETON)) { | |
- st_foreach(RCLASS_M_TBL(klass), method_entry_i, (st_data_t)list); | |
+ sa_foreach(RCLASS_M_TBL(klass), method_entry_i, (st_data_t)&list); | |
klass = RCLASS_SUPER(klass); | |
} | |
if (RTEST(recur)) { | |
while (klass && (FL_TEST(klass, FL_SINGLETON) || TYPE(klass) == T_ICLASS)) { | |
- st_foreach(RCLASS_M_TBL(klass), method_entry_i, (st_data_t)list); | |
+ sa_foreach(RCLASS_M_TBL(klass), method_entry_i, (st_data_t)&list); | |
klass = RCLASS_SUPER(klass); | |
} | |
} | |
ary = rb_ary_new(); | |
- st_foreach(list, ins_methods_i, ary); | |
- st_free_table(list); | |
+ sa_foreach(&list, ins_methods_i, ary); | |
+ sa_clear(&list); | |
return ary; | |
} | |
diff --git a/common.mk b/common.mk | |
index b2a2e71..ec79502 100644 | |
--- a/common.mk | |
+++ b/common.mk | |
@@ -79,6 +79,7 @@ COMMONOBJS = array.$(OBJEXT) \ | |
safe.$(OBJEXT) \ | |
signal.$(OBJEXT) \ | |
sprintf.$(OBJEXT) \ | |
+ sp_ar.$(OBJEXT) \ | |
st.$(OBJEXT) \ | |
strftime.$(OBJEXT) \ | |
string.$(OBJEXT) \ | |
@@ -704,6 +705,7 @@ signal.$(OBJEXT): {$(VPATH)}signal.c $(RUBY_H_INCLUDES) \ | |
$(VM_CORE_H_INCLUDES) {$(VPATH)}debug.h | |
sprintf.$(OBJEXT): {$(VPATH)}sprintf.c $(RUBY_H_INCLUDES) {$(VPATH)}re.h \ | |
{$(VPATH)}regex.h {$(VPATH)}vsnprintf.c $(ENCODING_H_INCLUDES) | |
+sp_ar.$(OBJEXT): {$(VPATH)}sp_ar.c $(RUBY_H_INCLUDES) | |
st.$(OBJEXT): {$(VPATH)}st.c $(RUBY_H_INCLUDES) {$(VPATH)}pool_alloc.h | |
strftime.$(OBJEXT): {$(VPATH)}strftime.c $(RUBY_H_INCLUDES) \ | |
{$(VPATH)}timev.h | |
diff --git a/constant.h b/constant.h | |
index 8232910..9106847 100644 | |
--- a/constant.h | |
+++ b/constant.h | |
@@ -23,7 +23,7 @@ typedef struct rb_const_entry_struct { | |
VALUE rb_mod_private_constant(int argc, VALUE *argv, VALUE obj); | |
VALUE rb_mod_public_constant(int argc, VALUE *argv, VALUE obj); | |
-void rb_free_const_table(st_table *tbl); | |
+void rb_free_const_table(sa_table *tbl); | |
VALUE rb_public_const_get(VALUE klass, ID id); | |
VALUE rb_public_const_get_at(VALUE klass, ID id); | |
VALUE rb_public_const_get_from(VALUE klass, ID id); | |
diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c | |
index fd08158..1e2ee5f 100644 | |
--- a/ext/objspace/objspace.c | |
+++ b/ext/objspace/objspace.c | |
@@ -60,20 +60,10 @@ memsize_of(VALUE obj) | |
break; | |
case T_MODULE: | |
case T_CLASS: | |
- size += st_memsize(RCLASS_M_TBL(obj)); | |
- if (RCLASS_IV_TBL(obj)) { | |
- size += st_memsize(RCLASS_IV_TBL(obj)); | |
- } | |
- if (RCLASS_IV_INDEX_TBL(obj)) { | |
- size += st_memsize(RCLASS_IV_INDEX_TBL(obj)); | |
- } | |
- if (RCLASS(obj)->ptr->iv_tbl) { | |
- size += st_memsize(RCLASS(obj)->ptr->iv_tbl); | |
- } | |
- if (RCLASS(obj)->ptr->const_tbl) { | |
- size += st_memsize(RCLASS(obj)->ptr->const_tbl); | |
- } | |
- size += sizeof(rb_classext_t); | |
+ size += sa_memsize(RCLASS_M_TBL(obj)); | |
+ size += sa_memsize(RCLASS_IV_TBL(obj)); | |
+ size += sa_memsize(RCLASS_IV_INDEX_TBL(obj)); | |
+ size += sa_memsize(RCLASS_CONST_TBL(obj)); | |
break; | |
case T_STRING: | |
size += rb_str_memsize(obj); | |
diff --git a/gc.c b/gc.c | |
index 1f155d0..dc7ac27 100644 | |
--- a/gc.c | |
+++ b/gc.c | |
@@ -690,7 +690,7 @@ rb_objspace_free(rb_objspace_t *objspace) | |
#define HEAP_OBJ_LIMIT (unsigned int)(HEAP_SIZE / sizeof(struct RVALUE)) | |
-extern st_table *rb_class_tbl; | |
+extern sa_table rb_class_tbl; | |
int ruby_disable_gc_stress = 0; | |
@@ -2360,6 +2360,15 @@ mark_tbl(rb_objspace_t *objspace, st_table *tbl) | |
st_foreach(tbl, mark_entry, (st_data_t)&arg); | |
} | |
+static void | |
+mark_sa_tbl(rb_objspace_t *objspace, sa_table *tbl) | |
+{ | |
+ if (!tbl) return; | |
+ SA_FOREACH_START(tbl); | |
+ gc_mark(objspace, (VALUE)value); | |
+ SA_FOREACH_END(); | |
+} | |
+ | |
static int | |
mark_key(VALUE key, VALUE value, st_data_t data) | |
{ | |
@@ -2436,74 +2445,52 @@ rb_mark_method_entry(const rb_method_entry_t *me) | |
mark_method_entry(&rb_objspace, me); | |
} | |
-static int | |
-mark_method_entry_i(ID key, const rb_method_entry_t *me, st_data_t data) | |
-{ | |
- struct mark_tbl_arg *arg = (void*)data; | |
- mark_method_entry(arg->objspace, me); | |
- return ST_CONTINUE; | |
-} | |
- | |
static void | |
-mark_m_tbl(rb_objspace_t *objspace, st_table *tbl) | |
-{ | |
- struct mark_tbl_arg arg; | |
- if (!tbl) return; | |
- arg.objspace = objspace; | |
- st_foreach(tbl, mark_method_entry_i, (st_data_t)&arg); | |
-} | |
- | |
-static int | |
-free_method_entry_i(ID key, rb_method_entry_t *me, st_data_t data) | |
+mark_m_tbl(rb_objspace_t *objspace, sa_table *tbl) | |
{ | |
- if (!me->mark) { | |
- rb_free_method_entry(me); | |
- } | |
- return ST_CONTINUE; | |
+ SA_FOREACH_START(tbl); | |
+ mark_method_entry(objspace, (const rb_method_entry_t*)value); | |
+ SA_FOREACH_END(); | |
} | |
void | |
-rb_free_m_table(st_table *tbl) | |
+rb_free_m_table(sa_table *tbl) | |
{ | |
- st_foreach(tbl, free_method_entry_i, 0); | |
- st_free_table(tbl); | |
-} | |
- | |
-static int | |
-mark_const_entry_i(ID key, const rb_const_entry_t *ce, st_data_t data) | |
-{ | |
- struct mark_tbl_arg *arg = (void*)data; | |
- gc_mark(arg->objspace, ce->value); | |
- return ST_CONTINUE; | |
+ SA_FOREACH_START(tbl); | |
+ if (!((rb_method_entry_t*)value)->mark) { | |
+ rb_free_method_entry((rb_method_entry_t*)value); | |
+ } | |
+ SA_FOREACH_END(); | |
+ sa_clear(tbl); | |
} | |
static void | |
-mark_const_tbl(rb_objspace_t *objspace, st_table *tbl) | |
+mark_const_tbl(rb_objspace_t *objspace, sa_table *tbl) | |
{ | |
- struct mark_tbl_arg arg; | |
- if (!tbl) return; | |
- arg.objspace = objspace; | |
- st_foreach(tbl, mark_const_entry_i, (st_data_t)&arg); | |
+ SA_FOREACH_START(tbl); | |
+ gc_mark(objspace, ((const rb_const_entry_t*)value)->value); | |
+ SA_FOREACH_END(); | |
} | |
-static int | |
-free_const_entry_i(ID key, rb_const_entry_t *ce, st_data_t data) | |
+void | |
+rb_free_const_table(sa_table *tbl) | |
{ | |
- xfree(ce); | |
- return ST_CONTINUE; | |
+ SA_FOREACH_START(tbl); | |
+ xfree((rb_const_entry_t*)value); | |
+ SA_FOREACH_END(); | |
+ sa_clear(tbl); | |
} | |
void | |
-rb_free_const_table(st_table *tbl) | |
+rb_mark_tbl(st_table *tbl) | |
{ | |
- st_foreach(tbl, free_const_entry_i, 0); | |
- st_free_table(tbl); | |
+ mark_tbl(&rb_objspace, tbl); | |
} | |
void | |
-rb_mark_tbl(st_table *tbl) | |
+rb_mark_sa_tbl(sa_table *tbl) | |
{ | |
- mark_tbl(&rb_objspace, tbl); | |
+ mark_sa_tbl(&rb_objspace, tbl); | |
} | |
void | |
@@ -2711,7 +2698,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr) | |
case T_CLASS: | |
case T_MODULE: | |
mark_m_tbl(objspace, RCLASS_M_TBL(obj)); | |
- mark_tbl(objspace, RCLASS_IV_TBL(obj)); | |
+ mark_sa_tbl(objspace, RCLASS_IV_TBL(obj)); | |
mark_const_tbl(objspace, RCLASS_CONST_TBL(obj)); | |
ptr = RCLASS_SUPER(obj); | |
goto again; | |
@@ -3286,15 +3273,9 @@ obj_free(rb_objspace_t *objspace, VALUE obj) | |
case T_CLASS: | |
rb_clear_cache_by_class((VALUE)obj); | |
rb_free_m_table(RCLASS_M_TBL(obj)); | |
- if (RCLASS_IV_TBL(obj)) { | |
- st_free_table(RCLASS_IV_TBL(obj)); | |
- } | |
- if (RCLASS_CONST_TBL(obj)) { | |
- rb_free_const_table(RCLASS_CONST_TBL(obj)); | |
- } | |
- if (RCLASS_IV_INDEX_TBL(obj)) { | |
- st_free_table(RCLASS_IV_INDEX_TBL(obj)); | |
- } | |
+ sa_clear(RCLASS_IV_TBL(obj)); | |
+ rb_free_const_table(RCLASS_CONST_TBL(obj)); | |
+ sa_clear(RCLASS_IV_INDEX_TBL(obj)); | |
xfree(RANY(obj)->as.klass.ptr); | |
break; | |
case T_STRING: | |
@@ -3347,7 +3328,6 @@ obj_free(rb_objspace_t *objspace, VALUE obj) | |
break; | |
case T_ICLASS: | |
/* iClass shares table with the module */ | |
- xfree(RANY(obj)->as.klass.ptr); | |
break; | |
case T_FLOAT: | |
@@ -3462,7 +3442,7 @@ gc_marks(rb_objspace_t *objspace) | |
rb_mark_end_proc(); | |
rb_gc_mark_global_tbl(); | |
- mark_tbl(objspace, rb_class_tbl); | |
+ mark_sa_tbl(objspace, &rb_class_tbl); | |
/* mark generic instance variables for special constants */ | |
rb_mark_generic_ivar_tbl(); | |
diff --git a/include/ruby/intern.h b/include/ruby/intern.h | |
index 9601c4d..e091adc 100644 | |
--- a/include/ruby/intern.h | |
+++ b/include/ruby/intern.h | |
@@ -432,6 +432,7 @@ size_t ruby_stack_length(VALUE**); | |
int rb_during_gc(void); | |
void rb_gc_mark_locations(VALUE*, VALUE*); | |
void rb_mark_tbl(struct st_table*); | |
+void rb_mark_sa_tbl(sa_table*); | |
void rb_mark_set(struct st_table*); | |
void rb_mark_hash(struct st_table*); | |
void rb_gc_mark_maybe(VALUE); | |
@@ -865,7 +866,7 @@ VALUE rb_f_trace_var(int, VALUE*); | |
VALUE rb_f_untrace_var(int, VALUE*); | |
VALUE rb_f_global_variables(void); | |
void rb_alias_variable(ID, ID); | |
-struct st_table* rb_generic_ivar_table(VALUE); | |
+sa_table* rb_generic_ivar_table(VALUE); | |
void rb_copy_generic_ivar(VALUE,VALUE); | |
void rb_mark_generic_ivar(VALUE); | |
void rb_mark_generic_ivar_tbl(void); | |
diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h | |
index 453a603..2625ea3 100644 | |
--- a/include/ruby/ruby.h | |
+++ b/include/ruby/ruby.h | |
@@ -605,7 +605,7 @@ struct RObject { | |
struct { | |
long numiv; | |
VALUE *ivptr; | |
- struct st_table *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */ | |
+ struct sa_table *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */ | |
} heap; | |
VALUE ary[ROBJECT_EMBED_LEN_MAX]; | |
} as; | |
@@ -629,9 +629,8 @@ typedef struct rb_classext_struct rb_classext_t; | |
struct RClass { | |
struct RBasic basic; | |
+ VALUE super; | |
rb_classext_t *ptr; | |
- struct st_table *m_tbl; | |
- struct st_table *iv_index_tbl; | |
}; | |
#define RCLASS_SUPER(c) rb_class_get_superclass(c) | |
#define RMODULE_IV_TBL(m) RCLASS_IV_TBL(m) | |
diff --git a/include/ruby/st.h b/include/ruby/st.h | |
index 94646b6..0738cfd 100644 | |
--- a/include/ruby/st.h | |
+++ b/include/ruby/st.h | |
@@ -152,6 +152,51 @@ st_index_t st_hash_start(st_index_t h); | |
#pragma GCC visibility pop | |
#endif | |
+typedef unsigned int sa_index_t; | |
+#define SA_STOP ST_STOP | |
+#define SA_CONTINUE ST_CONTINUE | |
+ | |
+#define SA_EMPTY 0 | |
+ | |
+typedef struct sa_entry { | |
+ sa_index_t next; | |
+ sa_index_t key; | |
+ st_data_t value; | |
+} sa_entry; | |
+ | |
+typedef struct sa_table { | |
+ sa_index_t num_bins; | |
+ sa_index_t num_entries; | |
+ sa_index_t free_pos; | |
+ sa_entry *entries; | |
+} sa_table; | |
+ | |
+#define SA_EMPTY_TABLE {0, 0, 0, 0}; | |
+void sa_init_table(sa_table *, sa_index_t); | |
+sa_table *sa_new_table(); | |
+int sa_insert(sa_table *, sa_index_t, st_data_t); | |
+int sa_lookup(sa_table *, sa_index_t, st_data_t *); | |
+int sa_delete(sa_table *, sa_index_t, st_data_t *); | |
+void sa_clear(sa_table *); | |
+void sa_clear_no_free(sa_table *); | |
+void sa_free_table(sa_table *); | |
+int sa_foreach(sa_table *, int (*)(ANYARGS), st_data_t); | |
+size_t sa_memsize(const sa_table *); | |
+sa_table *sa_copy(sa_table*); | |
+void sa_copy_to(sa_table*, sa_table*); | |
+typedef int (*sa_iter_func)(sa_index_t key, st_data_t val, st_data_t arg); | |
+ | |
+#define SA_FOREACH_START_I(table, entry) do { \ | |
+ sa_table *T##entry = (table); \ | |
+ sa_index_t K##entry; \ | |
+ for(K##entry = 0; K##entry < T##entry->num_bins; K##entry++) { \ | |
+ sa_entry *entry = T##entry->entries + K##entry; \ | |
+ if (entry->next != SA_EMPTY) { \ | |
+ st_data_t value = entry->value | |
+#define SA_FOREACH_END() } } } while(0) | |
+ | |
+#define SA_FOREACH_START(table) SA_FOREACH_START_I(table, entry) | |
+ | |
#if defined(__cplusplus) | |
#if 0 | |
{ /* satisfy cc-mode */ | |
diff --git a/internal.h b/internal.h | |
index 513b412..8ec6ef7 100644 | |
--- a/internal.h | |
+++ b/internal.h | |
@@ -24,18 +24,19 @@ struct rb_deprecated_classext_struct { | |
}; | |
struct rb_classext_struct { | |
- VALUE super; | |
- struct st_table *iv_tbl; | |
- struct st_table *const_tbl; | |
+ sa_table m_tbl; | |
+ sa_table iv_tbl; | |
+ sa_table const_tbl; | |
+ sa_table iv_index_tbl; | |
}; | |
#undef RCLASS_SUPER | |
#define RCLASS_EXT(c) (RCLASS(c)->ptr) | |
-#define RCLASS_SUPER(c) (RCLASS_EXT(c)->super) | |
-#define RCLASS_IV_TBL(c) (RCLASS_EXT(c)->iv_tbl) | |
-#define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl) | |
-#define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) | |
-#define RCLASS_IV_INDEX_TBL(c) (RCLASS(c)->iv_index_tbl) | |
+#define RCLASS_SUPER(c) (RCLASS(c)->super) | |
+#define RCLASS_IV_TBL(c) (&RCLASS_EXT(c)->iv_tbl) | |
+#define RCLASS_CONST_TBL(c) (&RCLASS_EXT(c)->const_tbl) | |
+#define RCLASS_M_TBL(c) (&RCLASS_EXT(c)->m_tbl) | |
+#define RCLASS_IV_INDEX_TBL(c) (&RCLASS_EXT(c)->iv_index_tbl) | |
struct vtm; /* defined by timev.h */ | |
diff --git a/marshal.c b/marshal.c | |
index 6ae3a55..48d9d9b 100644 | |
--- a/marshal.c | |
+++ b/marshal.c | |
@@ -506,7 +506,7 @@ w_uclass(VALUE obj, VALUE super, struct dump_arg *arg) | |
} | |
static int | |
-w_obj_each(ID id, VALUE value, struct dump_call_arg *arg) | |
+w_obj_each(sa_index_t id, VALUE value, struct dump_call_arg *arg) | |
{ | |
if (id == rb_id_encoding()) return ST_CONTINUE; | |
if (id == rb_intern("E")) return ST_CONTINUE; | |
@@ -553,13 +553,13 @@ w_encoding(VALUE obj, long num, struct dump_call_arg *arg) | |
} | |
static void | |
-w_ivar(VALUE obj, st_table *tbl, struct dump_call_arg *arg) | |
+w_ivar(VALUE obj, sa_table *tbl, struct dump_call_arg *arg) | |
{ | |
long num = tbl ? tbl->num_entries : 0; | |
w_encoding(obj, num, arg); | |
if (tbl) { | |
- st_foreach_safe(tbl, w_obj_each, (st_data_t)arg); | |
+ sa_foreach(tbl, w_obj_each, (st_data_t)arg); | |
} | |
} | |
@@ -586,7 +586,7 @@ static void | |
w_object(VALUE obj, struct dump_arg *arg, int limit) | |
{ | |
struct dump_call_arg c_arg; | |
- st_table *ivtbl = 0; | |
+ sa_table *ivtbl = 0; | |
st_data_t num; | |
int hasiv = 0; | |
#define has_ivars(obj, ivtbl) (((ivtbl) = rb_generic_ivar_table(obj)) != 0 || \ | |
@@ -651,7 +651,7 @@ w_object(VALUE obj, struct dump_arg *arg, int limit) | |
} | |
if (rb_respond_to(obj, s_dump)) { | |
VALUE v; | |
- st_table *ivtbl2 = 0; | |
+ sa_table *ivtbl2 = 0; | |
int hasiv2; | |
v = rb_funcall(obj, s_dump, 1, INT2NUM(limit)); | |
diff --git a/method.h b/method.h | |
index 9229896..2fecd57 100644 | |
--- a/method.h | |
+++ b/method.h | |
@@ -100,6 +100,6 @@ int rb_method_entry_eq(const rb_method_entry_t *m1, const rb_method_entry_t *m2) | |
void rb_mark_method_entry(const rb_method_entry_t *me); | |
void rb_free_method_entry(rb_method_entry_t *me); | |
void rb_sweep_method_entry(void *vm); | |
-void rb_free_m_table(st_table *tbl); | |
+void rb_free_m_table(sa_table *tbl); | |
#endif /* METHOD_H */ | |
diff --git a/object.c b/object.c | |
index 1ab28a3..dcdc437 100644 | |
--- a/object.c | |
+++ b/object.c | |
@@ -236,17 +236,8 @@ init_copy(VALUE dest, VALUE obj) | |
break; | |
case T_CLASS: | |
case T_MODULE: | |
- if (RCLASS_IV_TBL(dest)) { | |
- st_free_table(RCLASS_IV_TBL(dest)); | |
- RCLASS_IV_TBL(dest) = 0; | |
- } | |
- if (RCLASS_CONST_TBL(dest)) { | |
- rb_free_const_table(RCLASS_CONST_TBL(dest)); | |
- RCLASS_CONST_TBL(dest) = 0; | |
- } | |
- if (RCLASS_IV_TBL(obj)) { | |
- RCLASS_IV_TBL(dest) = st_copy(RCLASS_IV_TBL(obj)); | |
- } | |
+ rb_free_const_table(RCLASS_CONST_TBL(dest)); | |
+ sa_copy_to(RCLASS_IV_TBL(obj), RCLASS_IV_TBL(dest)); | |
break; | |
} | |
} | |
@@ -537,7 +528,7 @@ rb_obj_is_kind_of(VALUE obj, VALUE c) | |
} | |
while (cl) { | |
- if (cl == c || RCLASS_M_TBL(cl) == RCLASS_M_TBL(c)) | |
+ if (cl == c || RCLASS_EXT(cl) == RCLASS_EXT(c)) | |
return Qtrue; | |
cl = RCLASS_SUPER(cl); | |
} | |
@@ -1363,13 +1354,13 @@ rb_class_inherited_p(VALUE mod, VALUE arg) | |
rb_raise(rb_eTypeError, "compared with non class/module"); | |
} | |
while (mod) { | |
- if (RCLASS_M_TBL(mod) == RCLASS_M_TBL(arg)) | |
+ if (RCLASS_EXT(mod) == RCLASS_EXT(arg)) | |
return Qtrue; | |
mod = RCLASS_SUPER(mod); | |
} | |
/* not mod < arg; check if mod > arg */ | |
while (arg) { | |
- if (RCLASS_M_TBL(arg) == RCLASS_M_TBL(start)) | |
+ if (RCLASS_EXT(arg) == RCLASS_EXT(start)) | |
return Qfalse; | |
arg = RCLASS_SUPER(arg); | |
} | |
diff --git a/sp_ar.c b/sp_ar.c | |
new file mode 100644 | |
index 0000000..2ed69bf | |
--- /dev/null | |
+++ b/sp_ar.c | |
@@ -0,0 +1,374 @@ | |
+/* | |
+ * sparse array lib | |
+ * inspired by Lua table | |
+ * written by Sokolov Yura aka funny_falcon | |
+ */ | |
+#ifdef NOT_RUBY | |
+#include "regint.h" | |
+#include "st.h" | |
+#else | |
+#include "ruby/ruby.h" | |
+#endif | |
+ | |
+#include <stdio.h> | |
+#ifdef HAVE_STDLIB_H | |
+#include <stdlib.h> | |
+#endif | |
+#include <string.h> | |
+ | |
+#ifdef RUBY | |
+#define malloc xmalloc | |
+#define calloc xcalloc | |
+#define realloc xrealloc | |
+#define free xfree | |
+#endif | |
+ | |
+#define sa_table_alloc() (sa_table*)malloc(sizeof(sa_table)) | |
+#define sa_table_xalloc() (sa_table*)calloc(1, sizeof(sa_table)) | |
+#define sa_table_dealloc(table) free(table) | |
+#define sa_entry_alloc(n) (sa_entry*)calloc((n), sizeof(sa_entry)) | |
+#define sa_entry_dealloc(entries) free(entries) | |
+ | |
+#define SA_LAST 1 | |
+#define SA_OFFSET 2 | |
+ | |
+#define SA_MIN_SIZE 4 | |
+ | |
+void | |
+sa_init_table(register sa_table *table, sa_index_t num_bins) | |
+{ | |
+ if (num_bins) { | |
+ table->num_entries = 0; | |
+ table->entries = sa_entry_alloc(num_bins); | |
+ table->num_bins = num_bins; | |
+ table->free_pos = num_bins; | |
+ } | |
+ else { | |
+ memset(table, 0, sizeof(sa_table)); | |
+ } | |
+} | |
+ | |
+sa_table* | |
+sa_new_table() | |
+{ | |
+ sa_table* table = sa_table_alloc(); | |
+ sa_init_table(table, 0); | |
+ return table; | |
+} | |
+ | |
+static inline sa_index_t | |
+calc_pos(register sa_table* table, sa_index_t key) | |
+{ | |
+ /* this formula is empirical */ | |
+ /* it has no good avalance, but works well in our case */ | |
+ key ^= key >> 16; | |
+ key *= 0x445229; | |
+ return (key + (key >> 16)) % table->num_bins; | |
+} | |
+ | |
+static void | |
+fix_empty(register sa_table* table) | |
+{ | |
+ while(--table->free_pos && | |
+ table->entries[table->free_pos-1].next != SA_EMPTY); | |
+} | |
+ | |
+#define FLOOR_TO_4 ((~((sa_index_t)0)) << 2) | |
+static sa_index_t | |
+find_empty(register sa_table* table, register sa_index_t pos) | |
+{ | |
+ sa_index_t new_pos = table->free_pos-1; | |
+ sa_entry *entry; | |
+ pos &= FLOOR_TO_4; | |
+ entry = table->entries+pos; | |
+ | |
+ if (entry->next == SA_EMPTY) { new_pos = pos; goto check; } | |
+ pos++; entry++; | |
+ if (entry->next == SA_EMPTY) { new_pos = pos; goto check; } | |
+ pos++; entry++; | |
+ if (entry->next == SA_EMPTY) { new_pos = pos; goto check; } | |
+ pos++; entry++; | |
+ if (entry->next == SA_EMPTY) { new_pos = pos; goto check; } | |
+ | |
+check: | |
+ if (new_pos+1 == table->free_pos) fix_empty(table); | |
+ return new_pos; | |
+} | |
+ | |
+static void resize(register sa_table* table); | |
+static int insert_into_chain(register sa_table*, register sa_index_t, st_data_t, sa_index_t pos); | |
+static int insert_into_main(register sa_table*, sa_index_t, st_data_t, sa_index_t pos, sa_index_t prev_pos); | |
+ | |
+int | |
+sa_insert(register sa_table* table, register sa_index_t key, st_data_t value) | |
+{ | |
+ sa_index_t pos, main_pos; | |
+ register sa_entry *entry; | |
+ | |
+ if (table->num_bins == 0) { | |
+ sa_init_table(table, SA_MIN_SIZE); | |
+ } | |
+ | |
+ pos = calc_pos(table, key); | |
+ entry = table->entries + pos; | |
+ | |
+ if (entry->next == SA_EMPTY) { | |
+ entry->next = SA_LAST; | |
+ entry->key = key; | |
+ entry->value = value; | |
+ table->num_entries++; | |
+ if (pos+1 == table->free_pos) fix_empty(table); | |
+ return 0; | |
+ } | |
+ | |
+ if (entry->key == key) { | |
+ entry->value = value; | |
+ return 1; | |
+ } | |
+ | |
+ if (table->num_entries + (table->num_entries >> 2) > table->num_bins) { | |
+ resize(table); | |
+ return sa_insert(table, key, value); | |
+ } | |
+ | |
+ main_pos = calc_pos(table, entry->key); | |
+ if (main_pos == pos) { | |
+ return insert_into_chain(table, key, value, pos); | |
+ } | |
+ else { | |
+ if (!table->free_pos) { | |
+ resize(table); | |
+ return sa_insert(table, key, value); | |
+ } | |
+ return insert_into_main(table, key, value, pos, main_pos); | |
+ } | |
+} | |
+ | |
+static int | |
+insert_into_chain(register sa_table* table, register sa_index_t key, st_data_t value, sa_index_t pos) | |
+{ | |
+ sa_entry *entry = table->entries + pos, *new_entry; | |
+ sa_index_t new_pos; | |
+ | |
+ while (entry->next != SA_LAST) { | |
+ pos = entry->next - SA_OFFSET; | |
+ entry = table->entries + pos; | |
+ if (entry->key == key) { | |
+ entry->value = value; | |
+ return 1; | |
+ } | |
+ } | |
+ | |
+ if (!table->free_pos) { | |
+ resize(table); | |
+ return sa_insert(table, key, value); | |
+ } | |
+ | |
+ new_pos = find_empty(table, pos); | |
+ new_entry = table->entries + new_pos; | |
+ entry->next = new_pos + SA_OFFSET; | |
+ | |
+ new_entry->next = SA_LAST; | |
+ new_entry->key = key; | |
+ new_entry->value = value; | |
+ table->num_entries++; | |
+ return 0; | |
+} | |
+ | |
+static int | |
+insert_into_main(register sa_table* table, sa_index_t key, st_data_t value, sa_index_t pos, sa_index_t prev_pos) | |
+{ | |
+ sa_entry *entry = table->entries + pos; | |
+ sa_index_t new_pos = find_empty(table, pos); | |
+ sa_entry *new_entry = table->entries + new_pos; | |
+ sa_index_t npos; | |
+ | |
+ *new_entry = *entry; | |
+ | |
+ while((npos = table->entries[prev_pos].next - SA_OFFSET) != pos) { | |
+ prev_pos = npos; | |
+ } | |
+ table->entries[prev_pos].next = new_pos + SA_OFFSET; | |
+ | |
+ entry->next = SA_LAST; | |
+ entry->key = key; | |
+ entry->value = value; | |
+ table->num_entries++; | |
+ return 0; | |
+} | |
+ | |
+static sa_index_t | |
+new_size(sa_index_t num_entries) | |
+{ | |
+ sa_index_t msb = num_entries; | |
+ msb |= msb >> 1; | |
+ msb |= msb >> 2; | |
+ msb |= msb >> 4; | |
+ msb |= msb >> 8; | |
+ msb |= msb >> 16; | |
+ msb = ((msb >> 4) + 1) << 3; | |
+ return (num_entries & (msb | (msb >> 1))) + (msb >> 1); | |
+} | |
+ | |
+static void | |
+resize(register sa_table *table) | |
+{ | |
+ sa_table tmp_table; | |
+ sa_entry *entry; | |
+ sa_index_t i; | |
+ | |
+ if (table->num_entries == 0) { | |
+ sa_entry_dealloc(table->entries); | |
+ memset(table, 0, sizeof(sa_table)); | |
+ return; | |
+ } | |
+ | |
+ sa_init_table(&tmp_table, new_size(table->num_entries + (table->num_entries >> 2))); | |
+ entry = table->entries; | |
+ | |
+ for(i = 0; i < table->num_bins; i++, entry++) { | |
+ if (entry->next != SA_EMPTY) { | |
+ sa_insert(&tmp_table, entry->key, entry->value); | |
+ } | |
+ } | |
+ sa_entry_dealloc(table->entries); | |
+ *table = tmp_table; | |
+} | |
+ | |
+int | |
+sa_lookup(register sa_table *table, register sa_index_t key, st_data_t *value) | |
+{ | |
+ register sa_entry *entry; | |
+ | |
+ if (table->num_entries == 0) return 0; | |
+ | |
+ entry = table->entries + calc_pos(table, key); | |
+ if (entry->next == SA_EMPTY) return 0; | |
+ | |
+ if (entry->key == key) goto found; | |
+ if (entry->next == SA_LAST) return 0; | |
+ | |
+ entry = table->entries + (entry->next - SA_OFFSET); | |
+ if (entry->key == key) goto found; | |
+ | |
+ while(entry->next != SA_LAST) { | |
+ entry = table->entries + (entry->next - SA_OFFSET); | |
+ if (entry->key == key) goto found; | |
+ } | |
+ return 0; | |
+found: | |
+ if (value) *value = entry->value; | |
+ return 1; | |
+} | |
+ | |
+void | |
+sa_clear(sa_table *table) | |
+{ | |
+ sa_entry_dealloc(table->entries); | |
+ memset(table, 0, sizeof(sa_table)); | |
+} | |
+ | |
+void | |
+sa_clear_no_free(sa_table *table) | |
+{ | |
+ memset(table->entries, 0, sizeof(sa_entry) * table->num_bins); | |
+ table->num_entries = 0; | |
+ table->free_pos = table->num_bins; | |
+} | |
+ | |
+void | |
+sa_free_table(sa_table *table) | |
+{ | |
+ sa_entry_dealloc(table->entries); | |
+ sa_table_dealloc(table); | |
+} | |
+ | |
+int | |
+sa_delete(sa_table *table, sa_index_t key, st_data_t *value) | |
+{ | |
+ sa_index_t pos, prev_pos = ~0; | |
+ sa_entry *entry; | |
+ | |
+ if (table->num_entries == 0) goto not_found; | |
+ | |
+ pos = calc_pos(table, key); | |
+ entry = table->entries + pos; | |
+ | |
+ if (entry->next == SA_EMPTY) goto not_found; | |
+ | |
+ do { | |
+ if (entry->key == key) { | |
+ if (value) *value = entry->value; | |
+ if (entry->next != SA_LAST) { | |
+ sa_index_t npos = entry->next - SA_OFFSET; | |
+ *entry = table->entries[npos]; | |
+ memset(table->entries + npos, 0, sizeof(sa_entry)); | |
+ } | |
+ else { | |
+ memset(table->entries + pos, 0, sizeof(sa_entry)); | |
+ if (~prev_pos) { | |
+ table->entries[prev_pos].next = SA_LAST; | |
+ } | |
+ } | |
+ table->num_entries--; | |
+ if (table->num_entries < table->num_bins / 4) { | |
+ resize(table); | |
+ } | |
+ return 1; | |
+ } | |
+ if (entry->next == SA_LAST) break; | |
+ prev_pos = pos; | |
+ pos = entry->next - SA_OFFSET; | |
+ entry = table->entries + pos; | |
+ } while(1); | |
+ | |
+not_found: | |
+ if (value) *value = 0; | |
+ return 0; | |
+} | |
+ | |
+int | |
+sa_foreach(register sa_table *table, int (*func)(), st_data_t arg) | |
+{ | |
+ sa_index_t i; | |
+ if (table->num_bins == 0) { | |
+ return 0; | |
+ } | |
+ for(i = 0; i < table->num_bins ; i++) { | |
+ if (table->entries[i].next != SA_EMPTY) { | |
+ sa_index_t key = table->entries[i].key; | |
+ st_data_t val = table->entries[i].value; | |
+ if ((*func)(key, val, arg) == SA_STOP) break; | |
+ } | |
+ } | |
+ return 0; | |
+} | |
+ | |
+size_t | |
+sa_memsize(const sa_table *table) | |
+{ | |
+ return sizeof(sa_table) + table->num_bins * sizeof(sa_entry); | |
+} | |
+ | |
+sa_table* | |
+sa_copy(sa_table *table) | |
+{ | |
+ sa_table *new_table = sa_table_alloc(); | |
+ *new_table = *table; | |
+ if (table->num_bins) { | |
+ new_table->entries = sa_entry_alloc(table->num_bins); | |
+ memcpy(new_table->entries, table->entries, table->num_bins*sizeof(sa_entry)); | |
+ } | |
+ return new_table; | |
+} | |
+ | |
+void | |
+sa_copy_to(sa_table *from, sa_table *to) | |
+{ | |
+ sa_entry_dealloc(to->entries); | |
+ *to = *from; | |
+ if (to->num_bins) { | |
+ to->entries = sa_entry_alloc(to->num_bins); | |
+ memcpy(to->entries, from->entries, from->num_bins*sizeof(sa_entry)); | |
+ } | |
+} | |
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb | |
index 0f3f794..5c2e502 100644 | |
--- a/test/ruby/test_marshal.rb | |
+++ b/test/ruby/test_marshal.rb | |
@@ -312,7 +312,7 @@ class TestMarshal < Test::Unit::TestCase | |
assert_equal(a.instance_variable_get(i), b.instance_variable_get(i), bug1932) | |
end | |
end | |
- a.__send__(a.methods(true).grep(/=\z/)[0], a) | |
+ a.__send__(a.methods(true).grep(/r.*=\z/)[0], a) | |
assert_nothing_raised(bug1932) do | |
b = Marshal.load(Marshal.dump(a)) | |
assert_equal(ClassISO8859_1, b.class, bug1932) | |
diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb | |
index 889c50e..9af6336 100644 | |
--- a/test/ruby/test_method.rb | |
+++ b/test/ruby/test_method.rb | |
@@ -406,7 +406,7 @@ class TestMethod < Test::Unit::TestCase | |
obj = a.new | |
assert_equal([:a], obj.public_methods(false), bug) | |
obj.extend(m) | |
- assert_equal([:m1, :a], obj.public_methods(false), bug) | |
+ assert_equal([:a, :m1], obj.public_methods(false).sort, bug) | |
end | |
def test_visibility | |
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb | |
index 1fe67df..7f2c663 100644 | |
--- a/test/ruby/test_module.rb | |
+++ b/test/ruby/test_module.rb | |
@@ -894,7 +894,7 @@ class TestModule < Test::Unit::TestCase | |
(class << self ; self ; end).class_eval do | |
define_method :method_added do |sym| | |
memo << sym | |
- memo << mod.instance_methods(false) | |
+ memo << mod.instance_methods(false).sort | |
memo << (mod.instance_method(sym) rescue nil) | |
end | |
end | |
@@ -911,10 +911,10 @@ class TestModule < Test::Unit::TestCase | |
assert_equal [:f, :g], memo.shift | |
assert_equal mod.instance_method(:f), memo.shift | |
assert_equal :a, memo.shift | |
- assert_equal [:f, :g, :a], memo.shift | |
+ assert_equal [:a, :f, :g], memo.shift | |
assert_equal mod.instance_method(:a), memo.shift | |
assert_equal :a=, memo.shift | |
- assert_equal [:f, :g, :a, :a=], memo.shift | |
+ assert_equal [:a, :a=, :f, :g], memo.shift | |
assert_equal mod.instance_method(:a=), memo.shift | |
end | |
diff --git a/time.c b/time.c | |
index 6fd36f0..9146414 100644 | |
--- a/time.c | |
+++ b/time.c | |
@@ -4745,16 +4745,14 @@ time_mload(VALUE time, VALUE str) | |
long nsec; | |
VALUE submicro, nano_num, nano_den, offset; | |
wideval_t timew; | |
- st_data_t data; | |
time_modify(time); | |
#define get_attr(attr, iffound) \ | |
attr = rb_attr_get(str, id_##attr); \ | |
if (!NIL_P(attr)) { \ | |
- data = id_##attr; \ | |
iffound; \ | |
- st_delete(rb_generic_ivar_table(str), &data, 0); \ | |
+ sa_delete(rb_generic_ivar_table(str), (sa_index_t)id_##attr, 0); \ | |
} | |
get_attr(nano_num, {}); | |
diff --git a/variable.c b/variable.c | |
index 3da500e..4a61453 100644 | |
--- a/variable.c | |
+++ b/variable.c | |
@@ -19,15 +19,15 @@ | |
#include "constant.h" | |
#include "internal.h" | |
-st_table *rb_global_tbl; | |
-st_table *rb_class_tbl; | |
+sa_table rb_global_tbl; | |
+sa_table rb_class_tbl; | |
static ID autoload, classpath, tmp_classpath, classid; | |
void | |
Init_var_tables(void) | |
{ | |
- rb_global_tbl = st_init_numtable(); | |
- rb_class_tbl = st_init_numtable(); | |
+ sa_init_table(&rb_global_tbl, 0); | |
+ sa_init_table(&rb_class_tbl, 0); | |
CONST_ID(autoload, "__autoload__"); | |
CONST_ID(classpath, "__classpath__"); | |
CONST_ID(tmp_classpath, "__tmp_classpath__"); | |
@@ -43,7 +43,7 @@ struct fc_result { | |
}; | |
static VALUE | |
-fc_path(struct fc_result *fc, ID name) | |
+fc_path(struct fc_result *fc, sa_index_t name) | |
{ | |
VALUE path, tmp; | |
@@ -51,8 +51,7 @@ fc_path(struct fc_result *fc, ID name) | |
while (fc) { | |
st_data_t n; | |
if (fc->track == rb_cObject) break; | |
- if (RCLASS_IV_TBL(fc->track) && | |
- st_lookup(RCLASS_IV_TBL(fc->track), (st_data_t)classpath, &n)) { | |
+ if (sa_lookup(RCLASS_IV_TBL(fc->track), (sa_index_t)classpath, &n)) { | |
tmp = rb_str_dup((VALUE)n); | |
rb_str_cat2(tmp, "::"); | |
rb_str_append(tmp, path); | |
@@ -70,7 +69,7 @@ fc_path(struct fc_result *fc, ID name) | |
} | |
static int | |
-fc_i(ID key, rb_const_entry_t *ce, struct fc_result *res) | |
+fc_i(sa_index_t key, rb_const_entry_t *ce, struct fc_result *res) | |
{ | |
VALUE value = ce->value; | |
if (!rb_is_const_id(key)) return ST_CONTINUE; | |
@@ -98,7 +97,7 @@ fc_i(ID key, rb_const_entry_t *ce, struct fc_result *res) | |
arg.klass = res->klass; | |
arg.track = value; | |
arg.prev = res; | |
- st_foreach(RCLASS_CONST_TBL(value), fc_i, (st_data_t)&arg); | |
+ sa_foreach(RCLASS_CONST_TBL(value), fc_i, (st_data_t)&arg); | |
if (arg.path) { | |
res->path = arg.path; | |
return ST_STOP; | |
@@ -123,18 +122,14 @@ find_class_path(VALUE klass) | |
arg.track = rb_cObject; | |
arg.prev = 0; | |
if (RCLASS_CONST_TBL(rb_cObject)) { | |
- st_foreach_safe(RCLASS_CONST_TBL(rb_cObject), fc_i, (st_data_t)&arg); | |
+ sa_foreach(RCLASS_CONST_TBL(rb_cObject), fc_i, (st_data_t)&arg); | |
} | |
if (arg.path == 0) { | |
- st_foreach_safe(rb_class_tbl, fc_i, (st_data_t)&arg); | |
+ sa_foreach(&rb_class_tbl, fc_i, (st_data_t)&arg); | |
} | |
if (arg.path) { | |
- st_data_t tmp = tmp_classpath; | |
- if (!RCLASS_IV_TBL(klass)) { | |
- RCLASS_IV_TBL(klass) = st_init_numtable(); | |
- } | |
- st_insert(RCLASS_IV_TBL(klass), (st_data_t)classpath, arg.path); | |
- st_delete(RCLASS_IV_TBL(klass), &tmp, 0); | |
+ sa_insert(RCLASS_IV_TBL(klass), (sa_index_t)classpath, arg.path); | |
+ sa_delete(RCLASS_IV_TBL(klass), (sa_index_t)tmp_classpath, 0); | |
return arg.path; | |
} | |
return Qnil; | |
@@ -147,16 +142,15 @@ classname(VALUE klass) | |
st_data_t n; | |
if (!klass) klass = rb_cObject; | |
- if (RCLASS_IV_TBL(klass)) { | |
- if (!st_lookup(RCLASS_IV_TBL(klass), (st_data_t)classpath, &n)) { | |
- if (!st_lookup(RCLASS_IV_TBL(klass), (st_data_t)classid, &n)) { | |
+ if (RCLASS_IV_TBL(klass)->num_entries) { | |
+ if (!sa_lookup(RCLASS_IV_TBL(klass), (sa_index_t)classpath, &n)) { | |
+ if (!sa_lookup(RCLASS_IV_TBL(klass), (sa_index_t)classid, &n)) { | |
return find_class_path(klass); | |
} | |
path = rb_str_dup(rb_id2str(SYM2ID((VALUE)n))); | |
OBJ_FREEZE(path); | |
- st_insert(RCLASS_IV_TBL(klass), (st_data_t)classpath, (st_data_t)path); | |
- n = classid; | |
- st_delete(RCLASS_IV_TBL(klass), &n, 0); | |
+ sa_insert(RCLASS_IV_TBL(klass), (sa_index_t)classpath, (st_data_t)path); | |
+ sa_delete(RCLASS_IV_TBL(klass), (sa_index_t)classid, 0); | |
} | |
else { | |
path = (VALUE)n; | |
@@ -192,8 +186,7 @@ rb_class_path(VALUE klass) | |
st_data_t n = (st_data_t)path; | |
if (!NIL_P(path)) return path; | |
- if (RCLASS_IV_TBL(klass) && st_lookup(RCLASS_IV_TBL(klass), | |
- (st_data_t)tmp_classpath, &n)) { | |
+ if (sa_lookup(RCLASS_IV_TBL(klass), (sa_index_t)tmp_classpath, &n)) { | |
return (VALUE)n; | |
} | |
else { | |
@@ -364,7 +357,7 @@ rb_global_entry(ID id) | |
struct global_entry *entry; | |
st_data_t data; | |
- if (!st_lookup(rb_global_tbl, (st_data_t)id, &data)) { | |
+ if (!sa_lookup(&rb_global_tbl, (sa_index_t)id, &data)) { | |
struct global_variable *var; | |
entry = ALLOC(struct global_entry); | |
var = ALLOC(struct global_variable); | |
@@ -378,7 +371,7 @@ rb_global_entry(ID id) | |
var->block_trace = 0; | |
var->trace = 0; | |
- st_add_direct(rb_global_tbl, id, (st_data_t)entry); | |
+ sa_insert(&rb_global_tbl, (sa_index_t)id, (st_data_t)entry); | |
} | |
else { | |
entry = (struct global_entry *)data; | |
@@ -454,8 +447,8 @@ readonly_setter(VALUE val, ID id, void *data, struct global_variable *gvar) | |
rb_name_error(id, "%s is a read-only variable", rb_id2name(id)); | |
} | |
-static int | |
-mark_global_entry(ID key, struct global_entry *entry) | |
+static void | |
+mark_global_entry(struct global_entry *entry) | |
{ | |
struct trace_var *trace; | |
struct global_variable *var = entry->var; | |
@@ -466,14 +459,14 @@ mark_global_entry(ID key, struct global_entry *entry) | |
if (trace->data) rb_gc_mark_maybe(trace->data); | |
trace = trace->next; | |
} | |
- return ST_CONTINUE; | |
} | |
void | |
rb_gc_mark_global_tbl(void) | |
{ | |
- if (rb_global_tbl) | |
- st_foreach_safe(rb_global_tbl, mark_global_entry, 0); | |
+ SA_FOREACH_START(&rb_global_tbl); | |
+ mark_global_entry((struct global_entry*) value); | |
+ SA_FOREACH_END(); | |
} | |
static ID | |
@@ -635,7 +628,7 @@ rb_f_untrace_var(int argc, VALUE *argv) | |
rb_secure(4); | |
rb_scan_args(argc, argv, "11", &var, &cmd); | |
id = rb_to_id(var); | |
- if (!st_lookup(rb_global_tbl, (st_data_t)id, &data)) { | |
+ if (!sa_lookup(&rb_global_tbl, (sa_index_t)id, &data)) { | |
rb_name_error(id, "undefined global variable %s", rb_id2name(id)); | |
} | |
@@ -742,13 +735,6 @@ rb_gvar_defined(struct global_entry *entry) | |
return Qtrue; | |
} | |
-static int | |
-gvar_i(ID key, struct global_entry *entry, VALUE ary) | |
-{ | |
- rb_ary_push(ary, ID2SYM(key)); | |
- return ST_CONTINUE; | |
-} | |
- | |
/* | |
* call-seq: | |
* global_variables -> array | |
@@ -765,7 +751,9 @@ rb_f_global_variables(void) | |
char buf[2]; | |
int i; | |
- st_foreach_safe(rb_global_tbl, gvar_i, ary); | |
+ SA_FOREACH_START(&rb_global_tbl); | |
+ rb_ary_push(ary, ID2SYM(entry->key)); | |
+ SA_FOREACH_END(); | |
buf[0] = '$'; | |
for (i = 1; i <= 9; ++i) { | |
buf[1] = (char)(i + '0'); | |
@@ -784,10 +772,10 @@ rb_alias_variable(ID name1, ID name2) | |
rb_raise(rb_eSecurityError, "Insecure: can't alias global variable"); | |
entry2 = rb_global_entry(name2); | |
- if (!st_lookup(rb_global_tbl, (st_data_t)name1, &data1)) { | |
+ if (!sa_lookup(&rb_global_tbl, (sa_index_t)name1, &data1)) { | |
entry1 = ALLOC(struct global_entry); | |
entry1->id = name1; | |
- st_add_direct(rb_global_tbl, name1, (st_data_t)entry1); | |
+ sa_insert(&rb_global_tbl, (sa_index_t)name1, (st_data_t)entry1); | |
} | |
else if ((entry1 = (struct global_entry *)data1)->var != entry2->var) { | |
struct global_variable *var = entry1->var; | |
@@ -815,7 +803,7 @@ rb_alias_variable(ID name1, ID name2) | |
static int special_generic_ivar = 0; | |
static st_table *generic_iv_tbl; | |
-st_table* | |
+sa_table* | |
rb_generic_ivar_table(VALUE obj) | |
{ | |
st_data_t tbl; | |
@@ -823,7 +811,7 @@ rb_generic_ivar_table(VALUE obj) | |
if (!FL_TEST(obj, FL_EXIVAR)) return 0; | |
if (!generic_iv_tbl) return 0; | |
if (!st_lookup(generic_iv_tbl, (st_data_t)obj, &tbl)) return 0; | |
- return (st_table *)tbl; | |
+ return (sa_table *)tbl; | |
} | |
static VALUE | |
@@ -833,7 +821,7 @@ generic_ivar_get(VALUE obj, ID id, int warn) | |
if (generic_iv_tbl) { | |
if (st_lookup(generic_iv_tbl, (st_data_t)obj, &tbl)) { | |
- if (st_lookup((st_table *)tbl, (st_data_t)id, &val)) { | |
+ if (sa_lookup((sa_table *)tbl, (sa_index_t)id, &val)) { | |
return (VALUE)val; | |
} | |
} | |
@@ -847,7 +835,6 @@ generic_ivar_get(VALUE obj, ID id, int warn) | |
static void | |
generic_ivar_set(VALUE obj, ID id, VALUE val) | |
{ | |
- st_table *tbl; | |
st_data_t data; | |
if (rb_special_const_p(obj)) { | |
@@ -859,24 +846,22 @@ generic_ivar_set(VALUE obj, ID id, VALUE val) | |
} | |
if (!st_lookup(generic_iv_tbl, (st_data_t)obj, &data)) { | |
FL_SET(obj, FL_EXIVAR); | |
- tbl = st_init_numtable(); | |
- st_add_direct(generic_iv_tbl, (st_data_t)obj, (st_data_t)tbl); | |
- st_add_direct(tbl, (st_data_t)id, (st_data_t)val); | |
- return; | |
+ data = (st_data_t)sa_new_table(); | |
+ st_add_direct(generic_iv_tbl, (st_data_t)obj, data); | |
} | |
- st_insert((st_table *)data, (st_data_t)id, (st_data_t)val); | |
+ sa_insert((sa_table *)data, (sa_index_t)id, (st_data_t)val); | |
} | |
static VALUE | |
generic_ivar_defined(VALUE obj, ID id) | |
{ | |
- st_table *tbl; | |
+ sa_table *tbl; | |
st_data_t data; | |
if (!generic_iv_tbl) return Qfalse; | |
if (!st_lookup(generic_iv_tbl, (st_data_t)obj, &data)) return Qfalse; | |
- tbl = (st_table *)data; | |
- if (st_lookup(tbl, (st_data_t)id, &data)) { | |
+ tbl = (sa_table *)data; | |
+ if (sa_lookup(tbl, (sa_index_t)id, &data)) { | |
return Qtrue; | |
} | |
return Qfalse; | |
@@ -885,18 +870,18 @@ generic_ivar_defined(VALUE obj, ID id) | |
static int | |
generic_ivar_remove(VALUE obj, ID id, st_data_t *valp) | |
{ | |
- st_table *tbl; | |
+ sa_table *tbl; | |
st_data_t data, key = (st_data_t)id; | |
int status; | |
if (!generic_iv_tbl) return 0; | |
if (!st_lookup(generic_iv_tbl, (st_data_t)obj, &data)) return 0; | |
- tbl = (st_table *)data; | |
- status = st_delete(tbl, &key, valp); | |
+ tbl = (sa_table *)data; | |
+ status = sa_delete(tbl, (sa_index_t)id, valp); | |
if (tbl->num_entries == 0) { | |
key = (st_data_t)obj; | |
st_delete(generic_iv_tbl, &key, &data); | |
- st_free_table((st_table *)data); | |
+ sa_free_table(tbl); | |
} | |
return status; | |
} | |
@@ -908,22 +893,17 @@ rb_mark_generic_ivar(VALUE obj) | |
if (!generic_iv_tbl) return; | |
if (st_lookup(generic_iv_tbl, (st_data_t)obj, &tbl)) { | |
- rb_mark_tbl((st_table *)tbl); | |
+ rb_mark_sa_tbl((sa_table *)tbl); | |
} | |
} | |
static int | |
-givar_mark_i(ID key, VALUE value) | |
-{ | |
- rb_gc_mark(value); | |
- return ST_CONTINUE; | |
-} | |
- | |
-static int | |
-givar_i(VALUE obj, st_table *tbl) | |
+givar_i(VALUE obj, sa_table *tbl) | |
{ | |
if (rb_special_const_p(obj)) { | |
- st_foreach_safe(tbl, givar_mark_i, 0); | |
+ SA_FOREACH_START(tbl); | |
+ rb_gc_mark((VALUE)value); | |
+ SA_FOREACH_END(); | |
} | |
return ST_CONTINUE; | |
} | |
@@ -943,7 +923,7 @@ rb_free_generic_ivar(VALUE obj) | |
if (!generic_iv_tbl) return; | |
if (st_delete(generic_iv_tbl, &key, &tbl)) | |
- st_free_table((st_table *)tbl); | |
+ sa_free_table((sa_table *)tbl); | |
} | |
RUBY_FUNC_EXPORTED size_t | |
@@ -951,7 +931,7 @@ rb_generic_ivar_memsize(VALUE obj) | |
{ | |
st_data_t tbl; | |
if (st_lookup(generic_iv_tbl, (st_data_t)obj, &tbl)) | |
- return st_memsize((st_table *)tbl); | |
+ return sa_memsize((sa_table *)tbl); | |
return 0; | |
} | |
@@ -970,17 +950,17 @@ rb_copy_generic_ivar(VALUE clone, VALUE obj) | |
return; | |
} | |
if (st_lookup(generic_iv_tbl, (st_data_t)obj, &data)) { | |
- st_table *tbl = (st_table *)data; | |
+ sa_table *tbl = (sa_table *)data; | |
if (tbl->num_entries == 0) | |
goto clear; | |
if (st_lookup(generic_iv_tbl, (st_data_t)clone, &data)) { | |
- st_free_table((st_table *)data); | |
- st_insert(generic_iv_tbl, (st_data_t)clone, (st_data_t)st_copy(tbl)); | |
+ sa_free_table((sa_table *)data); | |
+ st_insert(generic_iv_tbl, (st_data_t)clone, (st_data_t)sa_copy(tbl)); | |
} | |
else { | |
- st_add_direct(generic_iv_tbl, (st_data_t)clone, (st_data_t)st_copy(tbl)); | |
+ st_add_direct(generic_iv_tbl, (st_data_t)clone, (st_data_t)sa_copy(tbl)); | |
FL_SET(clone, FL_EXIVAR); | |
} | |
} | |
@@ -990,7 +970,7 @@ static VALUE | |
ivar_get(VALUE obj, ID id, int warn) | |
{ | |
VALUE val, *ptr; | |
- struct st_table *iv_index_tbl; | |
+ sa_table *iv_index_tbl; | |
long len; | |
st_data_t index; | |
@@ -1000,7 +980,7 @@ ivar_get(VALUE obj, ID id, int warn) | |
ptr = ROBJECT_IVPTR(obj); | |
iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); | |
if (!iv_index_tbl) break; | |
- if (!st_lookup(iv_index_tbl, (st_data_t)id, &index)) break; | |
+ if (!sa_lookup(iv_index_tbl, (sa_index_t)id, &index)) break; | |
if (len <= (long)index) break; | |
val = ptr[index]; | |
if (val != Qundef) | |
@@ -1008,7 +988,7 @@ ivar_get(VALUE obj, ID id, int warn) | |
break; | |
case T_CLASS: | |
case T_MODULE: | |
- if (RCLASS_IV_TBL(obj) && st_lookup(RCLASS_IV_TBL(obj), (st_data_t)id, &index)) | |
+ if (sa_lookup(RCLASS_IV_TBL(obj), (sa_index_t)id, &index)) | |
return (VALUE)index; | |
break; | |
default: | |
@@ -1037,7 +1017,7 @@ rb_attr_get(VALUE obj, ID id) | |
VALUE | |
rb_ivar_set(VALUE obj, ID id, VALUE val) | |
{ | |
- struct st_table *iv_index_tbl; | |
+ struct sa_table *iv_index_tbl; | |
st_data_t index; | |
long i, len; | |
int ivar_extended; | |
@@ -1051,14 +1031,11 @@ rb_ivar_set(VALUE obj, ID id, VALUE val) | |
if (!iv_index_tbl) { | |
VALUE klass = rb_obj_class(obj); | |
iv_index_tbl = RCLASS_IV_INDEX_TBL(klass); | |
- if (!iv_index_tbl) { | |
- iv_index_tbl = RCLASS_IV_INDEX_TBL(klass) = st_init_numtable(); | |
- } | |
} | |
ivar_extended = 0; | |
- if (!st_lookup(iv_index_tbl, (st_data_t)id, &index)) { | |
+ if (!sa_lookup(iv_index_tbl, (sa_index_t)id, &index)) { | |
index = iv_index_tbl->num_entries; | |
- st_add_direct(iv_index_tbl, (st_data_t)id, index); | |
+ sa_insert(iv_index_tbl, (sa_index_t)id, index); | |
ivar_extended = 1; | |
} | |
len = ROBJECT_NUMIV(obj); | |
@@ -1098,8 +1075,7 @@ rb_ivar_set(VALUE obj, ID id, VALUE val) | |
break; | |
case T_CLASS: | |
case T_MODULE: | |
- if (!RCLASS_IV_TBL(obj)) RCLASS_IV_TBL(obj) = st_init_numtable(); | |
- st_insert(RCLASS_IV_TBL(obj), (st_data_t)id, val); | |
+ sa_insert(RCLASS_IV_TBL(obj), (sa_index_t)id, val); | |
break; | |
default: | |
generic_ivar_set(obj, id, val); | |
@@ -1112,13 +1088,13 @@ VALUE | |
rb_ivar_defined(VALUE obj, ID id) | |
{ | |
VALUE val; | |
- struct st_table *iv_index_tbl; | |
+ struct sa_table *iv_index_tbl; | |
st_data_t index; | |
switch (TYPE(obj)) { | |
case T_OBJECT: | |
iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); | |
if (!iv_index_tbl) break; | |
- if (!st_lookup(iv_index_tbl, (st_data_t)id, &index)) break; | |
+ if (!sa_lookup(iv_index_tbl, (sa_index_t)id, &index)) break; | |
if (ROBJECT_NUMIV(obj) <= (long)index) break; | |
val = ROBJECT_IVPTR(obj)[index]; | |
if (val != Qundef) | |
@@ -1126,7 +1102,7 @@ rb_ivar_defined(VALUE obj, ID id) | |
break; | |
case T_CLASS: | |
case T_MODULE: | |
- if (RCLASS_IV_TBL(obj) && st_lookup(RCLASS_IV_TBL(obj), (st_data_t)id, 0)) | |
+ if (sa_lookup(RCLASS_IV_TBL(obj), (sa_index_t)id, 0)) | |
return Qtrue; | |
break; | |
default: | |
@@ -1137,40 +1113,26 @@ rb_ivar_defined(VALUE obj, ID id) | |
return Qfalse; | |
} | |
-struct obj_ivar_tag { | |
- VALUE obj; | |
- int (*func)(ID key, VALUE val, st_data_t arg); | |
- st_data_t arg; | |
-}; | |
- | |
-static int | |
-obj_ivar_i(st_data_t key, st_data_t index, st_data_t arg) | |
-{ | |
- struct obj_ivar_tag *data = (struct obj_ivar_tag *)arg; | |
- if ((long)index < ROBJECT_NUMIV(data->obj)) { | |
- VALUE val = ROBJECT_IVPTR(data->obj)[(long)index]; | |
- if (val != Qundef) { | |
- return (data->func)((ID)key, val, data->arg); | |
- } | |
- } | |
- return ST_CONTINUE; | |
-} | |
- | |
static void | |
obj_ivar_each(VALUE obj, int (*func)(ANYARGS), st_data_t arg) | |
{ | |
- st_table *tbl; | |
- struct obj_ivar_tag data; | |
+ sa_table *tbl; | |
+ long numiv = ROBJECT_NUMIV(obj); | |
+ VALUE *vars = ROBJECT_IVPTR(obj); | |
tbl = ROBJECT_IV_INDEX_TBL(obj); | |
- if (!tbl) | |
+ if (!tbl || !numiv) | |
return; | |
- data.obj = obj; | |
- data.func = (int (*)(ID key, VALUE val, st_data_t arg))func; | |
- data.arg = arg; | |
- | |
- st_foreach_safe(tbl, obj_ivar_i, (st_data_t)&data); | |
+ SA_FOREACH_START(tbl); | |
+ if ((long)value < numiv) { | |
+ VALUE val = vars[value]; | |
+ if (val != Qundef) { | |
+ if (((sa_iter_func)func)(entry->key, val, arg) == SA_STOP) | |
+ break; | |
+ } | |
+ } | |
+ SA_FOREACH_END(); | |
} | |
void | |
@@ -1182,9 +1144,7 @@ rb_ivar_foreach(VALUE obj, int (*func)(ANYARGS), st_data_t arg) | |
break; | |
case T_CLASS: | |
case T_MODULE: | |
- if (RCLASS_IV_TBL(obj)) { | |
- st_foreach_safe(RCLASS_IV_TBL(obj), func, arg); | |
- } | |
+ sa_foreach(RCLASS_IV_TBL(obj), func, arg); | |
break; | |
default: | |
if (!generic_iv_tbl) break; | |
@@ -1192,7 +1152,7 @@ rb_ivar_foreach(VALUE obj, int (*func)(ANYARGS), st_data_t arg) | |
st_data_t tbl; | |
if (st_lookup(generic_iv_tbl, (st_data_t)obj, &tbl)) { | |
- st_foreach_safe((st_table *)tbl, func, arg); | |
+ sa_foreach((sa_table *)tbl, func, arg); | |
} | |
} | |
break; | |
@@ -1202,11 +1162,11 @@ rb_ivar_foreach(VALUE obj, int (*func)(ANYARGS), st_data_t arg) | |
st_index_t | |
rb_ivar_count(VALUE obj) | |
{ | |
- st_table *tbl; | |
+ sa_table *tbl; | |
switch (TYPE(obj)) { | |
case T_OBJECT: | |
if ((tbl = ROBJECT_IV_INDEX_TBL(obj)) != 0) { | |
- st_index_t i, count, num = tbl->num_entries; | |
+ sa_index_t i, count, num = tbl->num_entries; | |
const VALUE *const ivptr = ROBJECT_IVPTR(obj); | |
for (i = count = 0; i < num; ++i) { | |
if (ivptr[i] != Qundef) { | |
@@ -1228,7 +1188,7 @@ rb_ivar_count(VALUE obj) | |
st_data_t data; | |
if (st_lookup(generic_iv_tbl, (st_data_t)obj, &data) && | |
- (tbl = (st_table *)data) != 0) { | |
+ (tbl = (sa_table *)data) != 0) { | |
return tbl->num_entries; | |
} | |
} | |
@@ -1238,7 +1198,7 @@ rb_ivar_count(VALUE obj) | |
} | |
static int | |
-ivar_i(ID key, VALUE val, VALUE ary) | |
+ivar_i(sa_index_t key, VALUE val, VALUE ary) | |
{ | |
if (rb_is_instance_id(key)) { | |
rb_ary_push(ary, ID2SYM(key)); | |
@@ -1301,7 +1261,7 @@ rb_obj_remove_instance_variable(VALUE obj, VALUE name) | |
VALUE val = Qnil; | |
const ID id = rb_to_id(name); | |
st_data_t n, v; | |
- struct st_table *iv_index_tbl; | |
+ struct sa_table *iv_index_tbl; | |
st_data_t index; | |
if (!OBJ_UNTRUSTED(obj) && rb_safe_level() >= 4) | |
@@ -1315,7 +1275,7 @@ rb_obj_remove_instance_variable(VALUE obj, VALUE name) | |
case T_OBJECT: | |
iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); | |
if (!iv_index_tbl) break; | |
- if (!st_lookup(iv_index_tbl, (st_data_t)id, &index)) break; | |
+ if (!sa_lookup(iv_index_tbl, (sa_index_t)id, &index)) break; | |
if (ROBJECT_NUMIV(obj) <= (long)index) break; | |
val = ROBJECT_IVPTR(obj)[index]; | |
if (val != Qundef) { | |
@@ -1326,14 +1286,14 @@ rb_obj_remove_instance_variable(VALUE obj, VALUE name) | |
case T_CLASS: | |
case T_MODULE: | |
n = id; | |
- if (RCLASS_IV_TBL(obj) && st_delete(RCLASS_IV_TBL(obj), &n, &v)) { | |
+ if (sa_delete(RCLASS_IV_TBL(obj), (sa_index_t)id, &v)) { | |
return (VALUE)v; | |
} | |
break; | |
default: | |
if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj)) { | |
v = val; | |
- if (generic_ivar_remove(obj, (st_data_t)id, &v)) { | |
+ if (generic_ivar_remove(obj, id, &v)) { | |
return (VALUE)v; | |
} | |
} | |
@@ -1410,20 +1370,20 @@ rb_mod_const_missing(VALUE klass, VALUE name) | |
static void | |
autoload_mark(void *ptr) | |
{ | |
- rb_mark_tbl((st_table *)ptr); | |
+ rb_mark_sa_tbl((sa_table *)ptr); | |
} | |
static void | |
autoload_free(void *ptr) | |
{ | |
- st_free_table((st_table *)ptr); | |
+ sa_free_table((sa_table *)ptr); | |
} | |
static size_t | |
autoload_memsize(const void *ptr) | |
{ | |
- const st_table *tbl = ptr; | |
- return st_memsize(tbl); | |
+ const sa_table *tbl = ptr; | |
+ return sa_memsize(tbl); | |
} | |
static const rb_data_type_t autoload_data_type = { | |
@@ -1432,14 +1392,14 @@ static const rb_data_type_t autoload_data_type = { | |
}; | |
#define check_autoload_table(av) \ | |
- (struct st_table *)rb_check_typeddata((av), &autoload_data_type) | |
+ (struct sa_table *)rb_check_typeddata((av), &autoload_data_type) | |
void | |
rb_autoload(VALUE mod, ID id, const char *file) | |
{ | |
st_data_t av; | |
VALUE fn; | |
- struct st_table *tbl; | |
+ struct sa_table *tbl; | |
if (!rb_is_const_id(id)) { | |
rb_raise(rb_eNameError, "autoload must be constant name: %s", rb_id2name(id)); | |
@@ -1448,43 +1408,41 @@ rb_autoload(VALUE mod, ID id, const char *file) | |
rb_raise(rb_eArgError, "empty file name"); | |
} | |
- if ((tbl = RCLASS_CONST_TBL(mod)) && st_lookup(tbl, (st_data_t)id, &av) && ((rb_const_entry_t*)av)->value != Qundef) | |
+ if (sa_lookup(RCLASS_CONST_TBL(mod), (sa_index_t)id, &av) && ((rb_const_entry_t*)av)->value != Qundef) | |
return; | |
rb_const_set(mod, id, Qundef); | |
tbl = RCLASS_IV_TBL(mod); | |
- if (tbl && st_lookup(tbl, (st_data_t)autoload, &av)) { | |
+ if (sa_lookup(tbl, (sa_index_t)autoload, &av)) { | |
tbl = check_autoload_table((VALUE)av); | |
} | |
else { | |
- if (!tbl) tbl = RCLASS_IV_TBL(mod) = st_init_numtable(); | |
av = (st_data_t)TypedData_Wrap_Struct(0, &autoload_data_type, 0); | |
- st_add_direct(tbl, (st_data_t)autoload, av); | |
- DATA_PTR(av) = tbl = st_init_numtable(); | |
+ sa_insert(tbl, (sa_index_t)autoload, av); | |
+ DATA_PTR(av) = tbl = sa_new_table(); | |
} | |
fn = rb_str_new2(file); | |
FL_UNSET(fn, FL_TAINT); | |
OBJ_FREEZE(fn); | |
- st_insert(tbl, (st_data_t)id, (st_data_t)rb_node_newnode(NODE_MEMO, fn, rb_safe_level(), 0)); | |
+ sa_insert(tbl, (sa_index_t)id, (st_data_t)rb_node_newnode(NODE_MEMO, fn, rb_safe_level(), 0)); | |
} | |
static NODE* | |
autoload_delete(VALUE mod, ID id) | |
{ | |
- st_data_t val, load = 0, n = id; | |
+ st_data_t val, load = 0; | |
rb_const_entry_t *ce; | |
- st_delete(RCLASS_CONST_TBL(mod), &n, &val); | |
+ sa_delete(RCLASS_CONST_TBL(mod), (sa_index_t)id, &val); | |
ce = (rb_const_entry_t*)val; | |
if (ce) xfree(ce); | |
- if (st_lookup(RCLASS_IV_TBL(mod), (st_data_t)autoload, &val)) { | |
- struct st_table *tbl = check_autoload_table((VALUE)val); | |
+ if (sa_lookup(RCLASS_IV_TBL(mod), (sa_index_t)autoload, &val)) { | |
+ struct sa_table *tbl = check_autoload_table((VALUE)val); | |
- st_delete(tbl, &n, &load); | |
+ sa_delete(tbl, (sa_index_t)id, &load); | |
if (tbl->num_entries == 0) { | |
- n = autoload; | |
- st_delete(RCLASS_IV_TBL(mod), &n, &val); | |
+ sa_delete(RCLASS_IV_TBL(mod), (sa_index_t)autoload, &val); | |
} | |
} | |
@@ -1509,14 +1467,14 @@ static NODE * | |
autoload_node(VALUE mod, ID id, const char **loadingpath) | |
{ | |
VALUE file; | |
- struct st_table *tbl; | |
+ struct sa_table *tbl; | |
st_data_t val; | |
NODE *load; | |
const char *loading; | |
int safe; | |
- if (!st_lookup(RCLASS_IV_TBL(mod), autoload, &val) || | |
- !(tbl = check_autoload_table((VALUE)val)) || !st_lookup(tbl, (st_data_t)id, &val)) { | |
+ if (!sa_lookup(RCLASS_IV_TBL(mod), (sa_index_t)autoload, &val) || | |
+ !(tbl = check_autoload_table((VALUE)val)) || !sa_lookup(tbl, (sa_index_t)id, &val)) { | |
return 0; | |
} | |
load = (NODE *)val; | |
@@ -1541,10 +1499,10 @@ autoload_node(VALUE mod, ID id, const char **loadingpath) | |
static int | |
autoload_node_id(VALUE mod, ID id) | |
{ | |
- struct st_table *tbl = RCLASS_CONST_TBL(mod); | |
+ struct sa_table *tbl = RCLASS_CONST_TBL(mod); | |
st_data_t val; | |
- if (!tbl || !st_lookup(tbl, (st_data_t)id, &val) || ((rb_const_entry_t*)val)->value != Qundef) { | |
+ if (!tbl || !sa_lookup(tbl, (sa_index_t)id, &val) || ((rb_const_entry_t*)val)->value != Qundef) { | |
return 0; | |
} | |
return 1; | |
@@ -1593,7 +1551,7 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) | |
while (RTEST(tmp)) { | |
VALUE am = 0; | |
st_data_t data; | |
- while (RCLASS_CONST_TBL(tmp) && st_lookup(RCLASS_CONST_TBL(tmp), (st_data_t)id, &data)) { | |
+ while (sa_lookup(RCLASS_CONST_TBL(tmp), (sa_index_t)id, &data)) { | |
rb_const_entry_t *ce = (rb_const_entry_t *)data; | |
if (visibility && ce->flag == CONST_PRIVATE) { | |
rb_name_error(id, "private constant %s::%s referenced", rb_class2name(klass), rb_id2name(id)); | |
@@ -1686,12 +1644,12 @@ VALUE | |
rb_const_remove(VALUE mod, ID id) | |
{ | |
VALUE val; | |
- st_data_t v, n = id; | |
+ st_data_t v; | |
if (!OBJ_UNTRUSTED(mod) && rb_safe_level() >= 4) | |
rb_raise(rb_eSecurityError, "Insecure: can't remove constant"); | |
rb_check_frozen(mod); | |
- if (!RCLASS_CONST_TBL(mod) || !st_delete(RCLASS_CONST_TBL(mod), &n, &v)) { | |
+ if (!sa_delete(RCLASS_CONST_TBL(mod), (sa_index_t)id, &v)) { | |
if (rb_const_defined_at(mod, id)) { | |
rb_name_error(id, "cannot remove %s::%s", | |
rb_class2name(mod), rb_id2name(id)); | |
@@ -1711,27 +1669,20 @@ rb_const_remove(VALUE mod, ID id) | |
return val; | |
} | |
-static int | |
-sv_i(ID key, rb_const_entry_t *ce, st_table *tbl) | |
-{ | |
- if (rb_is_const_id(key)) { | |
- if (!st_lookup(tbl, (st_data_t)key, 0)) { | |
- st_insert(tbl, (st_data_t)key, (st_data_t)ce); | |
- } | |
- } | |
- return ST_CONTINUE; | |
-} | |
- | |
void* | |
rb_mod_const_at(VALUE mod, void *data) | |
{ | |
- st_table *tbl = data; | |
+ sa_table *tbl = data; | |
if (!tbl) { | |
- tbl = st_init_numtable(); | |
+ tbl = sa_new_table(); | |
} | |
- if (RCLASS_CONST_TBL(mod)) { | |
- st_foreach_safe(RCLASS_CONST_TBL(mod), sv_i, (st_data_t)tbl); | |
+ SA_FOREACH_START(RCLASS_CONST_TBL(mod)); | |
+ if (rb_is_const_id(entry->key)) { | |
+ if (!sa_lookup(tbl, entry->key, 0)) { | |
+ sa_insert(tbl, entry->key, value); | |
+ } | |
} | |
+ SA_FOREACH_END(); | |
return tbl; | |
} | |
@@ -1748,25 +1699,21 @@ rb_mod_const_of(VALUE mod, void *data) | |
return data; | |
} | |
-static int | |
-list_i(st_data_t key, st_data_t value, VALUE ary) | |
-{ | |
- ID sym = (ID)key; | |
- rb_const_entry_t *ce = (rb_const_entry_t *)value; | |
- if (ce->flag != CONST_PRIVATE) rb_ary_push(ary, ID2SYM(sym)); | |
- return ST_CONTINUE; | |
-} | |
- | |
VALUE | |
rb_const_list(void *data) | |
{ | |
- st_table *tbl = data; | |
+ sa_table *tbl = data; | |
VALUE ary; | |
if (!tbl) return rb_ary_new2(0); | |
ary = rb_ary_new2(tbl->num_entries); | |
- st_foreach_safe(tbl, list_i, ary); | |
- st_free_table(tbl); | |
+ | |
+ SA_FOREACH_START(tbl); | |
+ rb_const_entry_t *ce = (rb_const_entry_t *)value; | |
+ if (ce->flag != CONST_PRIVATE) rb_ary_push(ary, ID2SYM(entry->key)); | |
+ SA_FOREACH_END(); | |
+ | |
+ sa_free_table(tbl); | |
return ary; | |
} | |
@@ -1790,7 +1737,7 @@ VALUE | |
rb_mod_constants(int argc, VALUE *argv, VALUE mod) | |
{ | |
VALUE inherit; | |
- st_table *tbl; | |
+ sa_table *tbl; | |
if (argc == 0) { | |
inherit = Qtrue; | |
@@ -1817,7 +1764,7 @@ rb_const_defined_0(VALUE klass, ID id, int exclude, int recurse, int visibility) | |
tmp = klass; | |
retry: | |
while (tmp) { | |
- if (RCLASS_CONST_TBL(tmp) && st_lookup(RCLASS_CONST_TBL(tmp), (st_data_t)id, &value)) { | |
+ if (sa_lookup(RCLASS_CONST_TBL(tmp), (sa_index_t)id, &value)) { | |
rb_const_entry_t *ce = (rb_const_entry_t *)value; | |
if (visibility && ce->flag == CONST_PRIVATE) { | |
return (int)Qfalse; | |
@@ -1885,6 +1832,7 @@ void | |
rb_const_set(VALUE klass, ID id, VALUE val) | |
{ | |
rb_const_entry_t *ce; | |
+ st_data_t value; | |
VALUE visibility = CONST_PUBLIC; | |
if (NIL_P(klass)) { | |
@@ -1893,21 +1841,14 @@ rb_const_set(VALUE klass, ID id, VALUE val) | |
} | |
check_before_mod_set(klass, id, val, "constant"); | |
- if (!RCLASS_CONST_TBL(klass)) { | |
- RCLASS_CONST_TBL(klass) = st_init_numtable(); | |
- } | |
- else { | |
- st_data_t value; | |
- | |
- if (st_lookup(RCLASS_CONST_TBL(klass), (st_data_t)id, &value)) { | |
- rb_const_entry_t *ce = (rb_const_entry_t*)value; | |
- if (ce->value == Qundef) | |
- autoload_delete(klass, id); | |
- else { | |
- visibility = ce->flag; | |
- rb_warn("already initialized constant %s", rb_id2name(id)); | |
- } | |
- } | |
+ if (sa_lookup(RCLASS_CONST_TBL(klass), (sa_index_t)id, &value)) { | |
+ rb_const_entry_t *ce = (rb_const_entry_t*)value; | |
+ if (ce->value == Qundef) | |
+ autoload_delete(klass, id); | |
+ else { | |
+ visibility = ce->flag; | |
+ rb_warn("already initialized constant %s", rb_id2name(id)); | |
+ } | |
} | |
rb_vm_change_state(); | |
@@ -1916,7 +1857,7 @@ rb_const_set(VALUE klass, ID id, VALUE val) | |
ce->flag = (rb_const_flag_t)visibility; | |
ce->value = val; | |
- st_insert(RCLASS_CONST_TBL(klass), (st_data_t)id, (st_data_t)ce); | |
+ sa_insert(RCLASS_CONST_TBL(klass), (sa_index_t)id, (st_data_t)ce); | |
} | |
void | |
@@ -1958,8 +1899,7 @@ set_const_visibility(VALUE mod, int argc, VALUE *argv, rb_const_flag_t flag) | |
for (i = 0; i < argc; i++) { | |
VALUE val = argv[i]; | |
id = rb_to_id(val); | |
- if (RCLASS_CONST_TBL(mod) && | |
- st_lookup(RCLASS_CONST_TBL(mod), (st_data_t)id, &v)) { | |
+ if (sa_lookup(RCLASS_CONST_TBL(mod), (sa_index_t)id, &v)) { | |
((rb_const_entry_t*)v)->flag = flag; | |
} | |
else { | |
@@ -2008,7 +1948,7 @@ original_module(VALUE c) | |
} | |
#define CVAR_LOOKUP(v,r) do {\ | |
- if (RCLASS_IV_TBL(klass) && st_lookup(RCLASS_IV_TBL(klass),(st_data_t)id,(v))) {\ | |
+ if (sa_lookup(RCLASS_IV_TBL(klass),(sa_index_t)id,(v))) {\ | |
r;\ | |
}\ | |
if (FL_TEST(klass, FL_SINGLETON) ) {\ | |
@@ -2027,7 +1967,7 @@ original_module(VALUE c) | |
klass = RCLASS_SUPER(klass);\ | |
}\ | |
while (klass) {\ | |
- if (RCLASS_IV_TBL(klass) && st_lookup(RCLASS_IV_TBL(klass),(st_data_t)id,(v))) {\ | |
+ if (sa_lookup(RCLASS_IV_TBL(klass),(sa_index_t)id,(v))) {\ | |
r;\ | |
}\ | |
klass = RCLASS_SUPER(klass);\ | |
@@ -2043,15 +1983,13 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) | |
CVAR_LOOKUP(0, {if (!front) front = klass; target = klass;}); | |
if (target) { | |
if (front && target != front) { | |
- st_data_t did = id; | |
- | |
if (RTEST(ruby_verbose)) { | |
rb_warning("class variable %s of %s is overtaken by %s", | |
rb_id2name(id), rb_class2name(original_module(front)), | |
rb_class2name(original_module(target))); | |
} | |
if (BUILTIN_TYPE(front) == T_CLASS) { | |
- st_delete(RCLASS_IV_TBL(front),&did,0); | |
+ sa_delete(RCLASS_IV_TBL(front),(sa_index_t)id,0); | |
} | |
} | |
} | |
@@ -2060,11 +1998,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) | |
} | |
check_before_mod_set(target, id, val, "class variable"); | |
- if (!RCLASS_IV_TBL(target)) { | |
- RCLASS_IV_TBL(target) = st_init_numtable(); | |
- } | |
- | |
- st_insert(RCLASS_IV_TBL(target), (st_data_t)id, (st_data_t)val); | |
+ sa_insert(RCLASS_IV_TBL(target), (sa_index_t)id, (st_data_t)val); | |
} | |
VALUE | |
@@ -2080,15 +2014,13 @@ rb_cvar_get(VALUE klass, ID id) | |
rb_id2name(id), rb_class2name(tmp)); | |
} | |
if (front && target != front) { | |
- st_data_t did = id; | |
- | |
if (RTEST(ruby_verbose)) { | |
rb_warning("class variable %s of %s is overtaken by %s", | |
rb_id2name(id), rb_class2name(original_module(front)), | |
rb_class2name(original_module(target))); | |
} | |
if (BUILTIN_TYPE(front) == T_CLASS) { | |
- st_delete(RCLASS_IV_TBL(front),&did,0); | |
+ sa_delete(RCLASS_IV_TBL(front),(sa_index_t)id,0); | |
} | |
} | |
return (VALUE)value; | |
@@ -2134,7 +2066,7 @@ rb_define_class_variable(VALUE klass, const char *name, VALUE val) | |
} | |
static int | |
-cv_i(ID key, VALUE value, VALUE ary) | |
+cv_i(sa_index_t key, VALUE value, VALUE ary) | |
{ | |
if (rb_is_class_id(key)) { | |
VALUE kval = ID2SYM(key); | |
@@ -2167,7 +2099,7 @@ rb_mod_class_variables(VALUE obj) | |
VALUE ary = rb_ary_new(); | |
if (RCLASS_IV_TBL(obj)) { | |
- st_foreach_safe(RCLASS_IV_TBL(obj), cv_i, ary); | |
+ sa_foreach(RCLASS_IV_TBL(obj), cv_i, ary); | |
} | |
return ary; | |
} | |
@@ -2196,7 +2128,7 @@ VALUE | |
rb_mod_remove_cvar(VALUE mod, VALUE name) | |
{ | |
const ID id = rb_to_id(name); | |
- st_data_t val, n = id; | |
+ st_data_t val; | |
if (!rb_is_class_id(id)) { | |
rb_name_error(id, "wrong class variable name %s", rb_id2name(id)); | |
@@ -2204,7 +2136,7 @@ rb_mod_remove_cvar(VALUE mod, VALUE name) | |
if (!OBJ_UNTRUSTED(mod) && rb_safe_level() >= 4) | |
rb_raise(rb_eSecurityError, "Insecure: can't remove class variable"); | |
rb_check_frozen(mod); | |
- if (RCLASS_IV_TBL(mod) && st_delete(RCLASS_IV_TBL(mod), &n, &val)) { | |
+ if (RCLASS_IV_TBL(mod) && sa_delete(RCLASS_IV_TBL(mod), (sa_index_t)id, &val)) { | |
return (VALUE)val; | |
} | |
if (rb_cvar_defined(mod, id)) { | |
diff --git a/vm.c b/vm.c | |
index adc60d8..d5c9279 100644 | |
--- a/vm.c | |
+++ b/vm.c | |
@@ -1055,7 +1055,7 @@ static void | |
add_opt_method(VALUE klass, ID mid, VALUE bop) | |
{ | |
rb_method_entry_t *me; | |
- if (st_lookup(RCLASS_M_TBL(klass), mid, (void *)&me) && me->def && | |
+ if (sa_lookup(RCLASS_M_TBL(klass), (sa_index_t)mid, (void *)&me) && me->def && | |
me->def->type == VM_METHOD_TYPE_CFUNC) { | |
st_insert(vm_opt_method_table, (st_data_t)me, (st_data_t)bop); | |
} | |
diff --git a/vm_insnhelper.c b/vm_insnhelper.c | |
index 5f7d1ee..ade417e 100644 | |
--- a/vm_insnhelper.c | |
+++ b/vm_insnhelper.c | |
@@ -1179,7 +1179,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, | |
st_data_t data; | |
search_continue: | |
if (RCLASS_CONST_TBL(klass) && | |
- st_lookup(RCLASS_CONST_TBL(klass), id, &data)) { | |
+ sa_lookup(RCLASS_CONST_TBL(klass), (sa_index_t)id, &data)) { | |
val = ((rb_const_entry_t*)data)->value; | |
if (val == Qundef) { | |
if (am == klass) break; | |
@@ -1289,10 +1289,10 @@ vm_getivar(VALUE obj, ID id, IC ic) | |
st_data_t index; | |
long len = ROBJECT_NUMIV(obj); | |
VALUE *ptr = ROBJECT_IVPTR(obj); | |
- struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); | |
+ struct sa_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); | |
if (iv_index_tbl) { | |
- if (st_lookup(iv_index_tbl, id, &index)) { | |
+ if (sa_lookup(iv_index_tbl, (sa_index_t)id, &index)) { | |
if ((long)index < len) { | |
val = ptr[index]; | |
} | |
@@ -1342,9 +1342,9 @@ vm_setivar(VALUE obj, ID id, VALUE val, IC ic) | |
} | |
} | |
else { | |
- struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); | |
+ struct sa_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); | |
- if (iv_index_tbl && st_lookup(iv_index_tbl, (st_data_t)id, &index)) { | |
+ if (iv_index_tbl && sa_lookup(iv_index_tbl, (sa_index_t)id, &index)) { | |
ic->ic_class = klass; | |
ic->ic_value.index = index; | |
ic->ic_vmstat = GET_VM_STATE_VERSION(); | |
diff --git a/vm_method.c b/vm_method.c | |
index 85cb30c..d45df81 100644 | |
--- a/vm_method.c | |
+++ b/vm_method.c | |
@@ -162,7 +162,7 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, | |
rb_method_definition_t *def, rb_method_flag_t noex) | |
{ | |
rb_method_entry_t *me; | |
- st_table *mtbl; | |
+ sa_table *mtbl; | |
st_data_t data; | |
if (NIL_P(klass)) { | |
@@ -190,7 +190,7 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, | |
mtbl = RCLASS_M_TBL(klass); | |
/* check re-definition */ | |
- if (st_lookup(mtbl, mid, &data)) { | |
+ if (sa_lookup(mtbl, (sa_index_t)mid, &data)) { | |
rb_method_entry_t *old_me = (rb_method_entry_t *)data; | |
rb_method_definition_t *old_def = old_me->def; | |
@@ -248,7 +248,7 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, | |
} | |
} | |
- st_insert(mtbl, mid, (st_data_t) me); | |
+ sa_insert(mtbl, (sa_index_t)mid, (st_data_t) me); | |
return me; | |
} | |
@@ -371,7 +371,7 @@ search_method(VALUE klass, ID id) | |
return 0; | |
} | |
- while (!st_lookup(RCLASS_M_TBL(klass), id, &body)) { | |
+ while (!sa_lookup(RCLASS_M_TBL(klass), (sa_index_t)id, &body)) { | |
klass = RCLASS_SUPER(klass); | |
if (!klass) { | |
return 0; | |
@@ -429,7 +429,7 @@ rb_method_entry(VALUE klass, ID id) | |
static void | |
remove_method(VALUE klass, ID mid) | |
{ | |
- st_data_t key, data; | |
+ st_data_t data; | |
rb_method_entry_t *me = 0; | |
if (klass == rb_cObject) { | |
@@ -443,14 +443,13 @@ remove_method(VALUE klass, ID mid) | |
rb_warn("removing `%s' may cause serious problems", rb_id2name(mid)); | |
} | |
- if (!st_lookup(RCLASS_M_TBL(klass), mid, &data) || | |
+ if (!sa_lookup(RCLASS_M_TBL(klass), (sa_index_t)mid, &data) || | |
!(me = (rb_method_entry_t *)data) || | |
(!me->def || me->def->type == VM_METHOD_TYPE_UNDEF)) { | |
rb_name_error(mid, "method `%s' not defined in %s", | |
rb_id2name(mid), rb_class2name(klass)); | |
} | |
- key = (st_data_t)mid; | |
- st_delete(RCLASS_M_TBL(klass), &key, &data); | |
+ sa_delete(RCLASS_M_TBL(klass), (sa_index_t)mid, &data); | |
rb_vm_check_redefinition_opt_method(me); | |
rb_clear_cache_for_undef(klass, mid); | |
diff --git a/array.c b/array.c | |
index 78c003a..4637ae5 100644 | |
--- a/array.c | |
+++ b/array.c | |
@@ -255,15 +255,24 @@ rb_ary_modify(VALUE ary) | |
rb_ary_modify_check(ary); | |
if (ARY_SHARED_P(ary)) { | |
long len = RARRAY_LEN(ary); | |
+ VALUE shared = ARY_SHARED(ary); | |
if (len <= RARRAY_EMBED_LEN_MAX) { | |
VALUE *ptr = ARY_HEAP_PTR(ary); | |
- VALUE shared = ARY_SHARED(ary); | |
FL_UNSET_SHARED(ary); | |
FL_SET_EMBED(ary); | |
MEMCPY(ARY_EMBED_PTR(ary), ptr, VALUE, len); | |
rb_ary_decrement_share(shared); | |
ARY_SET_EMBED_LEN(ary, len); | |
} | |
+ else if (ARY_SHARED_NUM(shared) == 1 && len > RARRAY_LEN(shared)>>1) { | |
+ long shift = RARRAY_PTR(ary) - RARRAY_PTR(shared); | |
+ ARY_SET_PTR(ary, RARRAY_PTR(shared)); | |
+ ARY_SET_CAPA(ary, RARRAY_LEN(shared)); | |
+ MEMMOVE(RARRAY_PTR(ary), RARRAY_PTR(ary)+shift, VALUE, len); | |
+ FL_UNSET_SHARED(ary); | |
+ FL_SET_EMBED(shared); | |
+ rb_ary_decrement_share(shared); | |
+ } | |
else { | |
VALUE *ptr = ALLOC_N(VALUE, len); | |
MEMCPY(ptr, RARRAY_PTR(ary), VALUE, len); | |
@@ -274,6 +283,38 @@ rb_ary_modify(VALUE ary) | |
} | |
} | |
+static void | |
+ary_ensure_room_for_push(VALUE ary, long add_len) | |
+{ | |
+ long new_len = RARRAY_LEN(ary) + add_len; | |
+ long capa; | |
+ | |
+ if (ARY_SHARED_P(ary)) { | |
+ if (new_len > RARRAY_EMBED_LEN_MAX) { | |
+ VALUE shared = ARY_SHARED(ary); | |
+ if (ARY_SHARED_NUM(shared) == 1) { | |
+ if (RARRAY_PTR(ary) - RARRAY_PTR(shared) + new_len <= RARRAY_LEN(shared)) { | |
+ rb_ary_modify_check(ary); | |
+ } | |
+ else { | |
+ /* if array is shared, than it is likely it participate in push/shift pattern */ | |
+ rb_ary_modify(ary); | |
+ capa = ARY_CAPA(ary); | |
+ if (new_len > capa - (capa >> 6)) { | |
+ ary_double_capa(ary, new_len); | |
+ } | |
+ } | |
+ return; | |
+ } | |
+ } | |
+ } | |
+ rb_ary_modify(ary); | |
+ capa = ARY_CAPA(ary); | |
+ if (new_len > capa) { | |
+ ary_double_capa(ary, new_len); | |
+ } | |
+} | |
+ | |
VALUE | |
rb_ary_freeze(VALUE ary) | |
{ | |
@@ -446,8 +487,9 @@ ary_make_shared(VALUE ary) | |
OBJSETUP(shared, 0, T_ARRAY); | |
FL_UNSET_EMBED(shared); | |
- ARY_SET_LEN((VALUE)shared, RARRAY_LEN(ary)); | |
+ ARY_SET_LEN((VALUE)shared, ARY_CAPA(ary)); | |
ARY_SET_PTR((VALUE)shared, RARRAY_PTR(ary)); | |
+ rb_mem_clear(RARRAY_PTR(shared) + RARRAY_LEN(ary), ARY_CAPA(ary) - RARRAY_LEN(ary)); | |
FL_SET_SHARED_ROOT(shared); | |
ARY_SET_SHARED_NUM((VALUE)shared, 1); | |
FL_SET_SHARED(ary); | |
@@ -737,8 +779,6 @@ ary_take_first_or_last(int argc, VALUE *argv, VALUE ary, enum ary_take_pos_flags | |
return ary_make_partial(ary, rb_cArray, offset, n); | |
} | |
-static VALUE rb_ary_push_1(VALUE ary, VALUE item); | |
- | |
/* | |
* call-seq: | |
* ary << obj -> ary | |
@@ -755,8 +795,12 @@ static VALUE rb_ary_push_1(VALUE ary, VALUE item); | |
VALUE | |
rb_ary_push(VALUE ary, VALUE item) | |
{ | |
- rb_ary_modify(ary); | |
- return rb_ary_push_1(ary, item); | |
+ long idx = RARRAY_LEN(ary); | |
+ | |
+ ary_ensure_room_for_push(ary, 1); | |
+ RARRAY_PTR(ary)[idx] = item; | |
+ ARY_SET_LEN(ary, idx + 1); | |
+ return ary; | |
} | |
static VALUE | |
@@ -772,6 +816,18 @@ rb_ary_push_1(VALUE ary, VALUE item) | |
return ary; | |
} | |
+static VALUE | |
+rb_ary_cat(VALUE ary, const VALUE *ptr, long len) | |
+{ | |
+ long oldlen = RARRAY_LEN(ary); | |
+ | |
+ ary_ensure_room_for_push(ary, len); | |
+copy: | |
+ MEMCPY(RARRAY_PTR(ary) + oldlen, ptr, VALUE, len); | |
+ ARY_SET_LEN(ary, oldlen + len); | |
+ return ary; | |
+} | |
+ | |
/* | |
* call-seq: | |
* ary.push(obj, ... ) -> ary | |
@@ -788,11 +844,7 @@ rb_ary_push_1(VALUE ary, VALUE item) | |
static VALUE | |
rb_ary_push_m(int argc, VALUE *argv, VALUE ary) | |
{ | |
- rb_ary_modify(ary); | |
- while (argc--) { | |
- rb_ary_push_1(ary, *argv++); | |
- } | |
- return ary; | |
+ return rb_ary_cat(ary, argv, argc); | |
} | |
VALUE | |
@@ -920,6 +972,55 @@ rb_ary_shift_m(int argc, VALUE *argv, VALUE ary) | |
return result; | |
} | |
+static void | |
+ary_ensure_room_for_unshift(VALUE ary, int argc) | |
+{ | |
+ long len = RARRAY_LEN(ary); | |
+ long new_len = len + argc; | |
+ long capa; | |
+ VALUE *head, *sharedp; | |
+ | |
+ if (ARY_SHARED_P(ary)) { | |
+ VALUE shared = ARY_SHARED(ary); | |
+ capa = RARRAY_LEN(shared); | |
+ if (ARY_SHARED_NUM(shared) == 1 && capa > new_len) { | |
+ head = RARRAY_PTR(ary); | |
+ sharedp = RARRAY_PTR(shared); | |
+ goto makeroom_if_need; | |
+ } | |
+ } | |
+ | |
+ rb_ary_modify(ary); | |
+ capa = ARY_CAPA(ary); | |
+ if (capa - (capa >> 6) <= new_len) { | |
+ ary_double_capa(ary, new_len); | |
+ } | |
+ | |
+ /* use shared array for big "queues" */ | |
+ if (new_len > ARY_DEFAULT_SIZE * 4) { | |
+ /* make a room for unshifted items */ | |
+ capa = ARY_CAPA(ary); | |
+ ary_make_shared(ary); | |
+ | |
+ head = sharedp = RARRAY_PTR(ary); | |
+ goto makeroom; | |
+makeroom_if_need: | |
+ if (head - sharedp < argc) { | |
+ long room; | |
+makeroom: | |
+ room = capa - new_len; | |
+ room -= room >> 4; | |
+ MEMMOVE(sharedp + argc + room, head, VALUE, len); | |
+ head = sharedp + argc + room; | |
+ } | |
+ ARY_SET_PTR(ary, head - argc); | |
+ } | |
+ else { | |
+ /* sliding items */ | |
+ MEMMOVE(RARRAY_PTR(ary) + argc, RARRAY_PTR(ary), VALUE, len); | |
+ } | |
+} | |
+ | |
/* | |
* call-seq: | |
* ary.unshift(obj, ...) -> ary | |
@@ -935,19 +1036,16 @@ rb_ary_shift_m(int argc, VALUE *argv, VALUE ary) | |
static VALUE | |
rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary) | |
{ | |
- long len; | |
+ long len = RARRAY_LEN(ary); | |
- rb_ary_modify(ary); | |
- if (argc == 0) return ary; | |
- if (ARY_CAPA(ary) <= (len = RARRAY_LEN(ary)) + argc) { | |
- ary_double_capa(ary, len + argc); | |
+ if (argc == 0) { | |
+ rb_ary_modify_check(ary); | |
+ return ary; | |
} | |
- /* sliding items */ | |
- MEMMOVE(RARRAY_PTR(ary) + argc, RARRAY_PTR(ary), VALUE, len); | |
+ ary_ensure_room_for_unshift(ary, argc); | |
MEMCPY(RARRAY_PTR(ary), argv, VALUE, argc); | |
- ARY_INCREASE_LEN(ary, argc); | |
- | |
+ ARY_SET_LEN(ary, len + argc); | |
return ary; | |
} | |
@@ -1309,15 +1407,12 @@ rb_ary_splice(VALUE ary, long beg, long len, VALUE rpl) | |
rpl = rb_ary_to_ary(rpl); | |
rlen = RARRAY_LEN(rpl); | |
} | |
- rb_ary_modify(ary); | |
if (beg >= RARRAY_LEN(ary)) { | |
if (beg > ARY_MAX_SIZE - rlen) { | |
rb_raise(rb_eIndexError, "index %ld too big", beg); | |
} | |
+ ary_ensure_room_for_push(ary, rlen-len); /* len is 0 or negative */ | |
len = beg + rlen; | |
- if (len >= ARY_CAPA(ary)) { | |
- ary_double_capa(ary, len); | |
- } | |
rb_mem_clear(RARRAY_PTR(ary) + RARRAY_LEN(ary), beg - RARRAY_LEN(ary)); | |
if (rlen > 0) { | |
MEMCPY(RARRAY_PTR(ary) + beg, RARRAY_PTR(rpl), VALUE, rlen); | |
@@ -1327,6 +1422,7 @@ rb_ary_splice(VALUE ary, long beg, long len, VALUE rpl) | |
else { | |
long alen; | |
+ rb_ary_modify(ary); | |
alen = RARRAY_LEN(ary) + rlen - len; | |
if (alen >= ARY_CAPA(ary)) { | |
ary_double_capa(ary, alen); | |
@@ -2116,12 +2212,13 @@ rb_ary_sort_bang(VALUE ary) | |
if (RARRAY_LEN(ary) > 1) { | |
VALUE tmp = ary_make_substitution(ary); /* only ary refers tmp */ | |
struct ary_sort_data data; | |
+ long len = RARRAY_LEN(ary); | |
RBASIC(tmp)->klass = 0; | |
data.ary = tmp; | |
data.opt_methods = 0; | |
data.opt_inited = 0; | |
- ruby_qsort(RARRAY_PTR(tmp), RARRAY_LEN(tmp), sizeof(VALUE), | |
+ ruby_qsort(RARRAY_PTR(tmp), len, sizeof(VALUE), | |
rb_block_given_p()?sort_1:sort_2, &data); | |
if (ARY_EMBED_P(tmp)) { | |
@@ -2138,7 +2235,7 @@ rb_ary_sort_bang(VALUE ary) | |
if (ARY_HEAP_PTR(ary) == ARY_HEAP_PTR(tmp)) { | |
assert(!ARY_EMBED_P(ary)); | |
FL_UNSET_SHARED(ary); | |
- ARY_SET_CAPA(ary, ARY_CAPA(tmp)); | |
+ ARY_SET_CAPA(ary, RARRAY_LEN(tmp)); | |
} | |
else { | |
assert(!ARY_SHARED_P(tmp)); | |
@@ -2153,8 +2250,8 @@ rb_ary_sort_bang(VALUE ary) | |
xfree(ARY_HEAP_PTR(ary)); | |
} | |
ARY_SET_PTR(ary, RARRAY_PTR(tmp)); | |
- ARY_SET_HEAP_LEN(ary, RARRAY_LEN(tmp)); | |
- ARY_SET_CAPA(ary, ARY_CAPA(tmp)); | |
+ ARY_SET_HEAP_LEN(ary, len); | |
+ ARY_SET_CAPA(ary, RARRAY_LEN(tmp)); | |
} | |
/* tmp was lost ownership for the ptr */ | |
FL_UNSET(tmp, FL_FREEZE); | |
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb | |
index 4350e05..70540ca 100644 | |
--- a/test/ruby/test_array.rb | |
+++ b/test/ruby/test_array.rb | |
@@ -414,6 +414,18 @@ class TestArray < Test::Unit::TestCase | |
a = @cls[1, 2, 3] | |
a[-1, 0] = a | |
assert_equal([1, 2, 1, 2, 3, 3], a) | |
+ | |
+ a = @cls[] | |
+ a[5,0] = [5] | |
+ assert_equal([nil, nil, nil, nil, nil, 5], a) | |
+ | |
+ a = @cls[1] | |
+ a[1,0] = [2] | |
+ assert_equal([1, 2], a) | |
+ | |
+ a = @cls[1] | |
+ a[1,1] = [2] | |
+ assert_equal([1, 2], a) | |
end | |
def test_assoc | |
diff --git a/gc.c b/gc.c | |
index dc7ac27..8c3d043 100644 | |
--- a/gc.c | |
+++ b/gc.c | |
@@ -108,11 +108,6 @@ ruby_gc_params_t initial_params = { | |
#define LONG_LONG long | |
#endif | |
-static int heap_min_slots = 10000; | |
-static int heap_slots_increment = 10000; | |
-static int initial_heap_slots_increment = 10000; | |
-static double heap_slots_growth_factor = 1.8; | |
- | |
#define nomem_error GET_VM()->special_exceptions[ruby_error_nomemory] | |
#if SIZEOF_LONG == SIZEOF_VOIDP | |
@@ -321,6 +316,28 @@ typedef struct RVALUE { | |
#endif | |
} RVALUE; | |
+ | |
+/* tiny heap size */ | |
+/* 32KB */ | |
+/*#define HEAP_SIZE 0x8000 */ | |
+/* 128KB */ | |
+/*#define HEAP_SIZE 0x20000 */ | |
+/* 64KB */ | |
+/*#define HEAP_SIZE 0x10000 */ | |
+/* 16KB */ | |
+#define HEAP_SIZE 0x4000 | |
+/* 8KB */ | |
+/*#define HEAP_SIZE 0x2000 */ | |
+/* 4KB */ | |
+/*#define HEAP_SIZE 0x1000 */ | |
+/* 2KB */ | |
+/*#define HEAP_SIZE 0x800 */ | |
+ | |
+#define HEAP_OBJ_LIMIT (unsigned int)(HEAP_SIZE / sizeof(struct RVALUE)) | |
+ | |
+static int heap_slots_increment = 10000 / HEAP_OBJ_LIMIT; | |
+static double heap_slots_growth_factor = 1.8; | |
+ | |
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) | |
#pragma pack(pop) | |
#endif | |
@@ -537,17 +554,6 @@ rb_gc_set_params(void) | |
if (rb_safe_level() > 0) return; | |
- envp = getenv("RUBY_GC_STATS"); | |
- if (envp != NULL) { | |
- int i = atoi(envp); | |
- if (i > 0) { | |
- verbose_gc_stats = 1; | |
- fprintf(stderr, "RUBY_GC_STATS=%d\n", verbose_gc_stats); | |
- } | |
- /* child processes should not inherit RUBY_GC_STATS */ | |
- ruby_unsetenv("RUBY_GC_STATS"); | |
- } | |
- | |
envp = getenv("RUBY_GC_DATA_FILE"); | |
if (envp != NULL) { | |
FILE* data_file = fopen(envp, "w"); | |
@@ -561,6 +567,18 @@ rb_gc_set_params(void) | |
ruby_unsetenv("RUBY_GC_DATA_FILE"); | |
} | |
+ envp = getenv("RUBY_GC_STATS"); | |
+ if (envp != NULL) { | |
+ int i = atoi(envp); | |
+ if (i > 0) { | |
+ /* gc_statistics = 1; */ | |
+ verbose_gc_stats = 1; | |
+ fprintf(gc_data_file, "RUBY_GC_STATS=%d\n", verbose_gc_stats); | |
+ } | |
+ /* child processes should not inherit RUBY_GC_STATS */ | |
+ ruby_unsetenv("RUBY_GC_STATS"); | |
+ } | |
+ | |
envp = getenv("RUBY_GC_MALLOC_LIMIT"); | |
if (envp != NULL) { | |
int malloc_limit_i = atoi(envp); | |
@@ -572,7 +590,7 @@ rb_gc_set_params(void) | |
malloc_limit_i, initial_malloc_limit); | |
if (malloc_limit_i > 0) { | |
initial_malloc_limit = malloc_limit_i; | |
- // malloc_limit = initial_malloc_limit; | |
+ malloc_limit = initial_malloc_limit; | |
} | |
} | |
@@ -611,8 +629,7 @@ rb_gc_set_params(void) | |
if (verbose_gc_stats) { | |
fprintf(gc_data_file, "RUBY_HEAP_SLOTS_INCREMENT=%s\n", envp); | |
} | |
- heap_slots_increment = i; | |
- initial_heap_slots_increment = heap_slots_increment; | |
+ heap_slots_increment = i / HEAP_OBJ_LIMIT; | |
} | |
envp = getenv("RUBY_HEAP_SLOTS_GROWTH_FACTOR"); | |
@@ -672,24 +689,6 @@ rb_objspace_free(rb_objspace_t *objspace) | |
} | |
#endif | |
-/* tiny heap size */ | |
-/* 32KB */ | |
-/*#define HEAP_SIZE 0x8000 */ | |
-/* 128KB */ | |
-/*#define HEAP_SIZE 0x20000 */ | |
-/* 64KB */ | |
-/*#define HEAP_SIZE 0x10000 */ | |
-/* 16KB */ | |
-#define HEAP_SIZE 0x4000 | |
-/* 8KB */ | |
-/*#define HEAP_SIZE 0x2000 */ | |
-/* 4KB */ | |
-/*#define HEAP_SIZE 0x1000 */ | |
-/* 2KB */ | |
-/*#define HEAP_SIZE 0x800 */ | |
- | |
-#define HEAP_OBJ_LIMIT (unsigned int)(HEAP_SIZE / sizeof(struct RVALUE)) | |
- | |
extern sa_table rb_class_tbl; | |
int ruby_disable_gc_stress = 0; | |
@@ -1522,7 +1521,6 @@ rb_gc_dump_file_and_line_info(int argc, VALUE *argv) | |
rb_objspace_t *objspace = &rb_objspace; | |
VALUE filename, str, include_classnames = Qnil; | |
char *fname = NULL; | |
- char *klass = NULL; | |
FILE* f = NULL; | |
size_t i = 0; | |
@@ -1780,17 +1778,15 @@ aligned_free(void *ptr) | |
static void | |
assign_heap_slot(rb_objspace_t *objspace) | |
{ | |
- /* | |
- if (gc_statistics & verbose_gc_stats) { | |
- fprintf(gc_data_file, "assigning heap slot\n"); | |
- } | |
- */ | |
- | |
RVALUE *p, *pend, *membase; | |
struct heaps_slot *slot; | |
size_t hi, lo, mid; | |
size_t objs; | |
- | |
+ /* | |
+ if (gc_statistics & verbose_gc_stats) { | |
+ fprintf(gc_data_file, "assigning heap slot: %d\n", heaps_inc); | |
+ } | |
+ */ | |
objs = HEAP_OBJ_LIMIT; | |
p = (RVALUE*)malloc(HEAP_SIZE); | |
if (p == 0) { | |
@@ -1905,13 +1901,17 @@ static void | |
set_heaps_increment(rb_objspace_t *objspace) | |
{ | |
size_t next_heaps_length = (size_t)(heaps_used * heap_slots_growth_factor); | |
+ size_t next_heaps_length_alt = heaps_used + heap_slots_increment; | |
- if (next_heaps_length == heaps_used) { | |
- next_heaps_length++; | |
+ if (next_heaps_length < next_heaps_length_alt) { | |
+ next_heaps_length = next_heaps_length_alt; | |
} | |
heaps_inc = next_heaps_length - heaps_used; | |
- | |
+ /* | |
+ if (gc_statistics & verbose_gc_stats) | |
+ fprintf(gc_data_file, "heaps_inc:%lu, slots_inc: %lu\n", heaps_inc, heaps_inc * HEAP_OBJ_LIMIT); | |
+ */ | |
if (next_heaps_length > heaps_length) { | |
allocate_sorted_heaps(objspace, next_heaps_length); | |
} | |
@@ -2529,7 +2529,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr) | |
#ifdef GC_DEBUG | |
if (obj->file && obj->file != Qnil && is_pointer_to_heap(objspace, (void*)obj->file)) { | |
- gc_mark(objspace, obj->file, lev); | |
+ gc_mark(objspace, obj->file); | |
} | |
#endif | |
@@ -2545,7 +2545,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr) | |
#ifdef GC_DEBUG | |
if (obj->file && obj->file != Qnil && is_pointer_to_heap(objspace, (void*)obj->file)) { | |
- gc_mark(objspace, obj->file, lev); | |
+ gc_mark(objspace, obj->file); | |
} | |
#endif | |
@@ -3030,14 +3030,14 @@ before_gc_sweep(rb_objspace_t *objspace) | |
MEMZERO((void*)live_counts, unsigned long, T_MASK+1); | |
} | |
- objspace->heap.max_blocks_to_free = heaps_used - (heap_min_slots / HEAP_OBJ_LIMIT); | |
+ objspace->heap.max_blocks_to_free = heaps_used - (initial_heap_min_slots / HEAP_OBJ_LIMIT); | |
objspace->heap.freed_blocks = 0; | |
freelist = 0; | |
objspace->heap.do_heap_free = (size_t)((heaps_used * HEAP_OBJ_LIMIT) * 0.65); | |
objspace->heap.free_min = (size_t)((heaps_used * HEAP_OBJ_LIMIT) * 0.2); | |
if (objspace->heap.free_min < initial_free_min) { | |
- objspace->heap.do_heap_free = heaps_used * HEAP_OBJ_LIMIT; | |
+ /* objspace->heap.do_heap_free = heaps_used * HEAP_OBJ_LIMIT; */ | |
objspace->heap.free_min = initial_free_min; | |
} | |
objspace->heap.sweep_slots = heaps; | |
@@ -3069,7 +3069,11 @@ after_gc_sweep(rb_objspace_t *objspace) | |
if (malloc_limit < initial_malloc_limit) malloc_limit = initial_malloc_limit; | |
} | |
malloc_increase = 0; | |
- | |
+ /* | |
+ if (verbose_gc_stats) | |
+ fprintf(gc_data_file, "heap size before freeing unused heaps: %7lu\n", | |
+ (unsigned long)heaps_used*HEAP_OBJ_LIMIT); | |
+ */ | |
free_unused_heaps(objspace); | |
if (gc_statistics) { | |
@@ -3078,6 +3082,7 @@ after_gc_sweep(rb_objspace_t *objspace) | |
if (verbose_gc_stats) { | |
/* log gc stats if requested */ | |
fprintf(gc_data_file, "GC time: %lu musec\n", (unsigned long)(gc_time_accumulator-gc_time_accumulator_before_gc)); | |
+ fprintf(gc_data_file, "heap size : %7lu\n", (unsigned long)heaps_used*HEAP_OBJ_LIMIT); | |
fprintf(gc_data_file, "objects processed: %7lu\n", (unsigned long)processed); | |
fprintf(gc_data_file, "live objects : %7lu\n", (unsigned long)live_after_last_mark_phase); | |
fprintf(gc_data_file, "freelist objects : %7lu\n", (unsigned long)freelist_size); | |
@@ -3127,7 +3132,7 @@ rest_sweep(rb_objspace_t *objspace) | |
static void gc_marks(rb_objspace_t *objspace); | |
-/* only called from rb_new_obj */ | |
+/* only called from rb_newobj */ | |
static int | |
gc_lazy_sweep(rb_objspace_t *objspace) | |
{ | |
@@ -3414,13 +3419,11 @@ gc_marks(rb_objspace_t *objspace) | |
struct gc_list *list; | |
rb_thread_t *th = GET_THREAD(); | |
GC_PROF_MARK_TIMER_START; | |
- | |
/* | |
if (gc_statistics & verbose_gc_stats) { | |
fprintf(gc_data_file, "Marking objects\n"); | |
} | |
*/ | |
- | |
objspace->heap.live_num = 0; | |
objspace->count++; | |
live_objects = 0; | |
diff --git a/eval.c b/eval.c | |
index 92b02f7..708f899 100644 | |
--- a/eval.c | |
+++ b/eval.c | |
@@ -380,8 +380,7 @@ setup_exception(rb_thread_t *th, int tag, volatile VALUE mesg) | |
if (file) line = rb_sourceline(); | |
if (file && !NIL_P(mesg)) { | |
if (mesg == sysstack_error) { | |
- at = rb_enc_sprintf(rb_usascii_encoding(), "%s:%d", file, line); | |
- at = rb_ary_new3(1, at); | |
+ at = rb_make_backtrace(); | |
rb_iv_set(mesg, "bt", at); | |
} | |
else { | |
diff --git a/cont.c b/cont.c | |
index 1e42974..c656ac3 100644 | |
--- a/cont.c | |
+++ b/cont.c | |
@@ -48,6 +48,7 @@ | |
#define RB_PAGE_MASK (~(RB_PAGE_SIZE - 1)) | |
static long pagesize; | |
#define FIBER_MACHINE_STACK_ALLOCATION_SIZE (0x10000) | |
+static int fiber_machine_stack_allocation_size = FIBER_MACHINE_STACK_ALLOCATION_SIZE; | |
#endif | |
#define CAPTURE_JUST_VALID_VM_STACK 1 | |
@@ -620,7 +621,7 @@ fiber_setcontext(rb_fiber_t *newfib, rb_fiber_t *oldfib) | |
rb_thread_t *th = GET_THREAD(), *sth = &newfib->cont.saved_thread; | |
if (newfib->status != RUNNING) { | |
- fiber_initialize_machine_stack_context(newfib, FIBER_MACHINE_STACK_ALLOCATION_SIZE); | |
+ fiber_initialize_machine_stack_context(newfib, fiber_machine_stack_allocation_size); | |
} | |
/* restore thread context */ | |
@@ -1000,6 +1001,39 @@ rb_cont_call(int argc, VALUE *argv, VALUE contval) | |
*/ | |
#define FIBER_VM_STACK_SIZE (4 * 1024) | |
+static int fiber_vm_stack_size = FIBER_VM_STACK_SIZE; | |
+ | |
+void | |
+rb_fiber_set_stack_params(void) | |
+{ | |
+ char *envp; | |
+ | |
+ if (rb_safe_level() > 0) return; | |
+ | |
+#if FIBER_USE_NATIVE | |
+ envp = getenv("RUBY_FIBER_MACHINE_STACK_SIZE"); | |
+ if (envp != NULL) { | |
+ int machine_stack_allocation_size = atoi(envp); | |
+ if (machine_stack_allocation_size > 0) { | |
+ fiber_machine_stack_allocation_size = 1024*machine_stack_allocation_size; | |
+ } | |
+ if (RTEST(ruby_verbose)) | |
+ fprintf(stderr, "fiber_machine_stack_size=%dKB (%dKB)\n", | |
+ fiber_machine_stack_allocation_size/1024, FIBER_MACHINE_STACK_ALLOCATION_SIZE/1024); | |
+ } | |
+#endif | |
+ | |
+ envp = getenv("RUBY_FIBER_VM_STACK_SIZE"); | |
+ if (envp != NULL) { | |
+ int vm_stack_size = atoi(envp); | |
+ if (vm_stack_size > 0) { | |
+ fiber_vm_stack_size = 1024*vm_stack_size; | |
+ } | |
+ if (RTEST(ruby_verbose)) | |
+ fprintf(stderr, "fiber_vm_stack_size=%dKB (%dKB)\n", | |
+ fiber_vm_stack_size/1024, FIBER_VM_STACK_SIZE/1024); | |
+ } | |
+} | |
static const rb_data_type_t fiber_data_type = { | |
"fiber", | |
diff --git a/include/ruby/intern.h b/include/ruby/intern.h | |
index e091adc..15e4271 100644 | |
--- a/include/ruby/intern.h | |
+++ b/include/ruby/intern.h | |
@@ -205,6 +205,7 @@ VALUE rb_fiber_resume(VALUE fib, int argc, VALUE *args); | |
VALUE rb_fiber_yield(int argc, VALUE *args); | |
VALUE rb_fiber_current(void); | |
VALUE rb_fiber_alive_p(VALUE); | |
+void rb_fiber_set_stack_params(void); | |
/* enum.c */ | |
/* enumerator.c */ | |
VALUE rb_enumeratorize(VALUE, VALUE, int, VALUE *); | |
diff --git a/ruby.c b/ruby.c | |
index 7ffc78e..83c1c2c 100644 | |
--- a/ruby.c | |
+++ b/ruby.c | |
@@ -1501,6 +1501,7 @@ process_options(int argc, char **argv, struct cmdline_options *opt) | |
rb_set_safe_level(opt->safe_level); | |
rb_gc_set_params(); | |
+ rb_fiber_set_stack_params(); | |
return iseq; | |
} | |
diff --git a/ext/psych/.gitignore b/ext/psych/.gitignore | |
new file mode 100644 | |
index 0000000..836058c | |
--- /dev/null | |
+++ b/ext/psych/.gitignore | |
@@ -0,0 +1,11 @@ | |
+/api.c | |
+/config.h | |
+/dumper.c | |
+/emitter.c | |
+/loader.c | |
+/parser.c | |
+/reader.c | |
+/scanner.c | |
+/writer.c | |
+/yaml.h | |
+/yaml_private.h | |
diff --git a/ext/psych/emitter.c b/ext/psych/emitter.c | |
deleted file mode 100644 | |
index f0d0326..0000000 | |
--- a/ext/psych/emitter.c | |
+++ /dev/null | |
@@ -1,538 +0,0 @@ | |
-#include <psych.h> | |
- | |
-VALUE cPsychEmitter; | |
-static ID id_write; | |
-static ID id_line_width; | |
-static ID id_indentation; | |
-static ID id_canonical; | |
- | |
-static void emit(yaml_emitter_t * emitter, yaml_event_t * event) | |
-{ | |
- if(!yaml_emitter_emit(emitter, event)) | |
- rb_raise(rb_eRuntimeError, "%s", emitter->problem); | |
-} | |
- | |
-static int writer(void *ctx, unsigned char *buffer, size_t size) | |
-{ | |
- VALUE io = (VALUE)ctx; | |
- VALUE str = rb_str_new((const char *)buffer, (long)size); | |
- VALUE wrote = rb_funcall(io, id_write, 1, str); | |
- return (int)NUM2INT(wrote); | |
-} | |
- | |
-static void dealloc(void * ptr) | |
-{ | |
- yaml_emitter_t * emitter; | |
- | |
- emitter = (yaml_emitter_t *)ptr; | |
- yaml_emitter_delete(emitter); | |
- xfree(emitter); | |
-} | |
- | |
-static VALUE allocate(VALUE klass) | |
-{ | |
- yaml_emitter_t * emitter; | |
- | |
- emitter = xmalloc(sizeof(yaml_emitter_t)); | |
- | |
- yaml_emitter_initialize(emitter); | |
- yaml_emitter_set_unicode(emitter, 1); | |
- yaml_emitter_set_indent(emitter, 2); | |
- | |
- return Data_Wrap_Struct(klass, 0, dealloc, emitter); | |
-} | |
- | |
-/* call-seq: Psych::Emitter.new(io, options = Psych::Emitter::OPTIONS) | |
- * | |
- * Create a new Psych::Emitter that writes to +io+. | |
- */ | |
-static VALUE initialize(int argc, VALUE *argv, VALUE self) | |
-{ | |
- yaml_emitter_t * emitter; | |
- VALUE io, options; | |
- VALUE line_width; | |
- VALUE indent; | |
- VALUE canonical; | |
- | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- if (rb_scan_args(argc, argv, "11", &io, &options) == 2) { | |
- line_width = rb_funcall(options, id_line_width, 0); | |
- indent = rb_funcall(options, id_indentation, 0); | |
- canonical = rb_funcall(options, id_canonical, 0); | |
- | |
- yaml_emitter_set_width(emitter, NUM2INT(line_width)); | |
- yaml_emitter_set_indent(emitter, NUM2INT(indent)); | |
- yaml_emitter_set_canonical(emitter, Qtrue == canonical ? 1 : 0); | |
- } | |
- | |
- yaml_emitter_set_output(emitter, writer, (void *)io); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.start_stream(encoding) | |
- * | |
- * Start a stream emission with +encoding+ | |
- * | |
- * See Psych::Handler#start_stream | |
- */ | |
-static VALUE start_stream(VALUE self, VALUE encoding) | |
-{ | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- Check_Type(encoding, T_FIXNUM); | |
- | |
- yaml_stream_start_event_initialize(&event, (yaml_encoding_t)NUM2INT(encoding)); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.end_stream | |
- * | |
- * End a stream emission | |
- * | |
- * See Psych::Handler#end_stream | |
- */ | |
-static VALUE end_stream(VALUE self) | |
-{ | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_stream_end_event_initialize(&event); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.start_document(version, tags, implicit) | |
- * | |
- * Start a document emission with YAML +version+, +tags+, and an +implicit+ | |
- * start. | |
- * | |
- * See Psych::Handler#start_document | |
- */ | |
-static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp) | |
-{ | |
- yaml_emitter_t * emitter; | |
- yaml_tag_directive_t * head = NULL; | |
- yaml_tag_directive_t * tail = NULL; | |
- yaml_event_t event; | |
- yaml_version_directive_t version_directive; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- | |
- Check_Type(version, T_ARRAY); | |
- | |
- if(RARRAY_LEN(version) > 0) { | |
- VALUE major = rb_ary_entry(version, (long)0); | |
- VALUE minor = rb_ary_entry(version, (long)1); | |
- | |
- version_directive.major = NUM2INT(major); | |
- version_directive.minor = NUM2INT(minor); | |
- } | |
- | |
- if(RTEST(tags)) { | |
- int i = 0; | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- rb_encoding * encoding = rb_utf8_encoding(); | |
-#endif | |
- | |
- Check_Type(tags, T_ARRAY); | |
- | |
- head = xcalloc((size_t)RARRAY_LEN(tags), sizeof(yaml_tag_directive_t)); | |
- tail = head; | |
- | |
- for(i = 0; i < RARRAY_LEN(tags); i++) { | |
- VALUE tuple = RARRAY_PTR(tags)[i]; | |
- VALUE name; | |
- VALUE value; | |
- | |
- Check_Type(tuple, T_ARRAY); | |
- | |
- if(RARRAY_LEN(tuple) < 2) { | |
- xfree(head); | |
- rb_raise(rb_eRuntimeError, "tag tuple must be of length 2"); | |
- } | |
- name = RARRAY_PTR(tuple)[0]; | |
- value = RARRAY_PTR(tuple)[1]; | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- name = rb_str_export_to_enc(name, encoding); | |
- value = rb_str_export_to_enc(value, encoding); | |
-#endif | |
- | |
- tail->handle = (yaml_char_t *)StringValuePtr(name); | |
- tail->prefix = (yaml_char_t *)StringValuePtr(value); | |
- | |
- tail++; | |
- } | |
- } | |
- | |
- yaml_document_start_event_initialize( | |
- &event, | |
- (RARRAY_LEN(version) > 0) ? &version_directive : NULL, | |
- head, | |
- tail, | |
- imp ? 1 : 0 | |
- ); | |
- | |
- emit(emitter, &event); | |
- | |
- if(head) xfree(head); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.end_document(implicit) | |
- * | |
- * End a document emission with an +implicit+ ending. | |
- * | |
- * See Psych::Handler#end_document | |
- */ | |
-static VALUE end_document(VALUE self, VALUE imp) | |
-{ | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_document_end_event_initialize(&event, imp ? 1 : 0); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.scalar(value, anchor, tag, plain, quoted, style) | |
- * | |
- * Emit a scalar with +value+, +anchor+, +tag+, and a +plain+ or +quoted+ | |
- * string type with +style+. | |
- * | |
- * See Psych::Handler#scalar | |
- */ | |
-static VALUE scalar( | |
- VALUE self, | |
- VALUE value, | |
- VALUE anchor, | |
- VALUE tag, | |
- VALUE plain, | |
- VALUE quoted, | |
- VALUE style | |
- ) { | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- rb_encoding *encoding; | |
-#endif | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- Check_Type(value, T_STRING); | |
- | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- encoding = rb_utf8_encoding(); | |
- | |
- value = rb_str_export_to_enc(value, encoding); | |
- | |
- if(!NIL_P(anchor)) { | |
- Check_Type(anchor, T_STRING); | |
- anchor = rb_str_export_to_enc(anchor, encoding); | |
- } | |
- | |
- if(!NIL_P(tag)) { | |
- Check_Type(tag, T_STRING); | |
- tag = rb_str_export_to_enc(tag, encoding); | |
- } | |
-#endif | |
- | |
- yaml_scalar_event_initialize( | |
- &event, | |
- (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)), | |
- (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)), | |
- (yaml_char_t*)StringValuePtr(value), | |
- (int)RSTRING_LEN(value), | |
- plain ? 1 : 0, | |
- quoted ? 1 : 0, | |
- (yaml_scalar_style_t)NUM2INT(style) | |
- ); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.start_sequence(anchor, tag, implicit, style) | |
- * | |
- * Start emitting a sequence with +anchor+, a +tag+, +implicit+ sequence | |
- * start and end, along with +style+. | |
- * | |
- * See Psych::Handler#start_sequence | |
- */ | |
-static VALUE start_sequence( | |
- VALUE self, | |
- VALUE anchor, | |
- VALUE tag, | |
- VALUE implicit, | |
- VALUE style | |
- ) { | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
- | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- rb_encoding * encoding = rb_utf8_encoding(); | |
- | |
- if(!NIL_P(anchor)) { | |
- Check_Type(anchor, T_STRING); | |
- anchor = rb_str_export_to_enc(anchor, encoding); | |
- } | |
- | |
- if(!NIL_P(tag)) { | |
- Check_Type(tag, T_STRING); | |
- tag = rb_str_export_to_enc(tag, encoding); | |
- } | |
-#endif | |
- | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_sequence_start_event_initialize( | |
- &event, | |
- (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)), | |
- (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)), | |
- implicit ? 1 : 0, | |
- (yaml_sequence_style_t)NUM2INT(style) | |
- ); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.end_sequence | |
- * | |
- * End sequence emission. | |
- * | |
- * See Psych::Handler#end_sequence | |
- */ | |
-static VALUE end_sequence(VALUE self) | |
-{ | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_sequence_end_event_initialize(&event); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.start_mapping(anchor, tag, implicit, style) | |
- * | |
- * Start emitting a YAML map with +anchor+, +tag+, an +implicit+ start | |
- * and end, and +style+. | |
- * | |
- * See Psych::Handler#start_mapping | |
- */ | |
-static VALUE start_mapping( | |
- VALUE self, | |
- VALUE anchor, | |
- VALUE tag, | |
- VALUE implicit, | |
- VALUE style | |
- ) { | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- rb_encoding *encoding; | |
-#endif | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- encoding = rb_utf8_encoding(); | |
- | |
- if(!NIL_P(anchor)) { | |
- Check_Type(anchor, T_STRING); | |
- anchor = rb_str_export_to_enc(anchor, encoding); | |
- } | |
- | |
- if(!NIL_P(tag)) { | |
- Check_Type(tag, T_STRING); | |
- tag = rb_str_export_to_enc(tag, encoding); | |
- } | |
-#endif | |
- | |
- yaml_mapping_start_event_initialize( | |
- &event, | |
- (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)), | |
- (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)), | |
- implicit ? 1 : 0, | |
- (yaml_mapping_style_t)NUM2INT(style) | |
- ); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.end_mapping | |
- * | |
- * Emit the end of a mapping. | |
- * | |
- * See Psych::Handler#end_mapping | |
- */ | |
-static VALUE end_mapping(VALUE self) | |
-{ | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_mapping_end_event_initialize(&event); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.alias(anchor) | |
- * | |
- * Emit an alias with +anchor+. | |
- * | |
- * See Psych::Handler#alias | |
- */ | |
-static VALUE alias(VALUE self, VALUE anchor) | |
-{ | |
- yaml_emitter_t * emitter; | |
- yaml_event_t event; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- if(!NIL_P(anchor)) { | |
- Check_Type(anchor, T_STRING); | |
- anchor = rb_str_export_to_enc(anchor, rb_utf8_encoding()); | |
- } | |
-#endif | |
- | |
- yaml_alias_event_initialize( | |
- &event, | |
- (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)) | |
- ); | |
- | |
- emit(emitter, &event); | |
- | |
- return self; | |
-} | |
- | |
-/* call-seq: emitter.canonical = true | |
- * | |
- * Set the output style to canonical, or not. | |
- */ | |
-static VALUE set_canonical(VALUE self, VALUE style) | |
-{ | |
- yaml_emitter_t * emitter; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_emitter_set_canonical(emitter, Qtrue == style ? 1 : 0); | |
- | |
- return style; | |
-} | |
- | |
-/* call-seq: emitter.canonical | |
- * | |
- * Get the output style, canonical or not. | |
- */ | |
-static VALUE canonical(VALUE self) | |
-{ | |
- yaml_emitter_t * emitter; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- return (emitter->canonical == 0) ? Qfalse : Qtrue; | |
-} | |
- | |
-/* call-seq: emitter.indentation = level | |
- * | |
- * Set the indentation level to +level+. The level must be less than 10 and | |
- * greater than 1. | |
- */ | |
-static VALUE set_indentation(VALUE self, VALUE level) | |
-{ | |
- yaml_emitter_t * emitter; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_emitter_set_indent(emitter, NUM2INT(level)); | |
- | |
- return level; | |
-} | |
- | |
-/* call-seq: emitter.indentation | |
- * | |
- * Get the indentation level. | |
- */ | |
-static VALUE indentation(VALUE self) | |
-{ | |
- yaml_emitter_t * emitter; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- return INT2NUM(emitter->best_indent); | |
-} | |
- | |
-/* call-seq: emitter.line_width | |
- * | |
- * Get the preferred line width. | |
- */ | |
-static VALUE line_width(VALUE self) | |
-{ | |
- yaml_emitter_t * emitter; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- return INT2NUM(emitter->best_width); | |
-} | |
- | |
-/* call-seq: emitter.line_width = width | |
- * | |
- * Set the preferred line with to +width+. | |
- */ | |
-static VALUE set_line_width(VALUE self, VALUE width) | |
-{ | |
- yaml_emitter_t * emitter; | |
- Data_Get_Struct(self, yaml_emitter_t, emitter); | |
- | |
- yaml_emitter_set_width(emitter, NUM2INT(width)); | |
- | |
- return width; | |
-} | |
- | |
-void Init_psych_emitter() | |
-{ | |
- VALUE psych = rb_define_module("Psych"); | |
- VALUE handler = rb_define_class_under(psych, "Handler", rb_cObject); | |
- cPsychEmitter = rb_define_class_under(psych, "Emitter", handler); | |
- | |
- rb_define_alloc_func(cPsychEmitter, allocate); | |
- | |
- rb_define_method(cPsychEmitter, "initialize", initialize, -1); | |
- rb_define_method(cPsychEmitter, "start_stream", start_stream, 1); | |
- rb_define_method(cPsychEmitter, "end_stream", end_stream, 0); | |
- rb_define_method(cPsychEmitter, "start_document", start_document, 3); | |
- rb_define_method(cPsychEmitter, "end_document", end_document, 1); | |
- rb_define_method(cPsychEmitter, "scalar", scalar, 6); | |
- rb_define_method(cPsychEmitter, "start_sequence", start_sequence, 4); | |
- rb_define_method(cPsychEmitter, "end_sequence", end_sequence, 0); | |
- rb_define_method(cPsychEmitter, "start_mapping", start_mapping, 4); | |
- rb_define_method(cPsychEmitter, "end_mapping", end_mapping, 0); | |
- rb_define_method(cPsychEmitter, "alias", alias, 1); | |
- rb_define_method(cPsychEmitter, "canonical", canonical, 0); | |
- rb_define_method(cPsychEmitter, "canonical=", set_canonical, 1); | |
- rb_define_method(cPsychEmitter, "indentation", indentation, 0); | |
- rb_define_method(cPsychEmitter, "indentation=", set_indentation, 1); | |
- rb_define_method(cPsychEmitter, "line_width", line_width, 0); | |
- rb_define_method(cPsychEmitter, "line_width=", set_line_width, 1); | |
- | |
- id_write = rb_intern("write"); | |
- id_line_width = rb_intern("line_width"); | |
- id_indentation = rb_intern("indentation"); | |
- id_canonical = rb_intern("canonical"); | |
-} | |
-/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/emitter.h b/ext/psych/emitter.h | |
deleted file mode 100644 | |
index 560451e..0000000 | |
--- a/ext/psych/emitter.h | |
+++ /dev/null | |
@@ -1,8 +0,0 @@ | |
-#ifndef PSYCH_EMITTER_H | |
-#define PSYCH_EMITTER_H | |
- | |
-#include <psych.h> | |
- | |
-void Init_psych_emitter(); | |
- | |
-#endif | |
diff --git a/ext/psych/extconf.rb b/ext/psych/extconf.rb | |
index ccc8c9c..65e83a3 100644 | |
--- a/ext/psych/extconf.rb | |
+++ b/ext/psych/extconf.rb | |
@@ -1,15 +1,37 @@ | |
+# -*- coding: us-ascii -*- | |
require 'mkmf' | |
+require 'fileutils' | |
# :stopdoc: | |
dir_config 'libyaml' | |
-def asplode missing | |
- raise "#{missing} is missing. Please install libyaml." | |
-end | |
+if enable_config("bundled-libyaml", false) || !(find_header('yaml.h') && find_library('yaml', 'yaml_get_version')) | |
+ # Embed libyaml since we could not find it. | |
+ | |
+ $VPATH << "$(srcdir)/yaml" | |
+ $INCFLAGS << " -I$(srcdir)/yaml" | |
+ | |
+ $srcs = Dir.glob("#{$srcdir}/{,yaml/}*.c").map {|n| File.basename(n)} | |
-asplode('yaml.h') unless find_header 'yaml.h' | |
-asplode('libyaml') unless find_library 'yaml', 'yaml_get_version' | |
+ if have_macro("_WIN32") | |
+ $CPPFLAGS << " -DYAML_DECLARE_STATIC -DHAVE_CONFIG_H" | |
+ end | |
+ | |
+ have_header 'dlfcn.h' | |
+ have_header 'inttypes.h' | |
+ have_header 'memory.h' | |
+ have_header 'stdint.h' | |
+ have_header 'stdlib.h' | |
+ have_header 'strings.h' | |
+ have_header 'string.h' | |
+ have_header 'sys/stat.h' | |
+ have_header 'sys/types.h' | |
+ have_header 'unistd.h' | |
+ | |
+ find_header 'yaml.h' | |
+ have_header 'config.h' | |
+end | |
create_makefile 'psych' | |
diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb | |
index 19d8b2b..e9571b7 100644 | |
--- a/ext/psych/lib/psych.rb | |
+++ b/ext/psych/lib/psych.rb | |
@@ -18,10 +18,12 @@ require 'psych/handlers/document_stream' | |
### | |
# = Overview | |
# | |
-# Psych is a YAML parser and emitter. Psych leverages | |
-# libyaml[http://libyaml.org] for it's YAML parsing and emitting capabilities. | |
-# In addition to wrapping libyaml, Psych also knows how to serialize and | |
-# de-serialize most Ruby objects to and from the YAML format. | |
+# Psych is a YAML parser and emitter. | |
+# Psych leverages libyaml [Home page: http://pyyaml.org/wiki/LibYAML] | |
+# or [Git repo: https://github.com/zerotao/libyaml] for its YAML parsing | |
+# and emitting capabilities. In addition to wrapping libyaml, Psych also | |
+# knows how to serialize and de-serialize most Ruby objects to and from | |
+# the YAML format. | |
# | |
# = I NEED TO PARSE OR EMIT YAML RIGHT NOW! | |
# | |
@@ -93,7 +95,7 @@ require 'psych/handlers/document_stream' | |
module Psych | |
# The version is Psych you're using | |
- VERSION = '1.3.4' | |
+ VERSION = '2.0.0' | |
# The version of libyaml Psych is using | |
LIBYAML_VERSION = Psych.libyaml_version.join '.' | |
@@ -121,7 +123,7 @@ module Psych | |
# Psych.load("--- `", "file.txt") | |
# rescue Psych::SyntaxError => ex | |
# ex.file # => 'file.txt' | |
- # ex.message # => "(foo.txt): found character that cannot start any token" | |
+ # ex.message # => "(file.txt): found character that cannot start any token" | |
# end | |
def self.load yaml, filename = nil | |
result = parse(yaml, filename) | |
@@ -143,7 +145,7 @@ module Psych | |
# Psych.parse("--- `", "file.txt") | |
# rescue Psych::SyntaxError => ex | |
# ex.file # => 'file.txt' | |
- # ex.message # => "(foo.txt): found character that cannot start any token" | |
+ # ex.message # => "(file.txt): found character that cannot start any token" | |
# end | |
# | |
# See Psych::Nodes for more information about YAML AST. | |
@@ -193,7 +195,7 @@ module Psych | |
# Psych.parse_stream("--- `", "file.txt") | |
# rescue Psych::SyntaxError => ex | |
# ex.file # => 'file.txt' | |
- # ex.message # => "(foo.txt): found character that cannot start any token" | |
+ # ex.message # => "(file.txt): found character that cannot start any token" | |
# end | |
# | |
# See Psych::Nodes for more information about YAML AST. | |
diff --git a/ext/psych/lib/psych/core_ext.rb b/ext/psych/lib/psych/core_ext.rb | |
index 4a04c2d..9c8134d 100644 | |
--- a/ext/psych/lib/psych/core_ext.rb | |
+++ b/ext/psych/lib/psych/core_ext.rb | |
@@ -31,12 +31,5 @@ class Module | |
end | |
if defined?(::IRB) | |
-module Kernel | |
- def psych_y *objects | |
- puts Psych.dump_stream(*objects) | |
- end | |
- remove_method :y rescue nil | |
- alias y psych_y | |
- private :y | |
-end | |
+ require 'psych/y' | |
end | |
diff --git a/ext/psych/lib/psych/deprecated.rb b/ext/psych/lib/psych/deprecated.rb | |
index 333c3a1..1e42859 100644 | |
--- a/ext/psych/lib/psych/deprecated.rb | |
+++ b/ext/psych/lib/psych/deprecated.rb | |
@@ -21,6 +21,7 @@ module Psych | |
target.psych_to_yaml unless opts[:nodump] | |
end | |
+ # This method is deprecated, use Psych.load_stream instead. | |
def self.load_documents yaml, &block | |
if $VERBOSE | |
warn "#{caller[0]}: load_documents is deprecated, use load_stream" | |
diff --git a/ext/psych/lib/psych/handler.rb b/ext/psych/lib/psych/handler.rb | |
index d3b9963..c55afe7 100644 | |
--- a/ext/psych/lib/psych/handler.rb | |
+++ b/ext/psych/lib/psych/handler.rb | |
@@ -25,6 +25,19 @@ module Psych | |
# Default dumping options | |
OPTIONS = DumperOptions.new | |
+ # Events that a Handler should respond to. | |
+ EVENTS = [ :alias, | |
+ :empty, | |
+ :end_document, | |
+ :end_mapping, | |
+ :end_sequence, | |
+ :end_stream, | |
+ :scalar, | |
+ :start_document, | |
+ :start_mapping, | |
+ :start_sequence, | |
+ :start_stream ] | |
+ | |
### | |
# Called with +encoding+ when the YAML stream starts. This method is | |
# called once per stream. A stream may contain multiple documents. | |
diff --git a/ext/psych/lib/psych/handlers/recorder.rb b/ext/psych/lib/psych/handlers/recorder.rb | |
new file mode 100644 | |
index 0000000..4eae62e | |
--- /dev/null | |
+++ b/ext/psych/lib/psych/handlers/recorder.rb | |
@@ -0,0 +1,39 @@ | |
+require 'psych/handler' | |
+ | |
+module Psych | |
+ module Handlers | |
+ ### | |
+ # This handler will capture an event and record the event. Recorder events | |
+ # are available vial Psych::Handlers::Recorder#events. | |
+ # | |
+ # For example: | |
+ # | |
+ # recorder = Psych::Handlers::Recorder.new | |
+ # parser = Psych::Parser.new recorder | |
+ # parser.parse '--- foo' | |
+ # | |
+ # recorder.events # => [list of events] | |
+ # | |
+ # # Replay the events | |
+ # | |
+ # emitter = Psych::Emitter.new $stdout | |
+ # recorder.events.each do |m, args| | |
+ # emitter.send m, *args | |
+ # end | |
+ | |
+ class Recorder < Psych::Handler | |
+ attr_reader :events | |
+ | |
+ def initialize | |
+ @events = [] | |
+ super | |
+ end | |
+ | |
+ EVENTS.each do |event| | |
+ define_method event do |*args| | |
+ @events << [event, args] | |
+ end | |
+ end | |
+ end | |
+ end | |
+end | |
diff --git a/ext/psych/lib/psych/scalar_scanner.rb b/ext/psych/lib/psych/scalar_scanner.rb | |
index fa2d385..8aa594e 100644 | |
--- a/ext/psych/lib/psych/scalar_scanner.rb | |
+++ b/ext/psych/lib/psych/scalar_scanner.rb | |
@@ -8,23 +8,33 @@ module Psych | |
TIME = /^\d{4}-\d{1,2}-\d{1,2}([Tt]|\s+)\d{1,2}:\d\d:\d\d(\.\d*)?(\s*Z|[-+]\d{1,2}(:\d\d)?)?/ | |
# Taken from http://yaml.org/type/float.html | |
- FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9.]*([eE][-+][0-9]+)?(?# base 10) | |
+ FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10) | |
|[-+]?[0-9][0-9_,]*(:[0-5]?[0-9])+\.[0-9_]*(?# base 60) | |
|[-+]?\.(inf|Inf|INF)(?# infinity) | |
|\.(nan|NaN|NAN)(?# not a number))$/x | |
+ # Taken from http://yaml.org/type/int.html | |
+ INTEGER = /^(?:[-+]?0b[0-1_]+ (?# base 2) | |
+ |[-+]?0[0-7_]+ (?# base 8) | |
+ |[-+]?(?:0|[1-9][0-9_]*) (?# base 10) | |
+ |[-+]?0x[0-9a-fA-F_]+ (?# base 16))$/x | |
+ | |
# Create a new scanner | |
def initialize | |
@string_cache = {} | |
+ @symbol_cache = {} | |
end | |
# Tokenize +string+ returning the ruby object | |
def tokenize string | |
return nil if string.empty? | |
return string if @string_cache.key?(string) | |
+ return @symbol_cache[string] if @symbol_cache.key?(string) | |
case string | |
- when /^[A-Za-z~]/ | |
+ # Check for a String type, being careful not to get caught by hash keys, hex values, and | |
+ # special floats (e.g., -.inf). | |
+ when /^[^\d\.:-]?[A-Za-z_\s!@#\$%\^&\*\(\)\{\}\<\>\|\/\\~;=]+/ | |
if string.length > 5 | |
@string_cache[string] = true | |
return string | |
@@ -45,7 +55,11 @@ module Psych | |
string | |
end | |
when TIME | |
- parse_time string | |
+ begin | |
+ parse_time string | |
+ rescue ArgumentError | |
+ string | |
+ end | |
when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/ | |
require 'date' | |
begin | |
@@ -54,16 +68,16 @@ module Psych | |
string | |
end | |
when /^\.inf$/i | |
- 1 / 0.0 | |
+ Float::INFINITY | |
when /^-\.inf$/i | |
- -1 / 0.0 | |
+ -Float::INFINITY | |
when /^\.nan$/i | |
- 0.0 / 0.0 | |
+ Float::NAN | |
when /^:./ | |
if string =~ /^:(["'])(.*)\1/ | |
- $2.sub(/^:/, '').to_sym | |
+ @symbol_cache[string] = $2.sub(/^:/, '').to_sym | |
else | |
- string.sub(/^:/, '').to_sym | |
+ @symbol_cache[string] = string.sub(/^:/, '').to_sym | |
end | |
when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/ | |
i = 0 | |
@@ -78,20 +92,15 @@ module Psych | |
end | |
i | |
when FLOAT | |
- begin | |
- return Float(string.gsub(/[,_]/, '')) | |
- rescue ArgumentError | |
+ if string == '.' | |
+ @string_cache[string] = true | |
+ string | |
+ else | |
+ Float(string.gsub(/[,_]|\.$/, '')) | |
end | |
- | |
- @string_cache[string] = true | |
- string | |
else | |
- if string.count('.') < 2 | |
- begin | |
- return Integer(string.gsub(/[,_]/, '')) | |
- rescue ArgumentError | |
- end | |
- end | |
+ int = parse_int string.gsub(/[,_]/, '') | |
+ return int if int | |
@string_cache[string] = true | |
string | |
@@ -99,6 +108,13 @@ module Psych | |
end | |
### | |
+ # Parse and return an int from +string+ | |
+ def parse_int string | |
+ return unless INTEGER === string | |
+ Integer(string) | |
+ end | |
+ | |
+ ### | |
# Parse and return a Time from +string+ | |
def parse_time string | |
date, time = *(string.split(/[ tT]/, 2)) | |
diff --git a/ext/psych/lib/psych/syntax_error.rb b/ext/psych/lib/psych/syntax_error.rb | |
index f79743d..f972256 100644 | |
--- a/ext/psych/lib/psych/syntax_error.rb | |
+++ b/ext/psych/lib/psych/syntax_error.rb | |
@@ -1,5 +1,8 @@ | |
module Psych | |
- class SyntaxError < ::SyntaxError | |
+ class Error < RuntimeError | |
+ end | |
+ | |
+ class SyntaxError < Error | |
attr_reader :file, :line, :column, :offset, :problem, :context | |
def initialize file, line, col, offset, problem, context | |
diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb | |
index 088301a..9ccf420 100644 | |
--- a/ext/psych/lib/psych/visitors/to_ruby.rb | |
+++ b/ext/psych/lib/psych/visitors/to_ruby.rb | |
@@ -118,6 +118,8 @@ module Psych | |
end | |
case o.tag | |
+ when nil | |
+ register_empty(o) | |
when '!omap', 'tag:yaml.org,2002:omap' | |
map = register(o, Psych::Omap.new) | |
o.children.each { |a| | |
@@ -130,9 +132,7 @@ module Psych | |
o.children.each { |c| list.push accept c } | |
list | |
else | |
- list = register(o, []) | |
- o.children.each { |c| list.push accept c } | |
- list | |
+ register_empty(o) | |
end | |
end | |
@@ -141,28 +141,6 @@ module Psych | |
return revive_hash({}, o) unless o.tag | |
case o.tag | |
- when /^!(?:str|ruby\/string)(?::(.*))?/, 'tag:yaml.org,2002:str' | |
- klass = resolve_class($1) | |
- members = Hash[*o.children.map { |c| accept c }] | |
- string = members.delete 'str' | |
- | |
- if klass | |
- string = klass.allocate.replace string | |
- register(o, string) | |
- end | |
- | |
- init_with(string, members.map { |k,v| [k.to_s.sub(/^@/, ''),v] }, o) | |
- when /^!ruby\/array:(.*)$/ | |
- klass = resolve_class($1) | |
- list = register(o, klass.allocate) | |
- | |
- members = Hash[o.children.map { |c| accept c }.each_slice(2).to_a] | |
- list.replace members['internal'] | |
- | |
- members['ivars'].each do |ivar, v| | |
- list.instance_variable_set ivar, v | |
- end | |
- list | |
when /^!ruby\/struct:?(.*)?$/ | |
klass = resolve_class($1) | |
@@ -187,6 +165,43 @@ module Psych | |
Struct.new(*h.map { |k,v| k.to_sym }).new(*h.map { |k,v| v }) | |
end | |
+ when /^!ruby\/object:?(.*)?$/ | |
+ name = $1 || 'Object' | |
+ | |
+ if name == 'Complex' | |
+ h = Hash[*o.children.map { |c| accept c }] | |
+ register o, Complex(h['real'], h['image']) | |
+ elsif name == 'Rational' | |
+ h = Hash[*o.children.map { |c| accept c }] | |
+ register o, Rational(h['numerator'], h['denominator']) | |
+ else | |
+ obj = revive((resolve_class(name) || Object), o) | |
+ obj | |
+ end | |
+ | |
+ when /^!(?:str|ruby\/string)(?::(.*))?/, 'tag:yaml.org,2002:str' | |
+ klass = resolve_class($1) | |
+ members = Hash[*o.children.map { |c| accept c }] | |
+ string = members.delete 'str' | |
+ | |
+ if klass | |
+ string = klass.allocate.replace string | |
+ register(o, string) | |
+ end | |
+ | |
+ init_with(string, members.map { |k,v| [k.to_s.sub(/^@/, ''),v] }, o) | |
+ when /^!ruby\/array:(.*)$/ | |
+ klass = resolve_class($1) | |
+ list = register(o, klass.allocate) | |
+ | |
+ members = Hash[o.children.map { |c| accept c }.each_slice(2).to_a] | |
+ list.replace members['internal'] | |
+ | |
+ members['ivars'].each do |ivar, v| | |
+ list.instance_variable_set ivar, v | |
+ end | |
+ list | |
+ | |
when '!ruby/range' | |
h = Hash[*o.children.map { |c| accept c }] | |
register o, Range.new(h['begin'], h['end'], h['excl']) | |
@@ -206,19 +221,6 @@ module Psych | |
end | |
set | |
- when '!ruby/object:Complex' | |
- h = Hash[*o.children.map { |c| accept c }] | |
- register o, Complex(h['real'], h['image']) | |
- | |
- when '!ruby/object:Rational' | |
- h = Hash[*o.children.map { |c| accept c }] | |
- register o, Rational(h['numerator'], h['denominator']) | |
- | |
- when /^!ruby\/object:?(.*)?$/ | |
- name = $1 || 'Object' | |
- obj = revive((resolve_class(name) || Object), o) | |
- obj | |
- | |
when /^!map:(.*)$/, /^!ruby\/hash:(.*)$/ | |
revive_hash resolve_class($1).new, o | |
@@ -252,31 +254,51 @@ module Psych | |
object | |
end | |
+ def register_empty object | |
+ list = register(object, []) | |
+ object.children.each { |c| list.push accept c } | |
+ list | |
+ end | |
+ | |
def revive_hash hash, o | |
@st[o.anchor] = hash if o.anchor | |
- o.children.each_slice(2) { |k,v| | |
+ o.children.each_slice(2) { |k,v| | |
key = accept(k) | |
+ val = accept(v) | |
if key == '<<' | |
case v | |
when Nodes::Alias | |
- hash.merge! accept(v) | |
+ begin | |
+ hash.merge! val | |
+ rescue TypeError | |
+ hash[key] = val | |
+ end | |
when Nodes::Sequence | |
- accept(v).reverse_each do |value| | |
- hash.merge! value | |
+ begin | |
+ h = {} | |
+ val.reverse_each do |value| | |
+ h.merge! value | |
+ end | |
+ hash.merge! h | |
+ rescue TypeError | |
+ hash[key] = val | |
end | |
else | |
- hash[key] = accept(v) | |
+ hash[key] = val | |
end | |
else | |
- hash[key] = accept(v) | |
+ hash[key] = val | |
end | |
} | |
hash | |
end | |
+ def merge_key hash, key, val | |
+ end | |
+ | |
def revive klass, node | |
s = klass.allocate | |
@st[node.anchor] = s if node.anchor | |
diff --git a/ext/psych/lib/psych/visitors/yaml_tree.rb b/ext/psych/lib/psych/visitors/yaml_tree.rb | |
index 948a976..6b6a5ff 100644 | |
--- a/ext/psych/lib/psych/visitors/yaml_tree.rb | |
+++ b/ext/psych/lib/psych/visitors/yaml_tree.rb | |
@@ -8,6 +8,30 @@ module Psych | |
# builder.tree # => #<Psych::Nodes::Stream .. } | |
# | |
class YAMLTree < Psych::Visitors::Visitor | |
+ class Registrar # :nodoc: | |
+ def initialize | |
+ @obj_to_id = {} | |
+ @obj_to_node = {} | |
+ @counter = 0 | |
+ end | |
+ | |
+ def register target, node | |
+ @obj_to_node[target.object_id] = node | |
+ end | |
+ | |
+ def key? target | |
+ @obj_to_node.key? target.object_id | |
+ end | |
+ | |
+ def id_for target | |
+ @obj_to_id[target.object_id] ||= (@counter += 1) | |
+ end | |
+ | |
+ def node_for target | |
+ @obj_to_node[target.object_id] | |
+ end | |
+ end | |
+ | |
attr_reader :started, :finished | |
alias :finished? :finished | |
alias :started? :started | |
@@ -17,7 +41,7 @@ module Psych | |
@started = false | |
@finished = false | |
@emitter = emitter | |
- @st = {} | |
+ @st = Registrar.new | |
@ss = ss | |
@options = options | |
@coders = [] | |
@@ -47,6 +71,7 @@ module Psych | |
def tree | |
finish unless finished? | |
+ @emitter.root | |
end | |
def push object | |
@@ -65,15 +90,15 @@ module Psych | |
@emitter.start_document version, [], false | |
accept object | |
- @emitter.end_document | |
+ @emitter.end_document [email protected]? | |
end | |
alias :<< :push | |
def accept target | |
# return any aliases we find | |
- if @st.key? target.object_id | |
- oid = target.object_id | |
- node = @st[oid] | |
+ if @st.key? target | |
+ oid = @st.id_for target | |
+ node = @st.node_for target | |
anchor = oid.to_s | |
node.anchor = anchor | |
return @emitter.alias anchor | |
@@ -220,27 +245,33 @@ module Psych | |
end | |
def binary? string | |
- string.encoding == Encoding::ASCII_8BIT || | |
+ (string.encoding == Encoding::ASCII_8BIT && !string.ascii_only?) || | |
string.index("\x00") || | |
- string.count("\x00-\x7F", "^ -~\t\r\n").fdiv(string.length) > 0.3 | |
+ string.count("\x00-\x7F", "^ -~\t\r\n").fdiv(string.length) > 0.3 || | |
+ string.class != String | |
end | |
private :binary? | |
def visit_String o | |
- plain = false | |
- quote = false | |
- style = Nodes::Scalar::ANY | |
+ plain = true | |
+ quote = true | |
+ style = Nodes::Scalar::PLAIN | |
+ tag = nil | |
+ str = o | |
if binary?(o) | |
str = [o].pack('m').chomp | |
tag = '!binary' # FIXME: change to below when syck is removed | |
#tag = 'tag:yaml.org,2002:binary' | |
style = Nodes::Scalar::LITERAL | |
+ plain = false | |
+ quote = false | |
+ elsif o =~ /\n/ | |
+ style = Nodes::Scalar::LITERAL | |
else | |
- str = o | |
- tag = nil | |
- quote = !(String === @ss.tokenize(o)) | |
- plain = !quote | |
+ unless String === @ss.tokenize(o) | |
+ style = Nodes::Scalar::SINGLE_QUOTED | |
+ end | |
end | |
ivars = find_ivars o | |
@@ -402,7 +433,7 @@ module Psych | |
end | |
def register target, yaml_obj | |
- @st[target.object_id] = yaml_obj | |
+ @st.register target, yaml_obj | |
yaml_obj | |
end | |
diff --git a/ext/psych/lib/psych/y.rb b/ext/psych/lib/psych/y.rb | |
new file mode 100644 | |
index 0000000..6fa0b65 | |
--- /dev/null | |
+++ b/ext/psych/lib/psych/y.rb | |
@@ -0,0 +1,8 @@ | |
+module Kernel | |
+ ### | |
+ # An alias for Psych.dump_stream meant to be used with IRB. | |
+ def y *objects | |
+ puts Psych.dump_stream(*objects) | |
+ end | |
+ private :y | |
+end | |
diff --git a/ext/psych/parser.c b/ext/psych/parser.c | |
deleted file mode 100644 | |
index 0908a1b..0000000 | |
--- a/ext/psych/parser.c | |
+++ /dev/null | |
@@ -1,579 +0,0 @@ | |
-#include <psych.h> | |
- | |
-VALUE cPsychParser; | |
-VALUE ePsychSyntaxError; | |
- | |
-static ID id_read; | |
-static ID id_path; | |
-static ID id_empty; | |
-static ID id_start_stream; | |
-static ID id_end_stream; | |
-static ID id_start_document; | |
-static ID id_end_document; | |
-static ID id_alias; | |
-static ID id_scalar; | |
-static ID id_start_sequence; | |
-static ID id_end_sequence; | |
-static ID id_start_mapping; | |
-static ID id_end_mapping; | |
- | |
-#define PSYCH_TRANSCODE(_str, _yaml_enc, _internal_enc) \ | |
- do { \ | |
- rb_enc_associate_index((_str), (_yaml_enc)); \ | |
- if(_internal_enc) \ | |
- (_str) = rb_str_export_to_enc((_str), (_internal_enc)); \ | |
- } while (0) | |
- | |
-static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read) | |
-{ | |
- VALUE io = (VALUE)data; | |
- VALUE string = rb_funcall(io, id_read, 1, INT2NUM(size)); | |
- | |
- *read = 0; | |
- | |
- if(! NIL_P(string)) { | |
- void * str = (void *)StringValuePtr(string); | |
- *read = (size_t)RSTRING_LEN(string); | |
- memcpy(buf, str, *read); | |
- } | |
- | |
- return 1; | |
-} | |
- | |
-static void dealloc(void * ptr) | |
-{ | |
- yaml_parser_t * parser; | |
- | |
- parser = (yaml_parser_t *)ptr; | |
- yaml_parser_delete(parser); | |
- xfree(parser); | |
-} | |
- | |
-static VALUE allocate(VALUE klass) | |
-{ | |
- yaml_parser_t * parser; | |
- | |
- parser = xmalloc(sizeof(yaml_parser_t)); | |
- yaml_parser_initialize(parser); | |
- | |
- return Data_Wrap_Struct(klass, 0, dealloc, parser); | |
-} | |
- | |
-static VALUE make_exception(yaml_parser_t * parser, VALUE path) | |
-{ | |
- size_t line, column; | |
- | |
- line = parser->context_mark.line + 1; | |
- column = parser->context_mark.column + 1; | |
- | |
- return rb_funcall(ePsychSyntaxError, rb_intern("new"), 6, | |
- path, | |
- INT2NUM(line), | |
- INT2NUM(column), | |
- INT2NUM(parser->problem_offset), | |
- parser->problem ? rb_usascii_str_new2(parser->problem) : Qnil, | |
- parser->context ? rb_usascii_str_new2(parser->context) : Qnil); | |
-} | |
- | |
-#ifdef HAVE_RUBY_ENCODING_H | |
-static VALUE transcode_string(VALUE src, int * parser_encoding) | |
-{ | |
- int utf8 = rb_utf8_encindex(); | |
- int utf16le = rb_enc_find_index("UTF-16LE"); | |
- int utf16be = rb_enc_find_index("UTF-16BE"); | |
- int source_encoding = rb_enc_get_index(src); | |
- | |
- if (source_encoding == utf8) { | |
- *parser_encoding = YAML_UTF8_ENCODING; | |
- return src; | |
- } | |
- | |
- if (source_encoding == utf16le) { | |
- *parser_encoding = YAML_UTF16LE_ENCODING; | |
- return src; | |
- } | |
- | |
- if (source_encoding == utf16be) { | |
- *parser_encoding = YAML_UTF16BE_ENCODING; | |
- return src; | |
- } | |
- | |
- src = rb_str_export_to_enc(src, rb_utf8_encoding()); | |
- RB_GC_GUARD(src); | |
- | |
- *parser_encoding = YAML_UTF8_ENCODING; | |
- return src; | |
-} | |
- | |
-static VALUE transcode_io(VALUE src, int * parser_encoding) | |
-{ | |
- VALUE io_external_encoding; | |
- int io_external_enc_index; | |
- | |
- io_external_encoding = rb_funcall(src, rb_intern("external_encoding"), 0); | |
- | |
- /* if no encoding is returned, assume ascii8bit. */ | |
- if (NIL_P(io_external_encoding)) { | |
- io_external_enc_index = rb_ascii8bit_encindex(); | |
- } else { | |
- io_external_enc_index = rb_to_encoding_index(io_external_encoding); | |
- } | |
- | |
- /* Treat US-ASCII as utf_8 */ | |
- if (io_external_enc_index == rb_usascii_encindex()) { | |
- *parser_encoding = YAML_UTF8_ENCODING; | |
- return src; | |
- } | |
- | |
- if (io_external_enc_index == rb_utf8_encindex()) { | |
- *parser_encoding = YAML_UTF8_ENCODING; | |
- return src; | |
- } | |
- | |
- if (io_external_enc_index == rb_enc_find_index("UTF-16LE")) { | |
- *parser_encoding = YAML_UTF16LE_ENCODING; | |
- return src; | |
- } | |
- | |
- if (io_external_enc_index == rb_enc_find_index("UTF-16BE")) { | |
- *parser_encoding = YAML_UTF16BE_ENCODING; | |
- return src; | |
- } | |
- | |
- /* Just guess on ASCII-8BIT */ | |
- if (io_external_enc_index == rb_ascii8bit_encindex()) { | |
- *parser_encoding = YAML_ANY_ENCODING; | |
- return src; | |
- } | |
- | |
- /* If the external encoding is something we don't know how to handle, | |
- * fall back to YAML_ANY_ENCODING. */ | |
- *parser_encoding = YAML_ANY_ENCODING; | |
- | |
- return src; | |
-} | |
- | |
-#endif | |
- | |
-static VALUE protected_start_stream(VALUE pointer) | |
-{ | |
- VALUE *args = (VALUE *)pointer; | |
- return rb_funcall(args[0], id_start_stream, 1, args[1]); | |
-} | |
- | |
-static VALUE protected_start_document(VALUE pointer) | |
-{ | |
- VALUE *args = (VALUE *)pointer; | |
- return rb_funcall3(args[0], id_start_document, 3, args + 1); | |
-} | |
- | |
-static VALUE protected_end_document(VALUE pointer) | |
-{ | |
- VALUE *args = (VALUE *)pointer; | |
- return rb_funcall(args[0], id_end_document, 1, args[1]); | |
-} | |
- | |
-static VALUE protected_alias(VALUE pointer) | |
-{ | |
- VALUE *args = (VALUE *)pointer; | |
- return rb_funcall(args[0], id_alias, 1, args[1]); | |
-} | |
- | |
-static VALUE protected_scalar(VALUE pointer) | |
-{ | |
- VALUE *args = (VALUE *)pointer; | |
- return rb_funcall3(args[0], id_scalar, 6, args + 1); | |
-} | |
- | |
-static VALUE protected_start_sequence(VALUE pointer) | |
-{ | |
- VALUE *args = (VALUE *)pointer; | |
- return rb_funcall3(args[0], id_start_sequence, 4, args + 1); | |
-} | |
- | |
-static VALUE protected_end_sequence(VALUE handler) | |
-{ | |
- return rb_funcall(handler, id_end_sequence, 0); | |
-} | |
- | |
-static VALUE protected_start_mapping(VALUE pointer) | |
-{ | |
- VALUE *args = (VALUE *)pointer; | |
- return rb_funcall3(args[0], id_start_mapping, 4, args + 1); | |
-} | |
- | |
-static VALUE protected_end_mapping(VALUE handler) | |
-{ | |
- return rb_funcall(handler, id_end_mapping, 0); | |
-} | |
- | |
-static VALUE protected_empty(VALUE handler) | |
-{ | |
- return rb_funcall(handler, id_empty, 0); | |
-} | |
- | |
-static VALUE protected_end_stream(VALUE handler) | |
-{ | |
- return rb_funcall(handler, id_end_stream, 0); | |
-} | |
- | |
-/* | |
- * call-seq: | |
- * parser.parse(yaml) | |
- * | |
- * Parse the YAML document contained in +yaml+. Events will be called on | |
- * the handler set on the parser instance. | |
- * | |
- * See Psych::Parser and Psych::Parser#handler | |
- */ | |
-static VALUE parse(int argc, VALUE *argv, VALUE self) | |
-{ | |
- VALUE yaml, path; | |
- yaml_parser_t * parser; | |
- yaml_event_t event; | |
- int done = 0; | |
- int tainted = 0; | |
- int state = 0; | |
- int parser_encoding = YAML_ANY_ENCODING; | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- int encoding = rb_utf8_encindex(); | |
- rb_encoding * internal_enc = rb_default_internal_encoding(); | |
-#endif | |
- VALUE handler = rb_iv_get(self, "@handler"); | |
- | |
- if (rb_scan_args(argc, argv, "11", &yaml, &path) == 1) { | |
- if(rb_respond_to(yaml, id_path)) | |
- path = rb_funcall(yaml, id_path, 0); | |
- else | |
- path = rb_str_new2("<unknown>"); | |
- } | |
- | |
- Data_Get_Struct(self, yaml_parser_t, parser); | |
- | |
- yaml_parser_delete(parser); | |
- yaml_parser_initialize(parser); | |
- | |
- if (OBJ_TAINTED(yaml)) tainted = 1; | |
- | |
- if (rb_respond_to(yaml, id_read)) { | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- yaml = transcode_io(yaml, &parser_encoding); | |
- yaml_parser_set_encoding(parser, parser_encoding); | |
-#endif | |
- yaml_parser_set_input(parser, io_reader, (void *)yaml); | |
- if (RTEST(rb_obj_is_kind_of(yaml, rb_cIO))) tainted = 1; | |
- } else { | |
- StringValue(yaml); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- yaml = transcode_string(yaml, &parser_encoding); | |
- yaml_parser_set_encoding(parser, parser_encoding); | |
-#endif | |
- yaml_parser_set_input_string( | |
- parser, | |
- (const unsigned char *)RSTRING_PTR(yaml), | |
- (size_t)RSTRING_LEN(yaml) | |
- ); | |
- } | |
- | |
- while(!done) { | |
- if(!yaml_parser_parse(parser, &event)) { | |
- VALUE exception; | |
- | |
- exception = make_exception(parser, path); | |
- yaml_parser_delete(parser); | |
- yaml_parser_initialize(parser); | |
- | |
- rb_exc_raise(exception); | |
- } | |
- | |
- switch(event.type) { | |
- case YAML_STREAM_START_EVENT: | |
- { | |
- VALUE args[2]; | |
- | |
- args[0] = handler; | |
- args[1] = INT2NUM((long)event.data.stream_start.encoding); | |
- rb_protect(protected_start_stream, (VALUE)args, &state); | |
- } | |
- break; | |
- case YAML_DOCUMENT_START_EVENT: | |
- { | |
- VALUE args[4]; | |
- /* Get a list of tag directives (if any) */ | |
- VALUE tag_directives = rb_ary_new(); | |
- /* Grab the document version */ | |
- VALUE version = event.data.document_start.version_directive ? | |
- rb_ary_new3( | |
- (long)2, | |
- INT2NUM((long)event.data.document_start.version_directive->major), | |
- INT2NUM((long)event.data.document_start.version_directive->minor) | |
- ) : rb_ary_new(); | |
- | |
- if(event.data.document_start.tag_directives.start) { | |
- yaml_tag_directive_t *start = | |
- event.data.document_start.tag_directives.start; | |
- yaml_tag_directive_t *end = | |
- event.data.document_start.tag_directives.end; | |
- for(; start != end; start++) { | |
- VALUE handle = Qnil; | |
- VALUE prefix = Qnil; | |
- if(start->handle) { | |
- handle = rb_str_new2((const char *)start->handle); | |
- if (tainted) OBJ_TAINT(handle); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(handle, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- if(start->prefix) { | |
- prefix = rb_str_new2((const char *)start->prefix); | |
- if (tainted) OBJ_TAINT(prefix); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(prefix, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- rb_ary_push(tag_directives, rb_ary_new3((long)2, handle, prefix)); | |
- } | |
- } | |
- args[0] = handler; | |
- args[1] = version; | |
- args[2] = tag_directives; | |
- args[3] = event.data.document_start.implicit == 1 ? Qtrue : Qfalse; | |
- rb_protect(protected_start_document, (VALUE)args, &state); | |
- } | |
- break; | |
- case YAML_DOCUMENT_END_EVENT: | |
- { | |
- VALUE args[2]; | |
- | |
- args[0] = handler; | |
- args[1] = event.data.document_end.implicit == 1 ? Qtrue : Qfalse; | |
- rb_protect(protected_end_document, (VALUE)args, &state); | |
- } | |
- break; | |
- case YAML_ALIAS_EVENT: | |
- { | |
- VALUE args[2]; | |
- VALUE alias = Qnil; | |
- if(event.data.alias.anchor) { | |
- alias = rb_str_new2((const char *)event.data.alias.anchor); | |
- if (tainted) OBJ_TAINT(alias); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(alias, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- args[0] = handler; | |
- args[1] = alias; | |
- rb_protect(protected_alias, (VALUE)args, &state); | |
- } | |
- break; | |
- case YAML_SCALAR_EVENT: | |
- { | |
- VALUE args[7]; | |
- VALUE anchor = Qnil; | |
- VALUE tag = Qnil; | |
- VALUE plain_implicit, quoted_implicit, style; | |
- VALUE val = rb_str_new( | |
- (const char *)event.data.scalar.value, | |
- (long)event.data.scalar.length | |
- ); | |
- if (tainted) OBJ_TAINT(val); | |
- | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(val, encoding, internal_enc); | |
-#endif | |
- | |
- if(event.data.scalar.anchor) { | |
- anchor = rb_str_new2((const char *)event.data.scalar.anchor); | |
- if (tainted) OBJ_TAINT(anchor); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(anchor, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- if(event.data.scalar.tag) { | |
- tag = rb_str_new2((const char *)event.data.scalar.tag); | |
- if (tainted) OBJ_TAINT(tag); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(tag, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- plain_implicit = | |
- event.data.scalar.plain_implicit == 0 ? Qfalse : Qtrue; | |
- | |
- quoted_implicit = | |
- event.data.scalar.quoted_implicit == 0 ? Qfalse : Qtrue; | |
- | |
- style = INT2NUM((long)event.data.scalar.style); | |
- | |
- args[0] = handler; | |
- args[1] = val; | |
- args[2] = anchor; | |
- args[3] = tag; | |
- args[4] = plain_implicit; | |
- args[5] = quoted_implicit; | |
- args[6] = style; | |
- rb_protect(protected_scalar, (VALUE)args, &state); | |
- } | |
- break; | |
- case YAML_SEQUENCE_START_EVENT: | |
- { | |
- VALUE args[5]; | |
- VALUE anchor = Qnil; | |
- VALUE tag = Qnil; | |
- VALUE implicit, style; | |
- if(event.data.sequence_start.anchor) { | |
- anchor = rb_str_new2((const char *)event.data.sequence_start.anchor); | |
- if (tainted) OBJ_TAINT(anchor); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(anchor, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- tag = Qnil; | |
- if(event.data.sequence_start.tag) { | |
- tag = rb_str_new2((const char *)event.data.sequence_start.tag); | |
- if (tainted) OBJ_TAINT(tag); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(tag, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- implicit = | |
- event.data.sequence_start.implicit == 0 ? Qfalse : Qtrue; | |
- | |
- style = INT2NUM((long)event.data.sequence_start.style); | |
- | |
- args[0] = handler; | |
- args[1] = anchor; | |
- args[2] = tag; | |
- args[3] = implicit; | |
- args[4] = style; | |
- | |
- rb_protect(protected_start_sequence, (VALUE)args, &state); | |
- } | |
- break; | |
- case YAML_SEQUENCE_END_EVENT: | |
- rb_protect(protected_end_sequence, handler, &state); | |
- break; | |
- case YAML_MAPPING_START_EVENT: | |
- { | |
- VALUE args[5]; | |
- VALUE anchor = Qnil; | |
- VALUE tag = Qnil; | |
- VALUE implicit, style; | |
- if(event.data.mapping_start.anchor) { | |
- anchor = rb_str_new2((const char *)event.data.mapping_start.anchor); | |
- if (tainted) OBJ_TAINT(anchor); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(anchor, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- if(event.data.mapping_start.tag) { | |
- tag = rb_str_new2((const char *)event.data.mapping_start.tag); | |
- if (tainted) OBJ_TAINT(tag); | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- PSYCH_TRANSCODE(tag, encoding, internal_enc); | |
-#endif | |
- } | |
- | |
- implicit = | |
- event.data.mapping_start.implicit == 0 ? Qfalse : Qtrue; | |
- | |
- style = INT2NUM((long)event.data.mapping_start.style); | |
- | |
- args[0] = handler; | |
- args[1] = anchor; | |
- args[2] = tag; | |
- args[3] = implicit; | |
- args[4] = style; | |
- | |
- rb_protect(protected_start_mapping, (VALUE)args, &state); | |
- } | |
- break; | |
- case YAML_MAPPING_END_EVENT: | |
- rb_protect(protected_end_mapping, handler, &state); | |
- break; | |
- case YAML_NO_EVENT: | |
- rb_protect(protected_empty, handler, &state); | |
- break; | |
- case YAML_STREAM_END_EVENT: | |
- rb_protect(protected_end_stream, handler, &state); | |
- done = 1; | |
- break; | |
- } | |
- yaml_event_delete(&event); | |
- if (state) rb_jump_tag(state); | |
- } | |
- | |
- return self; | |
-} | |
- | |
-/* | |
- * call-seq: | |
- * parser.mark # => #<Psych::Parser::Mark> | |
- * | |
- * Returns a Psych::Parser::Mark object that contains line, column, and index | |
- * information. | |
- */ | |
-static VALUE mark(VALUE self) | |
-{ | |
- VALUE mark_klass; | |
- VALUE args[3]; | |
- yaml_parser_t * parser; | |
- | |
- Data_Get_Struct(self, yaml_parser_t, parser); | |
- mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark")); | |
- args[0] = INT2NUM(parser->mark.index); | |
- args[1] = INT2NUM(parser->mark.line); | |
- args[2] = INT2NUM(parser->mark.column); | |
- | |
- return rb_class_new_instance(3, args, mark_klass); | |
-} | |
- | |
-void Init_psych_parser() | |
-{ | |
-#if 0 | |
- mPsych = rb_define_module("Psych"); | |
-#endif | |
- | |
- cPsychParser = rb_define_class_under(mPsych, "Parser", rb_cObject); | |
- rb_define_alloc_func(cPsychParser, allocate); | |
- | |
- /* Any encoding: Let the parser choose the encoding */ | |
- rb_define_const(cPsychParser, "ANY", INT2NUM(YAML_ANY_ENCODING)); | |
- | |
- /* UTF-8 Encoding */ | |
- rb_define_const(cPsychParser, "UTF8", INT2NUM(YAML_UTF8_ENCODING)); | |
- | |
- /* UTF-16-LE Encoding with BOM */ | |
- rb_define_const(cPsychParser, "UTF16LE", INT2NUM(YAML_UTF16LE_ENCODING)); | |
- | |
- /* UTF-16-BE Encoding with BOM */ | |
- rb_define_const(cPsychParser, "UTF16BE", INT2NUM(YAML_UTF16BE_ENCODING)); | |
- | |
- rb_require("psych/syntax_error"); | |
- ePsychSyntaxError = rb_define_class_under(mPsych, "SyntaxError", rb_eSyntaxError); | |
- | |
- rb_define_method(cPsychParser, "parse", parse, -1); | |
- rb_define_method(cPsychParser, "mark", mark, 0); | |
- | |
- id_read = rb_intern("read"); | |
- id_path = rb_intern("path"); | |
- id_empty = rb_intern("empty"); | |
- id_start_stream = rb_intern("start_stream"); | |
- id_end_stream = rb_intern("end_stream"); | |
- id_start_document = rb_intern("start_document"); | |
- id_end_document = rb_intern("end_document"); | |
- id_alias = rb_intern("alias"); | |
- id_scalar = rb_intern("scalar"); | |
- id_start_sequence = rb_intern("start_sequence"); | |
- id_end_sequence = rb_intern("end_sequence"); | |
- id_start_mapping = rb_intern("start_mapping"); | |
- id_end_mapping = rb_intern("end_mapping"); | |
-} | |
-/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/parser.h b/ext/psych/parser.h | |
deleted file mode 100644 | |
index 25e896f..0000000 | |
--- a/ext/psych/parser.h | |
+++ /dev/null | |
@@ -1,6 +0,0 @@ | |
-#ifndef PSYCH_PARSER_H | |
-#define PSYCH_PARSER_H | |
- | |
-void Init_psych_parser(); | |
- | |
-#endif | |
diff --git a/ext/psych/psych.gemspec b/ext/psych/psych.gemspec | |
new file mode 100644 | |
index 0000000..c15449c | |
--- /dev/null | |
+++ b/ext/psych/psych.gemspec | |
@@ -0,0 +1,24 @@ | |
+# -*- encoding: utf-8 -*- | |
+ | |
+Gem::Specification.new do |s| | |
+ s.name = "psych" | |
+ s.version = "2.0.0" | |
+ | |
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= | |
+ s.authors = ["Aaron Patterson"] | |
+ s.date = "2012-11-28" | |
+ s.description = "Psych is a YAML parser and emitter. Psych leverages libyaml[http://pyyaml.org/wiki/LibYAML]\nfor its YAML parsing and emitting capabilities. In addition to wrapping\nlibyaml, Psych also knows how to serialize and de-serialize most Ruby objects\nto and from the YAML format." | |
+ s.email = ["[email protected]"] | |
+ s.extensions = ["ext/psych/extconf.rb"] | |
+ s.extra_rdoc_files = ["CHANGELOG.rdoc", "Manifest.txt", "README.rdoc"] | |
+ s.files = [".autotest", ".travis.yml", "CHANGELOG.rdoc", "Manifest.txt", "README.rdoc", "Rakefile", "ext/psych/extconf.rb", "ext/psych/psych.c", "ext/psych/psych.h", "ext/psych/psych_emitter.c", "ext/psych/psych_emitter.h", "ext/psych/psych_parser.c", "ext/psych/psych_parser.h", "ext/psych/psych_to_ruby.c", "ext/psych/psych_to_ruby.h", "ext/psych/psych_yaml_tree.c", "ext/psych/psych_yaml_tree.h", "ext/psych/yaml/LICENSE", "ext/psych/yaml/api.c", "ext/psych/yaml/config.h", "ext/psych/yaml/dumper.c", "ext/psych/yaml/emitter.c", "ext/psych/yaml/loader.c", "ext/psych/yaml/parser.c", "ext/psych/yaml/reader.c", "ext/psych/yaml/scanner.c", "ext/psych/yaml/writer.c", "ext/psych/yaml/yaml.h", "ext/psych/yaml/yaml_private.h", "lib/psych.rb", "lib/psych/coder.rb", "lib/psych/core_ext.rb", "lib/psych/deprecated.rb", "lib/psych/handler.rb", "lib/psych/handlers/document_stream.rb", "lib/psych/handlers/recorder.rb", "lib/psych/json/ruby_events.rb", "lib/psych/json/stream.rb", "lib/psych/json/tree_builder.rb", "lib/psych/json/yaml_events.rb", "lib/psych/nodes.rb", "lib/psych/nodes/alias.rb", "lib/psych/nodes/document.rb", "lib/psych/nodes/mapping.rb", "lib/psych/nodes/node.rb", "lib/psych/nodes/scalar.rb", "lib/psych/nodes/sequence.rb", "lib/psych/nodes/stream.rb", "lib/psych/omap.rb", "lib/psych/parser.rb", "lib/psych/scalar_scanner.rb", "lib/psych/set.rb", "lib/psych/stream.rb", "lib/psych/streaming.rb", "lib/psych/syntax_error.rb", "lib/psych/tree_builder.rb", "lib/psych/visitors.rb", "lib/psych/visitors/depth_first.rb", "lib/psych/visitors/emitter.rb", "lib/psych/visitors/json_tree.rb", "lib/psych/visitors/to_ruby.rb", "lib/psych/visitors/visitor.rb", "lib/psych/visitors/yaml_tree.rb", "lib/psych/y.rb", "test/psych/handlers/test_recorder.rb", "test/psych/helper.rb", "test/psych/json/test_stream.rb", "test/psych/nodes/test_enumerable.rb", "test/psych/test_alias_and_anchor.rb", "test/psych/test_array.rb", "test/psych/test_boolean.rb", "test/psych/test_class.rb", "test/psych/test_coder.rb", "test/psych/test_date_time.rb", "test/psych/test_deprecated.rb", "test/psych/test_document.rb", "test/psych/test_emitter.rb", "test/psych/test_encoding.rb", "test/psych/test_engine_manager.rb", "test/psych/test_exception.rb", "test/psych/test_hash.rb", "test/psych/test_json_tree.rb", "test/psych/test_merge_keys.rb", "test/psych/test_nil.rb", "test/psych/test_null.rb", "test/psych/test_numeric.rb", "test/psych/test_object.rb", "test/psych/test_object_references.rb", "test/psych/test_omap.rb", "test/psych/test_parser.rb", "test/psych/test_psych.rb", "test/psych/test_scalar.rb", "test/psych/test_scalar_scanner.rb", "test/psych/test_serialize_subclasses.rb", "test/psych/test_set.rb", "test/psych/test_stream.rb", "test/psych/test_string.rb", "test/psych/test_struct.rb", "test/psych/test_symbol.rb", "test/psych/test_tainted.rb", "test/psych/test_to_yaml_properties.rb", "test/psych/test_tree_builder.rb", "test/psych/test_yaml.rb", "test/psych/test_yamldbm.rb", "test/psych/test_yamlstore.rb", "test/psych/visitors/test_depth_first.rb", "test/psych/visitors/test_emitter.rb", "test/psych/visitors/test_to_ruby.rb", "test/psych/visitors/test_yaml_tree.rb", ".gemtest"] | |
+ s.homepage = "http://github.com/tenderlove/psych" | |
+ s.rdoc_options = ["--main", "README.rdoc"] | |
+ s.require_paths = ["lib"] | |
+ s.required_ruby_version = Gem::Requirement.new(">= 1.9.2") | |
+ s.rubyforge_project = "psych" | |
+ s.rubygems_version = "1.8.24" | |
+ s.summary = "Psych is a YAML parser and emitter" | |
+ s.test_files = ["test/psych/handlers/test_recorder.rb", "test/psych/json/test_stream.rb", "test/psych/nodes/test_enumerable.rb", "test/psych/test_alias_and_anchor.rb", "test/psych/test_array.rb", "test/psych/test_boolean.rb", "test/psych/test_class.rb", "test/psych/test_coder.rb", "test/psych/test_date_time.rb", "test/psych/test_deprecated.rb", "test/psych/test_document.rb", "test/psych/test_emitter.rb", "test/psych/test_encoding.rb", "test/psych/test_engine_manager.rb", "test/psych/test_exception.rb", "test/psych/test_hash.rb", "test/psych/test_json_tree.rb", "test/psych/test_merge_keys.rb", "test/psych/test_nil.rb", "test/psych/test_null.rb", "test/psych/test_numeric.rb", "test/psych/test_object.rb", "test/psych/test_object_references.rb", "test/psych/test_omap.rb", "test/psych/test_parser.rb", "test/psych/test_psych.rb", "test/psych/test_scalar.rb", "test/psych/test_scalar_scanner.rb", "test/psych/test_serialize_subclasses.rb", "test/psych/test_set.rb", "test/psych/test_stream.rb", "test/psych/test_string.rb", "test/psych/test_struct.rb", "test/psych/test_symbol.rb", "test/psych/test_tainted.rb", "test/psych/test_to_yaml_properties.rb", "test/psych/test_tree_builder.rb", "test/psych/test_yaml.rb", "test/psych/test_yamldbm.rb", "test/psych/test_yamlstore.rb", "test/psych/visitors/test_depth_first.rb", "test/psych/visitors/test_emitter.rb", "test/psych/visitors/test_to_ruby.rb", "test/psych/visitors/test_yaml_tree.rb"] | |
+ | |
+end | |
diff --git a/ext/psych/psych.h b/ext/psych/psych.h | |
index 9f1be44..1830ca4 100644 | |
--- a/ext/psych/psych.h | |
+++ b/ext/psych/psych.h | |
@@ -9,10 +9,10 @@ | |
#include <yaml.h> | |
-#include <parser.h> | |
-#include <emitter.h> | |
-#include <to_ruby.h> | |
-#include <yaml_tree.h> | |
+#include <psych_parser.h> | |
+#include <psych_emitter.h> | |
+#include <psych_to_ruby.h> | |
+#include <psych_yaml_tree.h> | |
extern VALUE mPsych; | |
diff --git a/ext/psych/psych_emitter.c b/ext/psych/psych_emitter.c | |
new file mode 100644 | |
index 0000000..f0d0326 | |
--- /dev/null | |
+++ b/ext/psych/psych_emitter.c | |
@@ -0,0 +1,538 @@ | |
+#include <psych.h> | |
+ | |
+VALUE cPsychEmitter; | |
+static ID id_write; | |
+static ID id_line_width; | |
+static ID id_indentation; | |
+static ID id_canonical; | |
+ | |
+static void emit(yaml_emitter_t * emitter, yaml_event_t * event) | |
+{ | |
+ if(!yaml_emitter_emit(emitter, event)) | |
+ rb_raise(rb_eRuntimeError, "%s", emitter->problem); | |
+} | |
+ | |
+static int writer(void *ctx, unsigned char *buffer, size_t size) | |
+{ | |
+ VALUE io = (VALUE)ctx; | |
+ VALUE str = rb_str_new((const char *)buffer, (long)size); | |
+ VALUE wrote = rb_funcall(io, id_write, 1, str); | |
+ return (int)NUM2INT(wrote); | |
+} | |
+ | |
+static void dealloc(void * ptr) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ | |
+ emitter = (yaml_emitter_t *)ptr; | |
+ yaml_emitter_delete(emitter); | |
+ xfree(emitter); | |
+} | |
+ | |
+static VALUE allocate(VALUE klass) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ | |
+ emitter = xmalloc(sizeof(yaml_emitter_t)); | |
+ | |
+ yaml_emitter_initialize(emitter); | |
+ yaml_emitter_set_unicode(emitter, 1); | |
+ yaml_emitter_set_indent(emitter, 2); | |
+ | |
+ return Data_Wrap_Struct(klass, 0, dealloc, emitter); | |
+} | |
+ | |
+/* call-seq: Psych::Emitter.new(io, options = Psych::Emitter::OPTIONS) | |
+ * | |
+ * Create a new Psych::Emitter that writes to +io+. | |
+ */ | |
+static VALUE initialize(int argc, VALUE *argv, VALUE self) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ VALUE io, options; | |
+ VALUE line_width; | |
+ VALUE indent; | |
+ VALUE canonical; | |
+ | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ if (rb_scan_args(argc, argv, "11", &io, &options) == 2) { | |
+ line_width = rb_funcall(options, id_line_width, 0); | |
+ indent = rb_funcall(options, id_indentation, 0); | |
+ canonical = rb_funcall(options, id_canonical, 0); | |
+ | |
+ yaml_emitter_set_width(emitter, NUM2INT(line_width)); | |
+ yaml_emitter_set_indent(emitter, NUM2INT(indent)); | |
+ yaml_emitter_set_canonical(emitter, Qtrue == canonical ? 1 : 0); | |
+ } | |
+ | |
+ yaml_emitter_set_output(emitter, writer, (void *)io); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.start_stream(encoding) | |
+ * | |
+ * Start a stream emission with +encoding+ | |
+ * | |
+ * See Psych::Handler#start_stream | |
+ */ | |
+static VALUE start_stream(VALUE self, VALUE encoding) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ Check_Type(encoding, T_FIXNUM); | |
+ | |
+ yaml_stream_start_event_initialize(&event, (yaml_encoding_t)NUM2INT(encoding)); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.end_stream | |
+ * | |
+ * End a stream emission | |
+ * | |
+ * See Psych::Handler#end_stream | |
+ */ | |
+static VALUE end_stream(VALUE self) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_stream_end_event_initialize(&event); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.start_document(version, tags, implicit) | |
+ * | |
+ * Start a document emission with YAML +version+, +tags+, and an +implicit+ | |
+ * start. | |
+ * | |
+ * See Psych::Handler#start_document | |
+ */ | |
+static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ yaml_tag_directive_t * head = NULL; | |
+ yaml_tag_directive_t * tail = NULL; | |
+ yaml_event_t event; | |
+ yaml_version_directive_t version_directive; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ | |
+ Check_Type(version, T_ARRAY); | |
+ | |
+ if(RARRAY_LEN(version) > 0) { | |
+ VALUE major = rb_ary_entry(version, (long)0); | |
+ VALUE minor = rb_ary_entry(version, (long)1); | |
+ | |
+ version_directive.major = NUM2INT(major); | |
+ version_directive.minor = NUM2INT(minor); | |
+ } | |
+ | |
+ if(RTEST(tags)) { | |
+ int i = 0; | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ rb_encoding * encoding = rb_utf8_encoding(); | |
+#endif | |
+ | |
+ Check_Type(tags, T_ARRAY); | |
+ | |
+ head = xcalloc((size_t)RARRAY_LEN(tags), sizeof(yaml_tag_directive_t)); | |
+ tail = head; | |
+ | |
+ for(i = 0; i < RARRAY_LEN(tags); i++) { | |
+ VALUE tuple = RARRAY_PTR(tags)[i]; | |
+ VALUE name; | |
+ VALUE value; | |
+ | |
+ Check_Type(tuple, T_ARRAY); | |
+ | |
+ if(RARRAY_LEN(tuple) < 2) { | |
+ xfree(head); | |
+ rb_raise(rb_eRuntimeError, "tag tuple must be of length 2"); | |
+ } | |
+ name = RARRAY_PTR(tuple)[0]; | |
+ value = RARRAY_PTR(tuple)[1]; | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ name = rb_str_export_to_enc(name, encoding); | |
+ value = rb_str_export_to_enc(value, encoding); | |
+#endif | |
+ | |
+ tail->handle = (yaml_char_t *)StringValuePtr(name); | |
+ tail->prefix = (yaml_char_t *)StringValuePtr(value); | |
+ | |
+ tail++; | |
+ } | |
+ } | |
+ | |
+ yaml_document_start_event_initialize( | |
+ &event, | |
+ (RARRAY_LEN(version) > 0) ? &version_directive : NULL, | |
+ head, | |
+ tail, | |
+ imp ? 1 : 0 | |
+ ); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ if(head) xfree(head); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.end_document(implicit) | |
+ * | |
+ * End a document emission with an +implicit+ ending. | |
+ * | |
+ * See Psych::Handler#end_document | |
+ */ | |
+static VALUE end_document(VALUE self, VALUE imp) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_document_end_event_initialize(&event, imp ? 1 : 0); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.scalar(value, anchor, tag, plain, quoted, style) | |
+ * | |
+ * Emit a scalar with +value+, +anchor+, +tag+, and a +plain+ or +quoted+ | |
+ * string type with +style+. | |
+ * | |
+ * See Psych::Handler#scalar | |
+ */ | |
+static VALUE scalar( | |
+ VALUE self, | |
+ VALUE value, | |
+ VALUE anchor, | |
+ VALUE tag, | |
+ VALUE plain, | |
+ VALUE quoted, | |
+ VALUE style | |
+ ) { | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ rb_encoding *encoding; | |
+#endif | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ Check_Type(value, T_STRING); | |
+ | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ encoding = rb_utf8_encoding(); | |
+ | |
+ value = rb_str_export_to_enc(value, encoding); | |
+ | |
+ if(!NIL_P(anchor)) { | |
+ Check_Type(anchor, T_STRING); | |
+ anchor = rb_str_export_to_enc(anchor, encoding); | |
+ } | |
+ | |
+ if(!NIL_P(tag)) { | |
+ Check_Type(tag, T_STRING); | |
+ tag = rb_str_export_to_enc(tag, encoding); | |
+ } | |
+#endif | |
+ | |
+ yaml_scalar_event_initialize( | |
+ &event, | |
+ (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)), | |
+ (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)), | |
+ (yaml_char_t*)StringValuePtr(value), | |
+ (int)RSTRING_LEN(value), | |
+ plain ? 1 : 0, | |
+ quoted ? 1 : 0, | |
+ (yaml_scalar_style_t)NUM2INT(style) | |
+ ); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.start_sequence(anchor, tag, implicit, style) | |
+ * | |
+ * Start emitting a sequence with +anchor+, a +tag+, +implicit+ sequence | |
+ * start and end, along with +style+. | |
+ * | |
+ * See Psych::Handler#start_sequence | |
+ */ | |
+static VALUE start_sequence( | |
+ VALUE self, | |
+ VALUE anchor, | |
+ VALUE tag, | |
+ VALUE implicit, | |
+ VALUE style | |
+ ) { | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+ | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ rb_encoding * encoding = rb_utf8_encoding(); | |
+ | |
+ if(!NIL_P(anchor)) { | |
+ Check_Type(anchor, T_STRING); | |
+ anchor = rb_str_export_to_enc(anchor, encoding); | |
+ } | |
+ | |
+ if(!NIL_P(tag)) { | |
+ Check_Type(tag, T_STRING); | |
+ tag = rb_str_export_to_enc(tag, encoding); | |
+ } | |
+#endif | |
+ | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_sequence_start_event_initialize( | |
+ &event, | |
+ (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)), | |
+ (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)), | |
+ implicit ? 1 : 0, | |
+ (yaml_sequence_style_t)NUM2INT(style) | |
+ ); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.end_sequence | |
+ * | |
+ * End sequence emission. | |
+ * | |
+ * See Psych::Handler#end_sequence | |
+ */ | |
+static VALUE end_sequence(VALUE self) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_sequence_end_event_initialize(&event); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.start_mapping(anchor, tag, implicit, style) | |
+ * | |
+ * Start emitting a YAML map with +anchor+, +tag+, an +implicit+ start | |
+ * and end, and +style+. | |
+ * | |
+ * See Psych::Handler#start_mapping | |
+ */ | |
+static VALUE start_mapping( | |
+ VALUE self, | |
+ VALUE anchor, | |
+ VALUE tag, | |
+ VALUE implicit, | |
+ VALUE style | |
+ ) { | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ rb_encoding *encoding; | |
+#endif | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ encoding = rb_utf8_encoding(); | |
+ | |
+ if(!NIL_P(anchor)) { | |
+ Check_Type(anchor, T_STRING); | |
+ anchor = rb_str_export_to_enc(anchor, encoding); | |
+ } | |
+ | |
+ if(!NIL_P(tag)) { | |
+ Check_Type(tag, T_STRING); | |
+ tag = rb_str_export_to_enc(tag, encoding); | |
+ } | |
+#endif | |
+ | |
+ yaml_mapping_start_event_initialize( | |
+ &event, | |
+ (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)), | |
+ (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)), | |
+ implicit ? 1 : 0, | |
+ (yaml_mapping_style_t)NUM2INT(style) | |
+ ); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.end_mapping | |
+ * | |
+ * Emit the end of a mapping. | |
+ * | |
+ * See Psych::Handler#end_mapping | |
+ */ | |
+static VALUE end_mapping(VALUE self) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_mapping_end_event_initialize(&event); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.alias(anchor) | |
+ * | |
+ * Emit an alias with +anchor+. | |
+ * | |
+ * See Psych::Handler#alias | |
+ */ | |
+static VALUE alias(VALUE self, VALUE anchor) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ yaml_event_t event; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ if(!NIL_P(anchor)) { | |
+ Check_Type(anchor, T_STRING); | |
+ anchor = rb_str_export_to_enc(anchor, rb_utf8_encoding()); | |
+ } | |
+#endif | |
+ | |
+ yaml_alias_event_initialize( | |
+ &event, | |
+ (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)) | |
+ ); | |
+ | |
+ emit(emitter, &event); | |
+ | |
+ return self; | |
+} | |
+ | |
+/* call-seq: emitter.canonical = true | |
+ * | |
+ * Set the output style to canonical, or not. | |
+ */ | |
+static VALUE set_canonical(VALUE self, VALUE style) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_emitter_set_canonical(emitter, Qtrue == style ? 1 : 0); | |
+ | |
+ return style; | |
+} | |
+ | |
+/* call-seq: emitter.canonical | |
+ * | |
+ * Get the output style, canonical or not. | |
+ */ | |
+static VALUE canonical(VALUE self) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ return (emitter->canonical == 0) ? Qfalse : Qtrue; | |
+} | |
+ | |
+/* call-seq: emitter.indentation = level | |
+ * | |
+ * Set the indentation level to +level+. The level must be less than 10 and | |
+ * greater than 1. | |
+ */ | |
+static VALUE set_indentation(VALUE self, VALUE level) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_emitter_set_indent(emitter, NUM2INT(level)); | |
+ | |
+ return level; | |
+} | |
+ | |
+/* call-seq: emitter.indentation | |
+ * | |
+ * Get the indentation level. | |
+ */ | |
+static VALUE indentation(VALUE self) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ return INT2NUM(emitter->best_indent); | |
+} | |
+ | |
+/* call-seq: emitter.line_width | |
+ * | |
+ * Get the preferred line width. | |
+ */ | |
+static VALUE line_width(VALUE self) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ return INT2NUM(emitter->best_width); | |
+} | |
+ | |
+/* call-seq: emitter.line_width = width | |
+ * | |
+ * Set the preferred line with to +width+. | |
+ */ | |
+static VALUE set_line_width(VALUE self, VALUE width) | |
+{ | |
+ yaml_emitter_t * emitter; | |
+ Data_Get_Struct(self, yaml_emitter_t, emitter); | |
+ | |
+ yaml_emitter_set_width(emitter, NUM2INT(width)); | |
+ | |
+ return width; | |
+} | |
+ | |
+void Init_psych_emitter() | |
+{ | |
+ VALUE psych = rb_define_module("Psych"); | |
+ VALUE handler = rb_define_class_under(psych, "Handler", rb_cObject); | |
+ cPsychEmitter = rb_define_class_under(psych, "Emitter", handler); | |
+ | |
+ rb_define_alloc_func(cPsychEmitter, allocate); | |
+ | |
+ rb_define_method(cPsychEmitter, "initialize", initialize, -1); | |
+ rb_define_method(cPsychEmitter, "start_stream", start_stream, 1); | |
+ rb_define_method(cPsychEmitter, "end_stream", end_stream, 0); | |
+ rb_define_method(cPsychEmitter, "start_document", start_document, 3); | |
+ rb_define_method(cPsychEmitter, "end_document", end_document, 1); | |
+ rb_define_method(cPsychEmitter, "scalar", scalar, 6); | |
+ rb_define_method(cPsychEmitter, "start_sequence", start_sequence, 4); | |
+ rb_define_method(cPsychEmitter, "end_sequence", end_sequence, 0); | |
+ rb_define_method(cPsychEmitter, "start_mapping", start_mapping, 4); | |
+ rb_define_method(cPsychEmitter, "end_mapping", end_mapping, 0); | |
+ rb_define_method(cPsychEmitter, "alias", alias, 1); | |
+ rb_define_method(cPsychEmitter, "canonical", canonical, 0); | |
+ rb_define_method(cPsychEmitter, "canonical=", set_canonical, 1); | |
+ rb_define_method(cPsychEmitter, "indentation", indentation, 0); | |
+ rb_define_method(cPsychEmitter, "indentation=", set_indentation, 1); | |
+ rb_define_method(cPsychEmitter, "line_width", line_width, 0); | |
+ rb_define_method(cPsychEmitter, "line_width=", set_line_width, 1); | |
+ | |
+ id_write = rb_intern("write"); | |
+ id_line_width = rb_intern("line_width"); | |
+ id_indentation = rb_intern("indentation"); | |
+ id_canonical = rb_intern("canonical"); | |
+} | |
+/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/psych_emitter.h b/ext/psych/psych_emitter.h | |
new file mode 100644 | |
index 0000000..560451e | |
--- /dev/null | |
+++ b/ext/psych/psych_emitter.h | |
@@ -0,0 +1,8 @@ | |
+#ifndef PSYCH_EMITTER_H | |
+#define PSYCH_EMITTER_H | |
+ | |
+#include <psych.h> | |
+ | |
+void Init_psych_emitter(); | |
+ | |
+#endif | |
diff --git a/ext/psych/psych_parser.c b/ext/psych/psych_parser.c | |
new file mode 100644 | |
index 0000000..8c65ce1 | |
--- /dev/null | |
+++ b/ext/psych/psych_parser.c | |
@@ -0,0 +1,579 @@ | |
+#include <psych.h> | |
+ | |
+VALUE cPsychParser; | |
+VALUE ePsychSyntaxError; | |
+ | |
+static ID id_read; | |
+static ID id_path; | |
+static ID id_empty; | |
+static ID id_start_stream; | |
+static ID id_end_stream; | |
+static ID id_start_document; | |
+static ID id_end_document; | |
+static ID id_alias; | |
+static ID id_scalar; | |
+static ID id_start_sequence; | |
+static ID id_end_sequence; | |
+static ID id_start_mapping; | |
+static ID id_end_mapping; | |
+ | |
+#define PSYCH_TRANSCODE(_str, _yaml_enc, _internal_enc) \ | |
+ do { \ | |
+ rb_enc_associate_index((_str), (_yaml_enc)); \ | |
+ if(_internal_enc) \ | |
+ (_str) = rb_str_export_to_enc((_str), (_internal_enc)); \ | |
+ } while (0) | |
+ | |
+static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read) | |
+{ | |
+ VALUE io = (VALUE)data; | |
+ VALUE string = rb_funcall(io, id_read, 1, INT2NUM(size)); | |
+ | |
+ *read = 0; | |
+ | |
+ if(! NIL_P(string)) { | |
+ void * str = (void *)StringValuePtr(string); | |
+ *read = (size_t)RSTRING_LEN(string); | |
+ memcpy(buf, str, *read); | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+static void dealloc(void * ptr) | |
+{ | |
+ yaml_parser_t * parser; | |
+ | |
+ parser = (yaml_parser_t *)ptr; | |
+ yaml_parser_delete(parser); | |
+ xfree(parser); | |
+} | |
+ | |
+static VALUE allocate(VALUE klass) | |
+{ | |
+ yaml_parser_t * parser; | |
+ | |
+ parser = xmalloc(sizeof(yaml_parser_t)); | |
+ yaml_parser_initialize(parser); | |
+ | |
+ return Data_Wrap_Struct(klass, 0, dealloc, parser); | |
+} | |
+ | |
+static VALUE make_exception(yaml_parser_t * parser, VALUE path) | |
+{ | |
+ size_t line, column; | |
+ | |
+ line = parser->context_mark.line + 1; | |
+ column = parser->context_mark.column + 1; | |
+ | |
+ return rb_funcall(ePsychSyntaxError, rb_intern("new"), 6, | |
+ path, | |
+ INT2NUM(line), | |
+ INT2NUM(column), | |
+ INT2NUM(parser->problem_offset), | |
+ parser->problem ? rb_usascii_str_new2(parser->problem) : Qnil, | |
+ parser->context ? rb_usascii_str_new2(parser->context) : Qnil); | |
+} | |
+ | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+static VALUE transcode_string(VALUE src, int * parser_encoding) | |
+{ | |
+ int utf8 = rb_utf8_encindex(); | |
+ int utf16le = rb_enc_find_index("UTF-16LE"); | |
+ int utf16be = rb_enc_find_index("UTF-16BE"); | |
+ int source_encoding = rb_enc_get_index(src); | |
+ | |
+ if (source_encoding == utf8) { | |
+ *parser_encoding = YAML_UTF8_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ if (source_encoding == utf16le) { | |
+ *parser_encoding = YAML_UTF16LE_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ if (source_encoding == utf16be) { | |
+ *parser_encoding = YAML_UTF16BE_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ src = rb_str_export_to_enc(src, rb_utf8_encoding()); | |
+ RB_GC_GUARD(src); | |
+ | |
+ *parser_encoding = YAML_UTF8_ENCODING; | |
+ return src; | |
+} | |
+ | |
+static VALUE transcode_io(VALUE src, int * parser_encoding) | |
+{ | |
+ VALUE io_external_encoding; | |
+ int io_external_enc_index; | |
+ | |
+ io_external_encoding = rb_funcall(src, rb_intern("external_encoding"), 0); | |
+ | |
+ /* if no encoding is returned, assume ascii8bit. */ | |
+ if (NIL_P(io_external_encoding)) { | |
+ io_external_enc_index = rb_ascii8bit_encindex(); | |
+ } else { | |
+ io_external_enc_index = rb_to_encoding_index(io_external_encoding); | |
+ } | |
+ | |
+ /* Treat US-ASCII as utf_8 */ | |
+ if (io_external_enc_index == rb_usascii_encindex()) { | |
+ *parser_encoding = YAML_UTF8_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ if (io_external_enc_index == rb_utf8_encindex()) { | |
+ *parser_encoding = YAML_UTF8_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ if (io_external_enc_index == rb_enc_find_index("UTF-16LE")) { | |
+ *parser_encoding = YAML_UTF16LE_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ if (io_external_enc_index == rb_enc_find_index("UTF-16BE")) { | |
+ *parser_encoding = YAML_UTF16BE_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ /* Just guess on ASCII-8BIT */ | |
+ if (io_external_enc_index == rb_ascii8bit_encindex()) { | |
+ *parser_encoding = YAML_ANY_ENCODING; | |
+ return src; | |
+ } | |
+ | |
+ /* If the external encoding is something we don't know how to handle, | |
+ * fall back to YAML_ANY_ENCODING. */ | |
+ *parser_encoding = YAML_ANY_ENCODING; | |
+ | |
+ return src; | |
+} | |
+ | |
+#endif | |
+ | |
+static VALUE protected_start_stream(VALUE pointer) | |
+{ | |
+ VALUE *args = (VALUE *)pointer; | |
+ return rb_funcall(args[0], id_start_stream, 1, args[1]); | |
+} | |
+ | |
+static VALUE protected_start_document(VALUE pointer) | |
+{ | |
+ VALUE *args = (VALUE *)pointer; | |
+ return rb_funcall3(args[0], id_start_document, 3, args + 1); | |
+} | |
+ | |
+static VALUE protected_end_document(VALUE pointer) | |
+{ | |
+ VALUE *args = (VALUE *)pointer; | |
+ return rb_funcall(args[0], id_end_document, 1, args[1]); | |
+} | |
+ | |
+static VALUE protected_alias(VALUE pointer) | |
+{ | |
+ VALUE *args = (VALUE *)pointer; | |
+ return rb_funcall(args[0], id_alias, 1, args[1]); | |
+} | |
+ | |
+static VALUE protected_scalar(VALUE pointer) | |
+{ | |
+ VALUE *args = (VALUE *)pointer; | |
+ return rb_funcall3(args[0], id_scalar, 6, args + 1); | |
+} | |
+ | |
+static VALUE protected_start_sequence(VALUE pointer) | |
+{ | |
+ VALUE *args = (VALUE *)pointer; | |
+ return rb_funcall3(args[0], id_start_sequence, 4, args + 1); | |
+} | |
+ | |
+static VALUE protected_end_sequence(VALUE handler) | |
+{ | |
+ return rb_funcall(handler, id_end_sequence, 0); | |
+} | |
+ | |
+static VALUE protected_start_mapping(VALUE pointer) | |
+{ | |
+ VALUE *args = (VALUE *)pointer; | |
+ return rb_funcall3(args[0], id_start_mapping, 4, args + 1); | |
+} | |
+ | |
+static VALUE protected_end_mapping(VALUE handler) | |
+{ | |
+ return rb_funcall(handler, id_end_mapping, 0); | |
+} | |
+ | |
+static VALUE protected_empty(VALUE handler) | |
+{ | |
+ return rb_funcall(handler, id_empty, 0); | |
+} | |
+ | |
+static VALUE protected_end_stream(VALUE handler) | |
+{ | |
+ return rb_funcall(handler, id_end_stream, 0); | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * parser.parse(yaml) | |
+ * | |
+ * Parse the YAML document contained in +yaml+. Events will be called on | |
+ * the handler set on the parser instance. | |
+ * | |
+ * See Psych::Parser and Psych::Parser#handler | |
+ */ | |
+static VALUE parse(int argc, VALUE *argv, VALUE self) | |
+{ | |
+ VALUE yaml, path; | |
+ yaml_parser_t * parser; | |
+ yaml_event_t event; | |
+ int done = 0; | |
+ int tainted = 0; | |
+ int state = 0; | |
+ int parser_encoding = YAML_ANY_ENCODING; | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ int encoding = rb_utf8_encindex(); | |
+ rb_encoding * internal_enc = rb_default_internal_encoding(); | |
+#endif | |
+ VALUE handler = rb_iv_get(self, "@handler"); | |
+ | |
+ if (rb_scan_args(argc, argv, "11", &yaml, &path) == 1) { | |
+ if(rb_respond_to(yaml, id_path)) | |
+ path = rb_funcall(yaml, id_path, 0); | |
+ else | |
+ path = rb_str_new2("<unknown>"); | |
+ } | |
+ | |
+ Data_Get_Struct(self, yaml_parser_t, parser); | |
+ | |
+ yaml_parser_delete(parser); | |
+ yaml_parser_initialize(parser); | |
+ | |
+ if (OBJ_TAINTED(yaml)) tainted = 1; | |
+ | |
+ if (rb_respond_to(yaml, id_read)) { | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ yaml = transcode_io(yaml, &parser_encoding); | |
+ yaml_parser_set_encoding(parser, parser_encoding); | |
+#endif | |
+ yaml_parser_set_input(parser, io_reader, (void *)yaml); | |
+ if (RTEST(rb_obj_is_kind_of(yaml, rb_cIO))) tainted = 1; | |
+ } else { | |
+ StringValue(yaml); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ yaml = transcode_string(yaml, &parser_encoding); | |
+ yaml_parser_set_encoding(parser, parser_encoding); | |
+#endif | |
+ yaml_parser_set_input_string( | |
+ parser, | |
+ (const unsigned char *)RSTRING_PTR(yaml), | |
+ (size_t)RSTRING_LEN(yaml) | |
+ ); | |
+ } | |
+ | |
+ while(!done) { | |
+ if(!yaml_parser_parse(parser, &event)) { | |
+ VALUE exception; | |
+ | |
+ exception = make_exception(parser, path); | |
+ yaml_parser_delete(parser); | |
+ yaml_parser_initialize(parser); | |
+ | |
+ rb_exc_raise(exception); | |
+ } | |
+ | |
+ switch(event.type) { | |
+ case YAML_STREAM_START_EVENT: | |
+ { | |
+ VALUE args[2]; | |
+ | |
+ args[0] = handler; | |
+ args[1] = INT2NUM((long)event.data.stream_start.encoding); | |
+ rb_protect(protected_start_stream, (VALUE)args, &state); | |
+ } | |
+ break; | |
+ case YAML_DOCUMENT_START_EVENT: | |
+ { | |
+ VALUE args[4]; | |
+ /* Get a list of tag directives (if any) */ | |
+ VALUE tag_directives = rb_ary_new(); | |
+ /* Grab the document version */ | |
+ VALUE version = event.data.document_start.version_directive ? | |
+ rb_ary_new3( | |
+ (long)2, | |
+ INT2NUM((long)event.data.document_start.version_directive->major), | |
+ INT2NUM((long)event.data.document_start.version_directive->minor) | |
+ ) : rb_ary_new(); | |
+ | |
+ if(event.data.document_start.tag_directives.start) { | |
+ yaml_tag_directive_t *start = | |
+ event.data.document_start.tag_directives.start; | |
+ yaml_tag_directive_t *end = | |
+ event.data.document_start.tag_directives.end; | |
+ for(; start != end; start++) { | |
+ VALUE handle = Qnil; | |
+ VALUE prefix = Qnil; | |
+ if(start->handle) { | |
+ handle = rb_str_new2((const char *)start->handle); | |
+ if (tainted) OBJ_TAINT(handle); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(handle, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ if(start->prefix) { | |
+ prefix = rb_str_new2((const char *)start->prefix); | |
+ if (tainted) OBJ_TAINT(prefix); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(prefix, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ rb_ary_push(tag_directives, rb_ary_new3((long)2, handle, prefix)); | |
+ } | |
+ } | |
+ args[0] = handler; | |
+ args[1] = version; | |
+ args[2] = tag_directives; | |
+ args[3] = event.data.document_start.implicit == 1 ? Qtrue : Qfalse; | |
+ rb_protect(protected_start_document, (VALUE)args, &state); | |
+ } | |
+ break; | |
+ case YAML_DOCUMENT_END_EVENT: | |
+ { | |
+ VALUE args[2]; | |
+ | |
+ args[0] = handler; | |
+ args[1] = event.data.document_end.implicit == 1 ? Qtrue : Qfalse; | |
+ rb_protect(protected_end_document, (VALUE)args, &state); | |
+ } | |
+ break; | |
+ case YAML_ALIAS_EVENT: | |
+ { | |
+ VALUE args[2]; | |
+ VALUE alias = Qnil; | |
+ if(event.data.alias.anchor) { | |
+ alias = rb_str_new2((const char *)event.data.alias.anchor); | |
+ if (tainted) OBJ_TAINT(alias); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(alias, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ args[0] = handler; | |
+ args[1] = alias; | |
+ rb_protect(protected_alias, (VALUE)args, &state); | |
+ } | |
+ break; | |
+ case YAML_SCALAR_EVENT: | |
+ { | |
+ VALUE args[7]; | |
+ VALUE anchor = Qnil; | |
+ VALUE tag = Qnil; | |
+ VALUE plain_implicit, quoted_implicit, style; | |
+ VALUE val = rb_str_new( | |
+ (const char *)event.data.scalar.value, | |
+ (long)event.data.scalar.length | |
+ ); | |
+ if (tainted) OBJ_TAINT(val); | |
+ | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(val, encoding, internal_enc); | |
+#endif | |
+ | |
+ if(event.data.scalar.anchor) { | |
+ anchor = rb_str_new2((const char *)event.data.scalar.anchor); | |
+ if (tainted) OBJ_TAINT(anchor); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(anchor, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ if(event.data.scalar.tag) { | |
+ tag = rb_str_new2((const char *)event.data.scalar.tag); | |
+ if (tainted) OBJ_TAINT(tag); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(tag, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ plain_implicit = | |
+ event.data.scalar.plain_implicit == 0 ? Qfalse : Qtrue; | |
+ | |
+ quoted_implicit = | |
+ event.data.scalar.quoted_implicit == 0 ? Qfalse : Qtrue; | |
+ | |
+ style = INT2NUM((long)event.data.scalar.style); | |
+ | |
+ args[0] = handler; | |
+ args[1] = val; | |
+ args[2] = anchor; | |
+ args[3] = tag; | |
+ args[4] = plain_implicit; | |
+ args[5] = quoted_implicit; | |
+ args[6] = style; | |
+ rb_protect(protected_scalar, (VALUE)args, &state); | |
+ } | |
+ break; | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ { | |
+ VALUE args[5]; | |
+ VALUE anchor = Qnil; | |
+ VALUE tag = Qnil; | |
+ VALUE implicit, style; | |
+ if(event.data.sequence_start.anchor) { | |
+ anchor = rb_str_new2((const char *)event.data.sequence_start.anchor); | |
+ if (tainted) OBJ_TAINT(anchor); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(anchor, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ tag = Qnil; | |
+ if(event.data.sequence_start.tag) { | |
+ tag = rb_str_new2((const char *)event.data.sequence_start.tag); | |
+ if (tainted) OBJ_TAINT(tag); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(tag, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ implicit = | |
+ event.data.sequence_start.implicit == 0 ? Qfalse : Qtrue; | |
+ | |
+ style = INT2NUM((long)event.data.sequence_start.style); | |
+ | |
+ args[0] = handler; | |
+ args[1] = anchor; | |
+ args[2] = tag; | |
+ args[3] = implicit; | |
+ args[4] = style; | |
+ | |
+ rb_protect(protected_start_sequence, (VALUE)args, &state); | |
+ } | |
+ break; | |
+ case YAML_SEQUENCE_END_EVENT: | |
+ rb_protect(protected_end_sequence, handler, &state); | |
+ break; | |
+ case YAML_MAPPING_START_EVENT: | |
+ { | |
+ VALUE args[5]; | |
+ VALUE anchor = Qnil; | |
+ VALUE tag = Qnil; | |
+ VALUE implicit, style; | |
+ if(event.data.mapping_start.anchor) { | |
+ anchor = rb_str_new2((const char *)event.data.mapping_start.anchor); | |
+ if (tainted) OBJ_TAINT(anchor); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(anchor, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ if(event.data.mapping_start.tag) { | |
+ tag = rb_str_new2((const char *)event.data.mapping_start.tag); | |
+ if (tainted) OBJ_TAINT(tag); | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ PSYCH_TRANSCODE(tag, encoding, internal_enc); | |
+#endif | |
+ } | |
+ | |
+ implicit = | |
+ event.data.mapping_start.implicit == 0 ? Qfalse : Qtrue; | |
+ | |
+ style = INT2NUM((long)event.data.mapping_start.style); | |
+ | |
+ args[0] = handler; | |
+ args[1] = anchor; | |
+ args[2] = tag; | |
+ args[3] = implicit; | |
+ args[4] = style; | |
+ | |
+ rb_protect(protected_start_mapping, (VALUE)args, &state); | |
+ } | |
+ break; | |
+ case YAML_MAPPING_END_EVENT: | |
+ rb_protect(protected_end_mapping, handler, &state); | |
+ break; | |
+ case YAML_NO_EVENT: | |
+ rb_protect(protected_empty, handler, &state); | |
+ break; | |
+ case YAML_STREAM_END_EVENT: | |
+ rb_protect(protected_end_stream, handler, &state); | |
+ done = 1; | |
+ break; | |
+ } | |
+ yaml_event_delete(&event); | |
+ if (state) rb_jump_tag(state); | |
+ } | |
+ | |
+ return self; | |
+} | |
+ | |
+/* | |
+ * call-seq: | |
+ * parser.mark # => #<Psych::Parser::Mark> | |
+ * | |
+ * Returns a Psych::Parser::Mark object that contains line, column, and index | |
+ * information. | |
+ */ | |
+static VALUE mark(VALUE self) | |
+{ | |
+ VALUE mark_klass; | |
+ VALUE args[3]; | |
+ yaml_parser_t * parser; | |
+ | |
+ Data_Get_Struct(self, yaml_parser_t, parser); | |
+ mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark")); | |
+ args[0] = INT2NUM(parser->mark.index); | |
+ args[1] = INT2NUM(parser->mark.line); | |
+ args[2] = INT2NUM(parser->mark.column); | |
+ | |
+ return rb_class_new_instance(3, args, mark_klass); | |
+} | |
+ | |
+void Init_psych_parser() | |
+{ | |
+#if 0 | |
+ mPsych = rb_define_module("Psych"); | |
+#endif | |
+ | |
+ cPsychParser = rb_define_class_under(mPsych, "Parser", rb_cObject); | |
+ rb_define_alloc_func(cPsychParser, allocate); | |
+ | |
+ /* Any encoding: Let the parser choose the encoding */ | |
+ rb_define_const(cPsychParser, "ANY", INT2NUM(YAML_ANY_ENCODING)); | |
+ | |
+ /* UTF-8 Encoding */ | |
+ rb_define_const(cPsychParser, "UTF8", INT2NUM(YAML_UTF8_ENCODING)); | |
+ | |
+ /* UTF-16-LE Encoding with BOM */ | |
+ rb_define_const(cPsychParser, "UTF16LE", INT2NUM(YAML_UTF16LE_ENCODING)); | |
+ | |
+ /* UTF-16-BE Encoding with BOM */ | |
+ rb_define_const(cPsychParser, "UTF16BE", INT2NUM(YAML_UTF16BE_ENCODING)); | |
+ | |
+ rb_require("psych/syntax_error"); | |
+ ePsychSyntaxError = rb_const_get(mPsych, rb_intern("SyntaxError")); | |
+ | |
+ rb_define_method(cPsychParser, "parse", parse, -1); | |
+ rb_define_method(cPsychParser, "mark", mark, 0); | |
+ | |
+ id_read = rb_intern("read"); | |
+ id_path = rb_intern("path"); | |
+ id_empty = rb_intern("empty"); | |
+ id_start_stream = rb_intern("start_stream"); | |
+ id_end_stream = rb_intern("end_stream"); | |
+ id_start_document = rb_intern("start_document"); | |
+ id_end_document = rb_intern("end_document"); | |
+ id_alias = rb_intern("alias"); | |
+ id_scalar = rb_intern("scalar"); | |
+ id_start_sequence = rb_intern("start_sequence"); | |
+ id_end_sequence = rb_intern("end_sequence"); | |
+ id_start_mapping = rb_intern("start_mapping"); | |
+ id_end_mapping = rb_intern("end_mapping"); | |
+} | |
+/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/psych_parser.h b/ext/psych/psych_parser.h | |
new file mode 100644 | |
index 0000000..25e896f | |
--- /dev/null | |
+++ b/ext/psych/psych_parser.h | |
@@ -0,0 +1,6 @@ | |
+#ifndef PSYCH_PARSER_H | |
+#define PSYCH_PARSER_H | |
+ | |
+void Init_psych_parser(); | |
+ | |
+#endif | |
diff --git a/ext/psych/psych_to_ruby.c b/ext/psych/psych_to_ruby.c | |
new file mode 100644 | |
index 0000000..ed5245e | |
--- /dev/null | |
+++ b/ext/psych/psych_to_ruby.c | |
@@ -0,0 +1,41 @@ | |
+#include <psych.h> | |
+ | |
+VALUE cPsychVisitorsToRuby; | |
+ | |
+/* call-seq: vis.build_exception(klass, message) | |
+ * | |
+ * Create an exception with class +klass+ and +message+ | |
+ */ | |
+static VALUE build_exception(VALUE self, VALUE klass, VALUE mesg) | |
+{ | |
+ VALUE e = rb_obj_alloc(klass); | |
+ | |
+ rb_iv_set(e, "mesg", mesg); | |
+ | |
+ return e; | |
+} | |
+ | |
+/* call-seq: vis.path2class(path) | |
+ * | |
+ * Convert +path+ string to a class | |
+ */ | |
+static VALUE path2class(VALUE self, VALUE path) | |
+{ | |
+#ifdef HAVE_RUBY_ENCODING_H | |
+ return rb_path_to_class(path); | |
+#else | |
+ return rb_path2class(StringValuePtr(path)); | |
+#endif | |
+} | |
+ | |
+void Init_psych_to_ruby(void) | |
+{ | |
+ VALUE psych = rb_define_module("Psych"); | |
+ VALUE visitors = rb_define_module_under(psych, "Visitors"); | |
+ VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); | |
+ cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor); | |
+ | |
+ rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2); | |
+ rb_define_private_method(cPsychVisitorsToRuby, "path2class", path2class, 1); | |
+} | |
+/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/psych_to_ruby.h b/ext/psych/psych_to_ruby.h | |
new file mode 100644 | |
index 0000000..7b8e757 | |
--- /dev/null | |
+++ b/ext/psych/psych_to_ruby.h | |
@@ -0,0 +1,8 @@ | |
+#ifndef PSYCH_TO_RUBY_H | |
+#define PSYCH_TO_RUBY_H | |
+ | |
+#include <psych.h> | |
+ | |
+void Init_psych_to_ruby(void); | |
+ | |
+#endif | |
diff --git a/ext/psych/psych_yaml_tree.c b/ext/psych/psych_yaml_tree.c | |
new file mode 100644 | |
index 0000000..bcf24d2 | |
--- /dev/null | |
+++ b/ext/psych/psych_yaml_tree.c | |
@@ -0,0 +1,24 @@ | |
+#include <psych.h> | |
+ | |
+VALUE cPsychVisitorsYamlTree; | |
+ | |
+/* | |
+ * call-seq: private_iv_get(target, prop) | |
+ * | |
+ * Get the private instance variable +prop+ from +target+ | |
+ */ | |
+static VALUE private_iv_get(VALUE self, VALUE target, VALUE prop) | |
+{ | |
+ return rb_attr_get(target, rb_intern(StringValuePtr(prop))); | |
+} | |
+ | |
+void Init_psych_yaml_tree(void) | |
+{ | |
+ VALUE psych = rb_define_module("Psych"); | |
+ VALUE visitors = rb_define_module_under(psych, "Visitors"); | |
+ VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); | |
+ cPsychVisitorsYamlTree = rb_define_class_under(visitors, "YAMLTree", visitor); | |
+ | |
+ rb_define_private_method(cPsychVisitorsYamlTree, "private_iv_get", private_iv_get, 2); | |
+} | |
+/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/psych_yaml_tree.h b/ext/psych/psych_yaml_tree.h | |
new file mode 100644 | |
index 0000000..4628a69 | |
--- /dev/null | |
+++ b/ext/psych/psych_yaml_tree.h | |
@@ -0,0 +1,8 @@ | |
+#ifndef PSYCH_YAML_TREE_H | |
+#define PSYCH_YAML_TREE_H | |
+ | |
+#include <psych.h> | |
+ | |
+void Init_psych_yaml_tree(void); | |
+ | |
+#endif | |
diff --git a/ext/psych/to_ruby.c b/ext/psych/to_ruby.c | |
deleted file mode 100644 | |
index ed5245e..0000000 | |
--- a/ext/psych/to_ruby.c | |
+++ /dev/null | |
@@ -1,41 +0,0 @@ | |
-#include <psych.h> | |
- | |
-VALUE cPsychVisitorsToRuby; | |
- | |
-/* call-seq: vis.build_exception(klass, message) | |
- * | |
- * Create an exception with class +klass+ and +message+ | |
- */ | |
-static VALUE build_exception(VALUE self, VALUE klass, VALUE mesg) | |
-{ | |
- VALUE e = rb_obj_alloc(klass); | |
- | |
- rb_iv_set(e, "mesg", mesg); | |
- | |
- return e; | |
-} | |
- | |
-/* call-seq: vis.path2class(path) | |
- * | |
- * Convert +path+ string to a class | |
- */ | |
-static VALUE path2class(VALUE self, VALUE path) | |
-{ | |
-#ifdef HAVE_RUBY_ENCODING_H | |
- return rb_path_to_class(path); | |
-#else | |
- return rb_path2class(StringValuePtr(path)); | |
-#endif | |
-} | |
- | |
-void Init_psych_to_ruby(void) | |
-{ | |
- VALUE psych = rb_define_module("Psych"); | |
- VALUE visitors = rb_define_module_under(psych, "Visitors"); | |
- VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); | |
- cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor); | |
- | |
- rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2); | |
- rb_define_private_method(cPsychVisitorsToRuby, "path2class", path2class, 1); | |
-} | |
-/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/to_ruby.h b/ext/psych/to_ruby.h | |
deleted file mode 100644 | |
index 7b8e757..0000000 | |
--- a/ext/psych/to_ruby.h | |
+++ /dev/null | |
@@ -1,8 +0,0 @@ | |
-#ifndef PSYCH_TO_RUBY_H | |
-#define PSYCH_TO_RUBY_H | |
- | |
-#include <psych.h> | |
- | |
-void Init_psych_to_ruby(void); | |
- | |
-#endif | |
diff --git a/ext/psych/yaml/LICENSE b/ext/psych/yaml/LICENSE | |
new file mode 100644 | |
index 0000000..050ced2 | |
--- /dev/null | |
+++ b/ext/psych/yaml/LICENSE | |
@@ -0,0 +1,19 @@ | |
+Copyright (c) 2006 Kirill Simonov | |
+ | |
+Permission is hereby granted, free of charge, to any person obtaining a copy of | |
+this software and associated documentation files (the "Software"), to deal in | |
+the Software without restriction, including without limitation the rights to | |
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |
+of the Software, and to permit persons to whom the Software is furnished to do | |
+so, subject to the following conditions: | |
+ | |
+The above copyright notice and this permission notice shall be included in all | |
+copies or substantial portions of the Software. | |
+ | |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+SOFTWARE. | |
diff --git a/ext/psych/yaml/api.c b/ext/psych/yaml/api.c | |
new file mode 100644 | |
index 0000000..3b393cf | |
--- /dev/null | |
+++ b/ext/psych/yaml/api.c | |
@@ -0,0 +1,1390 @@ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * Get the library version. | |
+ */ | |
+ | |
+YAML_DECLARE(const char *) | |
+yaml_get_version_string(void) | |
+{ | |
+ return YAML_VERSION_STRING; | |
+} | |
+ | |
+/* | |
+ * Get the library version numbers. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_get_version(int *major, int *minor, int *patch) | |
+{ | |
+ *major = YAML_VERSION_MAJOR; | |
+ *minor = YAML_VERSION_MINOR; | |
+ *patch = YAML_VERSION_PATCH; | |
+} | |
+ | |
+/* | |
+ * Allocate a dynamic memory block. | |
+ */ | |
+ | |
+YAML_DECLARE(void *) | |
+yaml_malloc(size_t size) | |
+{ | |
+ return malloc(size ? size : 1); | |
+} | |
+ | |
+/* | |
+ * Reallocate a dynamic memory block. | |
+ */ | |
+ | |
+YAML_DECLARE(void *) | |
+yaml_realloc(void *ptr, size_t size) | |
+{ | |
+ return ptr ? realloc(ptr, size ? size : 1) : malloc(size ? size : 1); | |
+} | |
+ | |
+/* | |
+ * Free a dynamic memory block. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_free(void *ptr) | |
+{ | |
+ if (ptr) free(ptr); | |
+} | |
+ | |
+/* | |
+ * Duplicate a string. | |
+ */ | |
+ | |
+YAML_DECLARE(yaml_char_t *) | |
+yaml_strdup(const yaml_char_t *str) | |
+{ | |
+ if (!str) | |
+ return NULL; | |
+ | |
+ return (yaml_char_t *)strdup((char *)str); | |
+} | |
+ | |
+/* | |
+ * Extend a string. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_string_extend(yaml_char_t **start, | |
+ yaml_char_t **pointer, yaml_char_t **end) | |
+{ | |
+ yaml_char_t *new_start = yaml_realloc(*start, (*end - *start)*2); | |
+ | |
+ if (!new_start) return 0; | |
+ | |
+ memset(new_start + (*end - *start), 0, *end - *start); | |
+ | |
+ *pointer = new_start + (*pointer - *start); | |
+ *end = new_start + (*end - *start)*2; | |
+ *start = new_start; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Append a string B to a string A. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_string_join( | |
+ yaml_char_t **a_start, yaml_char_t **a_pointer, yaml_char_t **a_end, | |
+ yaml_char_t **b_start, yaml_char_t **b_pointer, yaml_char_t **b_end) | |
+{ | |
+ if (*b_start == *b_pointer) | |
+ return 1; | |
+ | |
+ while (*a_end - *a_pointer <= *b_pointer - *b_start) { | |
+ if (!yaml_string_extend(a_start, a_pointer, a_end)) | |
+ return 0; | |
+ } | |
+ | |
+ memcpy(*a_pointer, *b_start, *b_pointer - *b_start); | |
+ *a_pointer += *b_pointer - *b_start; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Extend a stack. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_stack_extend(void **start, void **top, void **end) | |
+{ | |
+ void *new_start = yaml_realloc(*start, ((char *)*end - (char *)*start)*2); | |
+ | |
+ if (!new_start) return 0; | |
+ | |
+ *top = (char *)new_start + ((char *)*top - (char *)*start); | |
+ *end = (char *)new_start + ((char *)*end - (char *)*start)*2; | |
+ *start = new_start; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Extend or move a queue. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_queue_extend(void **start, void **head, void **tail, void **end) | |
+{ | |
+ /* Check if we need to resize the queue. */ | |
+ | |
+ if (*start == *head && *tail == *end) { | |
+ void *new_start = yaml_realloc(*start, | |
+ ((char *)*end - (char *)*start)*2); | |
+ | |
+ if (!new_start) return 0; | |
+ | |
+ *head = (char *)new_start + ((char *)*head - (char *)*start); | |
+ *tail = (char *)new_start + ((char *)*tail - (char *)*start); | |
+ *end = (char *)new_start + ((char *)*end - (char *)*start)*2; | |
+ *start = new_start; | |
+ } | |
+ | |
+ /* Check if we need to move the queue at the beginning of the buffer. */ | |
+ | |
+ if (*tail == *end) { | |
+ if (*head != *tail) { | |
+ memmove(*start, *head, (char *)*tail - (char *)*head); | |
+ } | |
+ *tail = (char *)*tail - (char *)*head + (char *)*start; | |
+ *head = *start; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+ | |
+/* | |
+ * Create a new parser object. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_initialize(yaml_parser_t *parser) | |
+{ | |
+ assert(parser); /* Non-NULL parser object expected. */ | |
+ | |
+ memset(parser, 0, sizeof(yaml_parser_t)); | |
+ if (!BUFFER_INIT(parser, parser->raw_buffer, INPUT_RAW_BUFFER_SIZE)) | |
+ goto error; | |
+ if (!BUFFER_INIT(parser, parser->buffer, INPUT_BUFFER_SIZE)) | |
+ goto error; | |
+ if (!QUEUE_INIT(parser, parser->tokens, INITIAL_QUEUE_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(parser, parser->indents, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(parser, parser->simple_keys, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(parser, parser->states, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(parser, parser->marks, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(parser, parser->tag_directives, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ | |
+ BUFFER_DEL(parser, parser->raw_buffer); | |
+ BUFFER_DEL(parser, parser->buffer); | |
+ QUEUE_DEL(parser, parser->tokens); | |
+ STACK_DEL(parser, parser->indents); | |
+ STACK_DEL(parser, parser->simple_keys); | |
+ STACK_DEL(parser, parser->states); | |
+ STACK_DEL(parser, parser->marks); | |
+ STACK_DEL(parser, parser->tag_directives); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Destroy a parser object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_delete(yaml_parser_t *parser) | |
+{ | |
+ assert(parser); /* Non-NULL parser object expected. */ | |
+ | |
+ BUFFER_DEL(parser, parser->raw_buffer); | |
+ BUFFER_DEL(parser, parser->buffer); | |
+ while (!QUEUE_EMPTY(parser, parser->tokens)) { | |
+ yaml_token_delete(&DEQUEUE(parser, parser->tokens)); | |
+ } | |
+ QUEUE_DEL(parser, parser->tokens); | |
+ STACK_DEL(parser, parser->indents); | |
+ STACK_DEL(parser, parser->simple_keys); | |
+ STACK_DEL(parser, parser->states); | |
+ STACK_DEL(parser, parser->marks); | |
+ while (!STACK_EMPTY(parser, parser->tag_directives)) { | |
+ yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); | |
+ yaml_free(tag_directive.handle); | |
+ yaml_free(tag_directive.prefix); | |
+ } | |
+ STACK_DEL(parser, parser->tag_directives); | |
+ | |
+ memset(parser, 0, sizeof(yaml_parser_t)); | |
+} | |
+ | |
+/* | |
+ * String read handler. | |
+ */ | |
+ | |
+static int | |
+yaml_string_read_handler(void *data, unsigned char *buffer, size_t size, | |
+ size_t *size_read) | |
+{ | |
+ yaml_parser_t *parser = data; | |
+ | |
+ if (parser->input.string.current == parser->input.string.end) { | |
+ *size_read = 0; | |
+ return 1; | |
+ } | |
+ | |
+ if (size > (size_t)(parser->input.string.end | |
+ - parser->input.string.current)) { | |
+ size = parser->input.string.end - parser->input.string.current; | |
+ } | |
+ | |
+ memcpy(buffer, parser->input.string.current, size); | |
+ parser->input.string.current += size; | |
+ *size_read = size; | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * File read handler. | |
+ */ | |
+ | |
+static int | |
+yaml_file_read_handler(void *data, unsigned char *buffer, size_t size, | |
+ size_t *size_read) | |
+{ | |
+ yaml_parser_t *parser = data; | |
+ | |
+ *size_read = fread(buffer, 1, size, parser->input.file); | |
+ return !ferror(parser->input.file); | |
+} | |
+ | |
+/* | |
+ * Set a string input. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_input_string(yaml_parser_t *parser, | |
+ const unsigned char *input, size_t size) | |
+{ | |
+ assert(parser); /* Non-NULL parser object expected. */ | |
+ assert(!parser->read_handler); /* You can set the source only once. */ | |
+ assert(input); /* Non-NULL input string expected. */ | |
+ | |
+ parser->read_handler = yaml_string_read_handler; | |
+ parser->read_handler_data = parser; | |
+ | |
+ parser->input.string.start = input; | |
+ parser->input.string.current = input; | |
+ parser->input.string.end = input+size; | |
+} | |
+ | |
+/* | |
+ * Set a file input. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_input_file(yaml_parser_t *parser, FILE *file) | |
+{ | |
+ assert(parser); /* Non-NULL parser object expected. */ | |
+ assert(!parser->read_handler); /* You can set the source only once. */ | |
+ assert(file); /* Non-NULL file object expected. */ | |
+ | |
+ parser->read_handler = yaml_file_read_handler; | |
+ parser->read_handler_data = parser; | |
+ | |
+ parser->input.file = file; | |
+} | |
+ | |
+/* | |
+ * Set a generic input. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_input(yaml_parser_t *parser, | |
+ yaml_read_handler_t *handler, void *data) | |
+{ | |
+ assert(parser); /* Non-NULL parser object expected. */ | |
+ assert(!parser->read_handler); /* You can set the source only once. */ | |
+ assert(handler); /* Non-NULL read handler expected. */ | |
+ | |
+ parser->read_handler = handler; | |
+ parser->read_handler_data = data; | |
+} | |
+ | |
+/* | |
+ * Set the source encoding. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_encoding(yaml_parser_t *parser, yaml_encoding_t encoding) | |
+{ | |
+ assert(parser); /* Non-NULL parser object expected. */ | |
+ assert(!parser->encoding); /* Encoding is already set or detected. */ | |
+ | |
+ parser->encoding = encoding; | |
+} | |
+ | |
+/* | |
+ * Create a new emitter object. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_initialize(yaml_emitter_t *emitter) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ | |
+ memset(emitter, 0, sizeof(yaml_emitter_t)); | |
+ if (!BUFFER_INIT(emitter, emitter->buffer, OUTPUT_BUFFER_SIZE)) | |
+ goto error; | |
+ if (!BUFFER_INIT(emitter, emitter->raw_buffer, OUTPUT_RAW_BUFFER_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(emitter, emitter->states, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ if (!QUEUE_INIT(emitter, emitter->events, INITIAL_QUEUE_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(emitter, emitter->indents, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ if (!STACK_INIT(emitter, emitter->tag_directives, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ | |
+ BUFFER_DEL(emitter, emitter->buffer); | |
+ BUFFER_DEL(emitter, emitter->raw_buffer); | |
+ STACK_DEL(emitter, emitter->states); | |
+ QUEUE_DEL(emitter, emitter->events); | |
+ STACK_DEL(emitter, emitter->indents); | |
+ STACK_DEL(emitter, emitter->tag_directives); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Destroy an emitter object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_delete(yaml_emitter_t *emitter) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ | |
+ BUFFER_DEL(emitter, emitter->buffer); | |
+ BUFFER_DEL(emitter, emitter->raw_buffer); | |
+ STACK_DEL(emitter, emitter->states); | |
+ while (!QUEUE_EMPTY(emitter, emitter->events)) { | |
+ yaml_event_delete(&DEQUEUE(emitter, emitter->events)); | |
+ } | |
+ QUEUE_DEL(emitter, emitter->events); | |
+ STACK_DEL(emitter, emitter->indents); | |
+ while (!STACK_EMPTY(empty, emitter->tag_directives)) { | |
+ yaml_tag_directive_t tag_directive = POP(emitter, emitter->tag_directives); | |
+ yaml_free(tag_directive.handle); | |
+ yaml_free(tag_directive.prefix); | |
+ } | |
+ STACK_DEL(emitter, emitter->tag_directives); | |
+ yaml_free(emitter->anchors); | |
+ | |
+ memset(emitter, 0, sizeof(yaml_emitter_t)); | |
+} | |
+ | |
+/* | |
+ * String write handler. | |
+ */ | |
+ | |
+static int | |
+yaml_string_write_handler(void *data, unsigned char *buffer, size_t size) | |
+{ | |
+ yaml_emitter_t *emitter = data; | |
+ | |
+ if (emitter->output.string.size + *emitter->output.string.size_written | |
+ < size) { | |
+ memcpy(emitter->output.string.buffer | |
+ + *emitter->output.string.size_written, | |
+ buffer, | |
+ emitter->output.string.size | |
+ - *emitter->output.string.size_written); | |
+ *emitter->output.string.size_written = emitter->output.string.size; | |
+ return 0; | |
+ } | |
+ | |
+ memcpy(emitter->output.string.buffer | |
+ + *emitter->output.string.size_written, buffer, size); | |
+ *emitter->output.string.size_written += size; | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * File write handler. | |
+ */ | |
+ | |
+static int | |
+yaml_file_write_handler(void *data, unsigned char *buffer, size_t size) | |
+{ | |
+ yaml_emitter_t *emitter = data; | |
+ | |
+ return (fwrite(buffer, 1, size, emitter->output.file) == size); | |
+} | |
+/* | |
+ * Set a string output. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_output_string(yaml_emitter_t *emitter, | |
+ unsigned char *output, size_t size, size_t *size_written) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ assert(!emitter->write_handler); /* You can set the output only once. */ | |
+ assert(output); /* Non-NULL output string expected. */ | |
+ | |
+ emitter->write_handler = yaml_string_write_handler; | |
+ emitter->write_handler_data = emitter; | |
+ | |
+ emitter->output.string.buffer = output; | |
+ emitter->output.string.size = size; | |
+ emitter->output.string.size_written = size_written; | |
+ *size_written = 0; | |
+} | |
+ | |
+/* | |
+ * Set a file output. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_output_file(yaml_emitter_t *emitter, FILE *file) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ assert(!emitter->write_handler); /* You can set the output only once. */ | |
+ assert(file); /* Non-NULL file object expected. */ | |
+ | |
+ emitter->write_handler = yaml_file_write_handler; | |
+ emitter->write_handler_data = emitter; | |
+ | |
+ emitter->output.file = file; | |
+} | |
+ | |
+/* | |
+ * Set a generic output handler. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_output(yaml_emitter_t *emitter, | |
+ yaml_write_handler_t *handler, void *data) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ assert(!emitter->write_handler); /* You can set the output only once. */ | |
+ assert(handler); /* Non-NULL handler object expected. */ | |
+ | |
+ emitter->write_handler = handler; | |
+ emitter->write_handler_data = data; | |
+} | |
+ | |
+/* | |
+ * Set the output encoding. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_encoding(yaml_emitter_t *emitter, yaml_encoding_t encoding) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ assert(!emitter->encoding); /* You can set encoding only once. */ | |
+ | |
+ emitter->encoding = encoding; | |
+} | |
+ | |
+/* | |
+ * Set the canonical output style. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_canonical(yaml_emitter_t *emitter, int canonical) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ | |
+ emitter->canonical = (canonical != 0); | |
+} | |
+ | |
+/* | |
+ * Set the indentation increment. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_indent(yaml_emitter_t *emitter, int indent) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ | |
+ emitter->best_indent = (1 < indent && indent < 10) ? indent : 2; | |
+} | |
+ | |
+/* | |
+ * Set the preferred line width. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_width(yaml_emitter_t *emitter, int width) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ | |
+ emitter->best_width = (width >= 0) ? width : -1; | |
+} | |
+ | |
+/* | |
+ * Set if unescaped non-ASCII characters are allowed. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_unicode(yaml_emitter_t *emitter, int unicode) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ | |
+ emitter->unicode = (unicode != 0); | |
+} | |
+ | |
+/* | |
+ * Set the preferred line break character. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_break(yaml_emitter_t *emitter, yaml_break_t line_break) | |
+{ | |
+ assert(emitter); /* Non-NULL emitter object expected. */ | |
+ | |
+ emitter->line_break = line_break; | |
+} | |
+ | |
+/* | |
+ * Destroy a token object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_token_delete(yaml_token_t *token) | |
+{ | |
+ assert(token); /* Non-NULL token object expected. */ | |
+ | |
+ switch (token->type) | |
+ { | |
+ case YAML_TAG_DIRECTIVE_TOKEN: | |
+ yaml_free(token->data.tag_directive.handle); | |
+ yaml_free(token->data.tag_directive.prefix); | |
+ break; | |
+ | |
+ case YAML_ALIAS_TOKEN: | |
+ yaml_free(token->data.alias.value); | |
+ break; | |
+ | |
+ case YAML_ANCHOR_TOKEN: | |
+ yaml_free(token->data.anchor.value); | |
+ break; | |
+ | |
+ case YAML_TAG_TOKEN: | |
+ yaml_free(token->data.tag.handle); | |
+ yaml_free(token->data.tag.suffix); | |
+ break; | |
+ | |
+ case YAML_SCALAR_TOKEN: | |
+ yaml_free(token->data.scalar.value); | |
+ break; | |
+ | |
+ default: | |
+ break; | |
+ } | |
+ | |
+ memset(token, 0, sizeof(yaml_token_t)); | |
+} | |
+ | |
+/* | |
+ * Check if a string is a valid UTF-8 sequence. | |
+ * | |
+ * Check 'reader.c' for more details on UTF-8 encoding. | |
+ */ | |
+ | |
+static int | |
+yaml_check_utf8(yaml_char_t *start, size_t length) | |
+{ | |
+ yaml_char_t *end = start+length; | |
+ yaml_char_t *pointer = start; | |
+ | |
+ while (pointer < end) { | |
+ unsigned char octet; | |
+ unsigned int width; | |
+ unsigned int value; | |
+ size_t k; | |
+ | |
+ octet = pointer[0]; | |
+ width = (octet & 0x80) == 0x00 ? 1 : | |
+ (octet & 0xE0) == 0xC0 ? 2 : | |
+ (octet & 0xF0) == 0xE0 ? 3 : | |
+ (octet & 0xF8) == 0xF0 ? 4 : 0; | |
+ value = (octet & 0x80) == 0x00 ? octet & 0x7F : | |
+ (octet & 0xE0) == 0xC0 ? octet & 0x1F : | |
+ (octet & 0xF0) == 0xE0 ? octet & 0x0F : | |
+ (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; | |
+ if (!width) return 0; | |
+ if (pointer+width > end) return 0; | |
+ for (k = 1; k < width; k ++) { | |
+ octet = pointer[k]; | |
+ if ((octet & 0xC0) != 0x80) return 0; | |
+ value = (value << 6) + (octet & 0x3F); | |
+ } | |
+ if (!((width == 1) || | |
+ (width == 2 && value >= 0x80) || | |
+ (width == 3 && value >= 0x800) || | |
+ (width == 4 && value >= 0x10000))) return 0; | |
+ | |
+ pointer += width; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Create STREAM-START. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_stream_start_event_initialize(yaml_event_t *event, | |
+ yaml_encoding_t encoding) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ | |
+ STREAM_START_EVENT_INIT(*event, encoding, mark, mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Create STREAM-END. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_stream_end_event_initialize(yaml_event_t *event) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ | |
+ STREAM_END_EVENT_INIT(*event, mark, mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Create DOCUMENT-START. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_start_event_initialize(yaml_event_t *event, | |
+ yaml_version_directive_t *version_directive, | |
+ yaml_tag_directive_t *tag_directives_start, | |
+ yaml_tag_directive_t *tag_directives_end, | |
+ int implicit) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_version_directive_t *version_directive_copy = NULL; | |
+ struct { | |
+ yaml_tag_directive_t *start; | |
+ yaml_tag_directive_t *end; | |
+ yaml_tag_directive_t *top; | |
+ } tag_directives_copy = { NULL, NULL, NULL }; | |
+ yaml_tag_directive_t value = { NULL, NULL }; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ assert((tag_directives_start && tag_directives_end) || | |
+ (tag_directives_start == tag_directives_end)); | |
+ /* Valid tag directives are expected. */ | |
+ | |
+ if (version_directive) { | |
+ version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)); | |
+ if (!version_directive_copy) goto error; | |
+ version_directive_copy->major = version_directive->major; | |
+ version_directive_copy->minor = version_directive->minor; | |
+ } | |
+ | |
+ if (tag_directives_start != tag_directives_end) { | |
+ yaml_tag_directive_t *tag_directive; | |
+ if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ for (tag_directive = tag_directives_start; | |
+ tag_directive != tag_directives_end; tag_directive ++) { | |
+ assert(tag_directive->handle); | |
+ assert(tag_directive->prefix); | |
+ if (!yaml_check_utf8(tag_directive->handle, | |
+ strlen((char *)tag_directive->handle))) | |
+ goto error; | |
+ if (!yaml_check_utf8(tag_directive->prefix, | |
+ strlen((char *)tag_directive->prefix))) | |
+ goto error; | |
+ value.handle = yaml_strdup(tag_directive->handle); | |
+ value.prefix = yaml_strdup(tag_directive->prefix); | |
+ if (!value.handle || !value.prefix) goto error; | |
+ if (!PUSH(&context, tag_directives_copy, value)) | |
+ goto error; | |
+ value.handle = NULL; | |
+ value.prefix = NULL; | |
+ } | |
+ } | |
+ | |
+ DOCUMENT_START_EVENT_INIT(*event, version_directive_copy, | |
+ tag_directives_copy.start, tag_directives_copy.top, | |
+ implicit, mark, mark); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(version_directive_copy); | |
+ while (!STACK_EMPTY(context, tag_directives_copy)) { | |
+ yaml_tag_directive_t value = POP(context, tag_directives_copy); | |
+ yaml_free(value.handle); | |
+ yaml_free(value.prefix); | |
+ } | |
+ STACK_DEL(context, tag_directives_copy); | |
+ yaml_free(value.handle); | |
+ yaml_free(value.prefix); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Create DOCUMENT-END. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_end_event_initialize(yaml_event_t *event, int implicit) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(event); /* Non-NULL emitter object is expected. */ | |
+ | |
+ DOCUMENT_END_EVENT_INIT(*event, implicit, mark, mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Create ALIAS. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_alias_event_initialize(yaml_event_t *event, yaml_char_t *anchor) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_char_t *anchor_copy = NULL; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ assert(anchor); /* Non-NULL anchor is expected. */ | |
+ | |
+ if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0; | |
+ | |
+ anchor_copy = yaml_strdup(anchor); | |
+ if (!anchor_copy) | |
+ return 0; | |
+ | |
+ ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Create SCALAR. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_scalar_event_initialize(yaml_event_t *event, | |
+ yaml_char_t *anchor, yaml_char_t *tag, | |
+ yaml_char_t *value, int length, | |
+ int plain_implicit, int quoted_implicit, | |
+ yaml_scalar_style_t style) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_char_t *anchor_copy = NULL; | |
+ yaml_char_t *tag_copy = NULL; | |
+ yaml_char_t *value_copy = NULL; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ assert(value); /* Non-NULL anchor is expected. */ | |
+ | |
+ if (anchor) { | |
+ if (!yaml_check_utf8(anchor, strlen((char *)anchor))) goto error; | |
+ anchor_copy = yaml_strdup(anchor); | |
+ if (!anchor_copy) goto error; | |
+ } | |
+ | |
+ if (tag) { | |
+ if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; | |
+ tag_copy = yaml_strdup(tag); | |
+ if (!tag_copy) goto error; | |
+ } | |
+ | |
+ if (length < 0) { | |
+ length = strlen((char *)value); | |
+ } | |
+ | |
+ if (!yaml_check_utf8(value, length)) goto error; | |
+ value_copy = yaml_malloc(length+1); | |
+ if (!value_copy) goto error; | |
+ memcpy(value_copy, value, length); | |
+ value_copy[length] = '\0'; | |
+ | |
+ SCALAR_EVENT_INIT(*event, anchor_copy, tag_copy, value_copy, length, | |
+ plain_implicit, quoted_implicit, style, mark, mark); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(anchor_copy); | |
+ yaml_free(tag_copy); | |
+ yaml_free(value_copy); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Create SEQUENCE-START. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_sequence_start_event_initialize(yaml_event_t *event, | |
+ yaml_char_t *anchor, yaml_char_t *tag, int implicit, | |
+ yaml_sequence_style_t style) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_char_t *anchor_copy = NULL; | |
+ yaml_char_t *tag_copy = NULL; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ | |
+ if (anchor) { | |
+ if (!yaml_check_utf8(anchor, strlen((char *)anchor))) goto error; | |
+ anchor_copy = yaml_strdup(anchor); | |
+ if (!anchor_copy) goto error; | |
+ } | |
+ | |
+ if (tag) { | |
+ if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; | |
+ tag_copy = yaml_strdup(tag); | |
+ if (!tag_copy) goto error; | |
+ } | |
+ | |
+ SEQUENCE_START_EVENT_INIT(*event, anchor_copy, tag_copy, | |
+ implicit, style, mark, mark); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(anchor_copy); | |
+ yaml_free(tag_copy); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Create SEQUENCE-END. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_sequence_end_event_initialize(yaml_event_t *event) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ | |
+ SEQUENCE_END_EVENT_INIT(*event, mark, mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Create MAPPING-START. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_mapping_start_event_initialize(yaml_event_t *event, | |
+ yaml_char_t *anchor, yaml_char_t *tag, int implicit, | |
+ yaml_mapping_style_t style) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_char_t *anchor_copy = NULL; | |
+ yaml_char_t *tag_copy = NULL; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ | |
+ if (anchor) { | |
+ if (!yaml_check_utf8(anchor, strlen((char *)anchor))) goto error; | |
+ anchor_copy = yaml_strdup(anchor); | |
+ if (!anchor_copy) goto error; | |
+ } | |
+ | |
+ if (tag) { | |
+ if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; | |
+ tag_copy = yaml_strdup(tag); | |
+ if (!tag_copy) goto error; | |
+ } | |
+ | |
+ MAPPING_START_EVENT_INIT(*event, anchor_copy, tag_copy, | |
+ implicit, style, mark, mark); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(anchor_copy); | |
+ yaml_free(tag_copy); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Create MAPPING-END. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_mapping_end_event_initialize(yaml_event_t *event) | |
+{ | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ | |
+ MAPPING_END_EVENT_INIT(*event, mark, mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Destroy an event object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_event_delete(yaml_event_t *event) | |
+{ | |
+ yaml_tag_directive_t *tag_directive; | |
+ | |
+ assert(event); /* Non-NULL event object expected. */ | |
+ | |
+ switch (event->type) | |
+ { | |
+ case YAML_DOCUMENT_START_EVENT: | |
+ yaml_free(event->data.document_start.version_directive); | |
+ for (tag_directive = event->data.document_start.tag_directives.start; | |
+ tag_directive != event->data.document_start.tag_directives.end; | |
+ tag_directive++) { | |
+ yaml_free(tag_directive->handle); | |
+ yaml_free(tag_directive->prefix); | |
+ } | |
+ yaml_free(event->data.document_start.tag_directives.start); | |
+ break; | |
+ | |
+ case YAML_ALIAS_EVENT: | |
+ yaml_free(event->data.alias.anchor); | |
+ break; | |
+ | |
+ case YAML_SCALAR_EVENT: | |
+ yaml_free(event->data.scalar.anchor); | |
+ yaml_free(event->data.scalar.tag); | |
+ yaml_free(event->data.scalar.value); | |
+ break; | |
+ | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ yaml_free(event->data.sequence_start.anchor); | |
+ yaml_free(event->data.sequence_start.tag); | |
+ break; | |
+ | |
+ case YAML_MAPPING_START_EVENT: | |
+ yaml_free(event->data.mapping_start.anchor); | |
+ yaml_free(event->data.mapping_start.tag); | |
+ break; | |
+ | |
+ default: | |
+ break; | |
+ } | |
+ | |
+ memset(event, 0, sizeof(yaml_event_t)); | |
+} | |
+ | |
+/* | |
+ * Create a document object. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_initialize(yaml_document_t *document, | |
+ yaml_version_directive_t *version_directive, | |
+ yaml_tag_directive_t *tag_directives_start, | |
+ yaml_tag_directive_t *tag_directives_end, | |
+ int start_implicit, int end_implicit) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ struct { | |
+ yaml_node_t *start; | |
+ yaml_node_t *end; | |
+ yaml_node_t *top; | |
+ } nodes = { NULL, NULL, NULL }; | |
+ yaml_version_directive_t *version_directive_copy = NULL; | |
+ struct { | |
+ yaml_tag_directive_t *start; | |
+ yaml_tag_directive_t *end; | |
+ yaml_tag_directive_t *top; | |
+ } tag_directives_copy = { NULL, NULL, NULL }; | |
+ yaml_tag_directive_t value = { NULL, NULL }; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ assert((tag_directives_start && tag_directives_end) || | |
+ (tag_directives_start == tag_directives_end)); | |
+ /* Valid tag directives are expected. */ | |
+ | |
+ if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error; | |
+ | |
+ if (version_directive) { | |
+ version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)); | |
+ if (!version_directive_copy) goto error; | |
+ version_directive_copy->major = version_directive->major; | |
+ version_directive_copy->minor = version_directive->minor; | |
+ } | |
+ | |
+ if (tag_directives_start != tag_directives_end) { | |
+ yaml_tag_directive_t *tag_directive; | |
+ if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ for (tag_directive = tag_directives_start; | |
+ tag_directive != tag_directives_end; tag_directive ++) { | |
+ assert(tag_directive->handle); | |
+ assert(tag_directive->prefix); | |
+ if (!yaml_check_utf8(tag_directive->handle, | |
+ strlen((char *)tag_directive->handle))) | |
+ goto error; | |
+ if (!yaml_check_utf8(tag_directive->prefix, | |
+ strlen((char *)tag_directive->prefix))) | |
+ goto error; | |
+ value.handle = yaml_strdup(tag_directive->handle); | |
+ value.prefix = yaml_strdup(tag_directive->prefix); | |
+ if (!value.handle || !value.prefix) goto error; | |
+ if (!PUSH(&context, tag_directives_copy, value)) | |
+ goto error; | |
+ value.handle = NULL; | |
+ value.prefix = NULL; | |
+ } | |
+ } | |
+ | |
+ DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, | |
+ tag_directives_copy.start, tag_directives_copy.top, | |
+ start_implicit, end_implicit, mark, mark); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STACK_DEL(&context, nodes); | |
+ yaml_free(version_directive_copy); | |
+ while (!STACK_EMPTY(&context, tag_directives_copy)) { | |
+ yaml_tag_directive_t value = POP(&context, tag_directives_copy); | |
+ yaml_free(value.handle); | |
+ yaml_free(value.prefix); | |
+ } | |
+ STACK_DEL(&context, tag_directives_copy); | |
+ yaml_free(value.handle); | |
+ yaml_free(value.prefix); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Destroy a document object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_document_delete(yaml_document_t *document) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ yaml_tag_directive_t *tag_directive; | |
+ | |
+ context.error = YAML_NO_ERROR; /* Eliminate a compliler warning. */ | |
+ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ | |
+ while (!STACK_EMPTY(&context, document->nodes)) { | |
+ yaml_node_t node = POP(&context, document->nodes); | |
+ yaml_free(node.tag); | |
+ switch (node.type) { | |
+ case YAML_SCALAR_NODE: | |
+ yaml_free(node.data.scalar.value); | |
+ break; | |
+ case YAML_SEQUENCE_NODE: | |
+ STACK_DEL(&context, node.data.sequence.items); | |
+ break; | |
+ case YAML_MAPPING_NODE: | |
+ STACK_DEL(&context, node.data.mapping.pairs); | |
+ break; | |
+ default: | |
+ assert(0); /* Should not happen. */ | |
+ } | |
+ } | |
+ STACK_DEL(&context, document->nodes); | |
+ | |
+ yaml_free(document->version_directive); | |
+ for (tag_directive = document->tag_directives.start; | |
+ tag_directive != document->tag_directives.end; | |
+ tag_directive++) { | |
+ yaml_free(tag_directive->handle); | |
+ yaml_free(tag_directive->prefix); | |
+ } | |
+ yaml_free(document->tag_directives.start); | |
+ | |
+ memset(document, 0, sizeof(yaml_document_t)); | |
+} | |
+ | |
+/** | |
+ * Get a document node. | |
+ */ | |
+ | |
+YAML_DECLARE(yaml_node_t *) | |
+yaml_document_get_node(yaml_document_t *document, int index) | |
+{ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ | |
+ if (index > 0 && document->nodes.start + index <= document->nodes.top) { | |
+ return document->nodes.start + index - 1; | |
+ } | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * Get the root object. | |
+ */ | |
+ | |
+YAML_DECLARE(yaml_node_t *) | |
+yaml_document_get_root_node(yaml_document_t *document) | |
+{ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ | |
+ if (document->nodes.top != document->nodes.start) { | |
+ return document->nodes.start; | |
+ } | |
+ return NULL; | |
+} | |
+ | |
+/* | |
+ * Add a scalar node to a document. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_add_scalar(yaml_document_t *document, | |
+ yaml_char_t *tag, yaml_char_t *value, int length, | |
+ yaml_scalar_style_t style) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_char_t *tag_copy = NULL; | |
+ yaml_char_t *value_copy = NULL; | |
+ yaml_node_t node; | |
+ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ assert(value); /* Non-NULL value is expected. */ | |
+ | |
+ if (!tag) { | |
+ tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG; | |
+ } | |
+ | |
+ if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; | |
+ tag_copy = yaml_strdup(tag); | |
+ if (!tag_copy) goto error; | |
+ | |
+ if (length < 0) { | |
+ length = strlen((char *)value); | |
+ } | |
+ | |
+ if (!yaml_check_utf8(value, length)) goto error; | |
+ value_copy = yaml_malloc(length+1); | |
+ if (!value_copy) goto error; | |
+ memcpy(value_copy, value, length); | |
+ value_copy[length] = '\0'; | |
+ | |
+ SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark); | |
+ if (!PUSH(&context, document->nodes, node)) goto error; | |
+ | |
+ return document->nodes.top - document->nodes.start; | |
+ | |
+error: | |
+ yaml_free(tag_copy); | |
+ yaml_free(value_copy); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Add a sequence node to a document. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_add_sequence(yaml_document_t *document, | |
+ yaml_char_t *tag, yaml_sequence_style_t style) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_char_t *tag_copy = NULL; | |
+ struct { | |
+ yaml_node_item_t *start; | |
+ yaml_node_item_t *end; | |
+ yaml_node_item_t *top; | |
+ } items = { NULL, NULL, NULL }; | |
+ yaml_node_t node; | |
+ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ | |
+ if (!tag) { | |
+ tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG; | |
+ } | |
+ | |
+ if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; | |
+ tag_copy = yaml_strdup(tag); | |
+ if (!tag_copy) goto error; | |
+ | |
+ if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error; | |
+ | |
+ SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, | |
+ style, mark, mark); | |
+ if (!PUSH(&context, document->nodes, node)) goto error; | |
+ | |
+ return document->nodes.top - document->nodes.start; | |
+ | |
+error: | |
+ STACK_DEL(&context, items); | |
+ yaml_free(tag_copy); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Add a mapping node to a document. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_add_mapping(yaml_document_t *document, | |
+ yaml_char_t *tag, yaml_mapping_style_t style) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ yaml_char_t *tag_copy = NULL; | |
+ struct { | |
+ yaml_node_pair_t *start; | |
+ yaml_node_pair_t *end; | |
+ yaml_node_pair_t *top; | |
+ } pairs = { NULL, NULL, NULL }; | |
+ yaml_node_t node; | |
+ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ | |
+ if (!tag) { | |
+ tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG; | |
+ } | |
+ | |
+ if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; | |
+ tag_copy = yaml_strdup(tag); | |
+ if (!tag_copy) goto error; | |
+ | |
+ if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error; | |
+ | |
+ MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, | |
+ style, mark, mark); | |
+ if (!PUSH(&context, document->nodes, node)) goto error; | |
+ | |
+ return document->nodes.top - document->nodes.start; | |
+ | |
+error: | |
+ STACK_DEL(&context, pairs); | |
+ yaml_free(tag_copy); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Append an item to a sequence node. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_append_sequence_item(yaml_document_t *document, | |
+ int sequence, int item) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ | |
+ assert(document); /* Non-NULL document is required. */ | |
+ assert(sequence > 0 | |
+ && document->nodes.start + sequence <= document->nodes.top); | |
+ /* Valid sequence id is required. */ | |
+ assert(document->nodes.start[sequence-1].type == YAML_SEQUENCE_NODE); | |
+ /* A sequence node is required. */ | |
+ assert(item > 0 && document->nodes.start + item <= document->nodes.top); | |
+ /* Valid item id is required. */ | |
+ | |
+ if (!PUSH(&context, | |
+ document->nodes.start[sequence-1].data.sequence.items, item)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Append a pair of a key and a value to a mapping node. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_append_mapping_pair(yaml_document_t *document, | |
+ int mapping, int key, int value) | |
+{ | |
+ struct { | |
+ yaml_error_type_t error; | |
+ } context; | |
+ | |
+ yaml_node_pair_t pair; | |
+ | |
+ assert(document); /* Non-NULL document is required. */ | |
+ assert(mapping > 0 | |
+ && document->nodes.start + mapping <= document->nodes.top); | |
+ /* Valid mapping id is required. */ | |
+ assert(document->nodes.start[mapping-1].type == YAML_MAPPING_NODE); | |
+ /* A mapping node is required. */ | |
+ assert(key > 0 && document->nodes.start + key <= document->nodes.top); | |
+ /* Valid key id is required. */ | |
+ assert(value > 0 && document->nodes.start + value <= document->nodes.top); | |
+ /* Valid value id is required. */ | |
+ | |
+ pair.key = key; | |
+ pair.value = value; | |
+ | |
+ if (!PUSH(&context, | |
+ document->nodes.start[mapping-1].data.mapping.pairs, pair)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
diff --git a/ext/psych/yaml/config.h b/ext/psych/yaml/config.h | |
new file mode 100644 | |
index 0000000..6d6c25b | |
--- /dev/null | |
+++ b/ext/psych/yaml/config.h | |
@@ -0,0 +1,11 @@ | |
+ | |
+#define PACKAGE_NAME "yaml" | |
+#define PACKAGE_TARNAME "yaml" | |
+#define PACKAGE_VERSION "0.1.4" | |
+#define PACKAGE_STRING "yaml 0.1.4" | |
+#define PACKAGE_BUGREPORT "http://pyyaml.org/newticket?component libyaml" | |
+#define PACKAGE_URL "" | |
+#define YAML_VERSION_MAJOR 0 | |
+#define YAML_VERSION_MINOR 1 | |
+#define YAML_VERSION_PATCH 4 | |
+#define YAML_VERSION_STRING "0.1.4" | |
diff --git a/ext/psych/yaml/dumper.c b/ext/psych/yaml/dumper.c | |
new file mode 100644 | |
index 0000000..5552329 | |
--- /dev/null | |
+++ b/ext/psych/yaml/dumper.c | |
@@ -0,0 +1,393 @@ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * API functions. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_open(yaml_emitter_t *emitter); | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_close(yaml_emitter_t *emitter); | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document); | |
+ | |
+/* | |
+ * Clean up functions. | |
+ */ | |
+ | |
+static void | |
+yaml_emitter_delete_document_and_anchors(yaml_emitter_t *emitter); | |
+ | |
+/* | |
+ * Anchor functions. | |
+ */ | |
+ | |
+static void | |
+yaml_emitter_anchor_node(yaml_emitter_t *emitter, int index); | |
+ | |
+static yaml_char_t * | |
+yaml_emitter_generate_anchor(yaml_emitter_t *emitter, int anchor_id); | |
+ | |
+ | |
+/* | |
+ * Serialize functions. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_dump_node(yaml_emitter_t *emitter, int index); | |
+ | |
+static int | |
+yaml_emitter_dump_alias(yaml_emitter_t *emitter, yaml_char_t *anchor); | |
+ | |
+static int | |
+yaml_emitter_dump_scalar(yaml_emitter_t *emitter, yaml_node_t *node, | |
+ yaml_char_t *anchor); | |
+ | |
+static int | |
+yaml_emitter_dump_sequence(yaml_emitter_t *emitter, yaml_node_t *node, | |
+ yaml_char_t *anchor); | |
+ | |
+static int | |
+yaml_emitter_dump_mapping(yaml_emitter_t *emitter, yaml_node_t *node, | |
+ yaml_char_t *anchor); | |
+ | |
+/* | |
+ * Issue a STREAM-START event. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_open(yaml_emitter_t *emitter) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(emitter); /* Non-NULL emitter object is required. */ | |
+ assert(!emitter->opened); /* Emitter should not be opened yet. */ | |
+ | |
+ STREAM_START_EVENT_INIT(event, YAML_ANY_ENCODING, mark, mark); | |
+ | |
+ if (!yaml_emitter_emit(emitter, &event)) { | |
+ return 0; | |
+ } | |
+ | |
+ emitter->opened = 1; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Issue a STREAM-END event. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_close(yaml_emitter_t *emitter) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(emitter); /* Non-NULL emitter object is required. */ | |
+ assert(emitter->opened); /* Emitter should be opened. */ | |
+ | |
+ if (emitter->closed) return 1; | |
+ | |
+ STREAM_END_EVENT_INIT(event, mark, mark); | |
+ | |
+ if (!yaml_emitter_emit(emitter, &event)) { | |
+ return 0; | |
+ } | |
+ | |
+ emitter->closed = 1; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Dump a YAML document. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ assert(emitter); /* Non-NULL emitter object is required. */ | |
+ assert(document); /* Non-NULL emitter object is expected. */ | |
+ | |
+ emitter->document = document; | |
+ | |
+ if (!emitter->opened) { | |
+ if (!yaml_emitter_open(emitter)) goto error; | |
+ } | |
+ | |
+ if (STACK_EMPTY(emitter, document->nodes)) { | |
+ if (!yaml_emitter_close(emitter)) goto error; | |
+ yaml_emitter_delete_document_and_anchors(emitter); | |
+ return 1; | |
+ } | |
+ | |
+ assert(emitter->opened); /* Emitter should be opened. */ | |
+ | |
+ emitter->anchors = yaml_malloc(sizeof(*(emitter->anchors)) | |
+ * (document->nodes.top - document->nodes.start)); | |
+ if (!emitter->anchors) goto error; | |
+ memset(emitter->anchors, 0, sizeof(*(emitter->anchors)) | |
+ * (document->nodes.top - document->nodes.start)); | |
+ | |
+ DOCUMENT_START_EVENT_INIT(event, document->version_directive, | |
+ document->tag_directives.start, document->tag_directives.end, | |
+ document->start_implicit, mark, mark); | |
+ if (!yaml_emitter_emit(emitter, &event)) goto error; | |
+ | |
+ yaml_emitter_anchor_node(emitter, 1); | |
+ if (!yaml_emitter_dump_node(emitter, 1)) goto error; | |
+ | |
+ DOCUMENT_END_EVENT_INIT(event, document->end_implicit, mark, mark); | |
+ if (!yaml_emitter_emit(emitter, &event)) goto error; | |
+ | |
+ yaml_emitter_delete_document_and_anchors(emitter); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ | |
+ yaml_emitter_delete_document_and_anchors(emitter); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Clean up the emitter object after a document is dumped. | |
+ */ | |
+ | |
+static void | |
+yaml_emitter_delete_document_and_anchors(yaml_emitter_t *emitter) | |
+{ | |
+ int index; | |
+ | |
+ if (!emitter->anchors) { | |
+ yaml_document_delete(emitter->document); | |
+ emitter->document = NULL; | |
+ return; | |
+ } | |
+ | |
+ for (index = 0; emitter->document->nodes.start + index | |
+ < emitter->document->nodes.top; index ++) { | |
+ yaml_node_t node = emitter->document->nodes.start[index]; | |
+ if (!emitter->anchors[index].serialized) { | |
+ yaml_free(node.tag); | |
+ if (node.type == YAML_SCALAR_NODE) { | |
+ yaml_free(node.data.scalar.value); | |
+ } | |
+ } | |
+ if (node.type == YAML_SEQUENCE_NODE) { | |
+ STACK_DEL(emitter, node.data.sequence.items); | |
+ } | |
+ if (node.type == YAML_MAPPING_NODE) { | |
+ STACK_DEL(emitter, node.data.mapping.pairs); | |
+ } | |
+ } | |
+ | |
+ STACK_DEL(emitter, emitter->document->nodes); | |
+ yaml_free(emitter->anchors); | |
+ | |
+ emitter->anchors = NULL; | |
+ emitter->last_anchor_id = 0; | |
+ emitter->document = NULL; | |
+} | |
+ | |
+/* | |
+ * Check the references of a node and assign the anchor id if needed. | |
+ */ | |
+ | |
+static void | |
+yaml_emitter_anchor_node(yaml_emitter_t *emitter, int index) | |
+{ | |
+ yaml_node_t *node = emitter->document->nodes.start + index - 1; | |
+ yaml_node_item_t *item; | |
+ yaml_node_pair_t *pair; | |
+ | |
+ emitter->anchors[index-1].references ++; | |
+ | |
+ if (emitter->anchors[index-1].references == 1) { | |
+ switch (node->type) { | |
+ case YAML_SEQUENCE_NODE: | |
+ for (item = node->data.sequence.items.start; | |
+ item < node->data.sequence.items.top; item ++) { | |
+ yaml_emitter_anchor_node(emitter, *item); | |
+ } | |
+ break; | |
+ case YAML_MAPPING_NODE: | |
+ for (pair = node->data.mapping.pairs.start; | |
+ pair < node->data.mapping.pairs.top; pair ++) { | |
+ yaml_emitter_anchor_node(emitter, pair->key); | |
+ yaml_emitter_anchor_node(emitter, pair->value); | |
+ } | |
+ break; | |
+ default: | |
+ break; | |
+ } | |
+ } | |
+ | |
+ else if (emitter->anchors[index-1].references == 2) { | |
+ emitter->anchors[index-1].anchor = (++ emitter->last_anchor_id); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Generate a textual representation for an anchor. | |
+ */ | |
+ | |
+#define ANCHOR_TEMPLATE "id%03d" | |
+#define ANCHOR_TEMPLATE_LENGTH 16 | |
+ | |
+static yaml_char_t * | |
+yaml_emitter_generate_anchor(yaml_emitter_t *emitter, int anchor_id) | |
+{ | |
+ yaml_char_t *anchor = yaml_malloc(ANCHOR_TEMPLATE_LENGTH); | |
+ | |
+ if (!anchor) return NULL; | |
+ | |
+ sprintf((char *)anchor, ANCHOR_TEMPLATE, anchor_id); | |
+ | |
+ return anchor; | |
+} | |
+ | |
+/* | |
+ * Serialize a node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_dump_node(yaml_emitter_t *emitter, int index) | |
+{ | |
+ yaml_node_t *node = emitter->document->nodes.start + index - 1; | |
+ int anchor_id = emitter->anchors[index-1].anchor; | |
+ yaml_char_t *anchor = NULL; | |
+ | |
+ if (anchor_id) { | |
+ anchor = yaml_emitter_generate_anchor(emitter, anchor_id); | |
+ if (!anchor) return 0; | |
+ } | |
+ | |
+ if (emitter->anchors[index-1].serialized) { | |
+ return yaml_emitter_dump_alias(emitter, anchor); | |
+ } | |
+ | |
+ emitter->anchors[index-1].serialized = 1; | |
+ | |
+ switch (node->type) { | |
+ case YAML_SCALAR_NODE: | |
+ return yaml_emitter_dump_scalar(emitter, node, anchor); | |
+ case YAML_SEQUENCE_NODE: | |
+ return yaml_emitter_dump_sequence(emitter, node, anchor); | |
+ case YAML_MAPPING_NODE: | |
+ return yaml_emitter_dump_mapping(emitter, node, anchor); | |
+ default: | |
+ assert(0); /* Could not happen. */ | |
+ break; | |
+ } | |
+ | |
+ return 0; /* Could not happen. */ | |
+} | |
+ | |
+/* | |
+ * Serialize an alias. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_dump_alias(yaml_emitter_t *emitter, yaml_char_t *anchor) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ ALIAS_EVENT_INIT(event, anchor, mark, mark); | |
+ | |
+ return yaml_emitter_emit(emitter, &event); | |
+} | |
+ | |
+/* | |
+ * Serialize a scalar. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_dump_scalar(yaml_emitter_t *emitter, yaml_node_t *node, | |
+ yaml_char_t *anchor) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ int plain_implicit = (strcmp((char *)node->tag, | |
+ YAML_DEFAULT_SCALAR_TAG) == 0); | |
+ int quoted_implicit = (strcmp((char *)node->tag, | |
+ YAML_DEFAULT_SCALAR_TAG) == 0); | |
+ | |
+ SCALAR_EVENT_INIT(event, anchor, node->tag, node->data.scalar.value, | |
+ node->data.scalar.length, plain_implicit, quoted_implicit, | |
+ node->data.scalar.style, mark, mark); | |
+ | |
+ return yaml_emitter_emit(emitter, &event); | |
+} | |
+ | |
+/* | |
+ * Serialize a sequence. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_dump_sequence(yaml_emitter_t *emitter, yaml_node_t *node, | |
+ yaml_char_t *anchor) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ int implicit = (strcmp((char *)node->tag, YAML_DEFAULT_SEQUENCE_TAG) == 0); | |
+ | |
+ yaml_node_item_t *item; | |
+ | |
+ SEQUENCE_START_EVENT_INIT(event, anchor, node->tag, implicit, | |
+ node->data.sequence.style, mark, mark); | |
+ if (!yaml_emitter_emit(emitter, &event)) return 0; | |
+ | |
+ for (item = node->data.sequence.items.start; | |
+ item < node->data.sequence.items.top; item ++) { | |
+ if (!yaml_emitter_dump_node(emitter, *item)) return 0; | |
+ } | |
+ | |
+ SEQUENCE_END_EVENT_INIT(event, mark, mark); | |
+ if (!yaml_emitter_emit(emitter, &event)) return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Serialize a mapping. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_dump_mapping(yaml_emitter_t *emitter, yaml_node_t *node, | |
+ yaml_char_t *anchor) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_mark_t mark = { 0, 0, 0 }; | |
+ | |
+ int implicit = (strcmp((char *)node->tag, YAML_DEFAULT_MAPPING_TAG) == 0); | |
+ | |
+ yaml_node_pair_t *pair; | |
+ | |
+ MAPPING_START_EVENT_INIT(event, anchor, node->tag, implicit, | |
+ node->data.mapping.style, mark, mark); | |
+ if (!yaml_emitter_emit(emitter, &event)) return 0; | |
+ | |
+ for (pair = node->data.mapping.pairs.start; | |
+ pair < node->data.mapping.pairs.top; pair ++) { | |
+ if (!yaml_emitter_dump_node(emitter, pair->key)) return 0; | |
+ if (!yaml_emitter_dump_node(emitter, pair->value)) return 0; | |
+ } | |
+ | |
+ MAPPING_END_EVENT_INIT(event, mark, mark); | |
+ if (!yaml_emitter_emit(emitter, &event)) return 0; | |
+ | |
+ return 1; | |
+} | |
diff --git a/ext/psych/yaml/emitter.c b/ext/psych/yaml/emitter.c | |
new file mode 100644 | |
index 0000000..6b51443 | |
--- /dev/null | |
+++ b/ext/psych/yaml/emitter.c | |
@@ -0,0 +1,2328 @@ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * Flush the buffer if needed. | |
+ */ | |
+ | |
+#define FLUSH(emitter) \ | |
+ ((emitter->buffer.pointer+5 < emitter->buffer.end) \ | |
+ || yaml_emitter_flush(emitter)) | |
+ | |
+/* | |
+ * Put a character to the output buffer. | |
+ */ | |
+ | |
+#define PUT(emitter,value) \ | |
+ (FLUSH(emitter) \ | |
+ && (*(emitter->buffer.pointer++) = (yaml_char_t)(value), \ | |
+ emitter->column ++, \ | |
+ 1)) | |
+ | |
+/* | |
+ * Put a line break to the output buffer. | |
+ */ | |
+ | |
+#define PUT_BREAK(emitter) \ | |
+ (FLUSH(emitter) \ | |
+ && ((emitter->line_break == YAML_CR_BREAK ? \ | |
+ (*(emitter->buffer.pointer++) = (yaml_char_t) '\r') : \ | |
+ emitter->line_break == YAML_LN_BREAK ? \ | |
+ (*(emitter->buffer.pointer++) = (yaml_char_t) '\n') : \ | |
+ emitter->line_break == YAML_CRLN_BREAK ? \ | |
+ (*(emitter->buffer.pointer++) = (yaml_char_t) '\r', \ | |
+ *(emitter->buffer.pointer++) = (yaml_char_t) '\n') : 0), \ | |
+ emitter->column = 0, \ | |
+ emitter->line ++, \ | |
+ 1)) | |
+ | |
+/* | |
+ * Copy a character from a string into buffer. | |
+ */ | |
+ | |
+#define WRITE(emitter,string) \ | |
+ (FLUSH(emitter) \ | |
+ && (COPY(emitter->buffer,string), \ | |
+ emitter->column ++, \ | |
+ 1)) | |
+ | |
+/* | |
+ * Copy a line break character from a string into buffer. | |
+ */ | |
+ | |
+#define WRITE_BREAK(emitter,string) \ | |
+ (FLUSH(emitter) \ | |
+ && (CHECK(string,'\n') ? \ | |
+ (PUT_BREAK(emitter), \ | |
+ string.pointer ++, \ | |
+ 1) : \ | |
+ (COPY(emitter->buffer,string), \ | |
+ emitter->column = 0, \ | |
+ emitter->line ++, \ | |
+ 1))) | |
+ | |
+/* | |
+ * API functions. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+/* | |
+ * Utility functions. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_set_emitter_error(yaml_emitter_t *emitter, const char *problem); | |
+ | |
+static int | |
+yaml_emitter_need_more_events(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_append_tag_directive(yaml_emitter_t *emitter, | |
+ yaml_tag_directive_t value, int allow_duplicates); | |
+ | |
+static int | |
+yaml_emitter_increase_indent(yaml_emitter_t *emitter, | |
+ int flow, int indentless); | |
+ | |
+/* | |
+ * State functions. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_state_machine(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_emitter_emit_stream_start(yaml_emitter_t *emitter, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_emitter_emit_document_start(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_emitter_emit_document_content(yaml_emitter_t *emitter, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_emitter_emit_document_end(yaml_emitter_t *emitter, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_emitter_emit_flow_sequence_item(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_emitter_emit_flow_mapping_key(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_emitter_emit_flow_mapping_value(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int simple); | |
+ | |
+static int | |
+yaml_emitter_emit_block_sequence_item(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_emitter_emit_block_mapping_key(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_emitter_emit_block_mapping_value(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int simple); | |
+ | |
+static int | |
+yaml_emitter_emit_node(yaml_emitter_t *emitter, yaml_event_t *event, | |
+ int root, int sequence, int mapping, int simple_key); | |
+ | |
+static int | |
+yaml_emitter_emit_alias(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_emitter_emit_scalar(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_emitter_emit_sequence_start(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_emitter_emit_mapping_start(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+/* | |
+ * Checkers. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_check_empty_document(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_check_empty_sequence(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_check_empty_mapping(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_check_simple_key(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_select_scalar_style(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+/* | |
+ * Processors. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_process_anchor(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_process_tag(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_process_scalar(yaml_emitter_t *emitter); | |
+ | |
+/* | |
+ * Analyzers. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_analyze_version_directive(yaml_emitter_t *emitter, | |
+ yaml_version_directive_t version_directive); | |
+ | |
+static int | |
+yaml_emitter_analyze_tag_directive(yaml_emitter_t *emitter, | |
+ yaml_tag_directive_t tag_directive); | |
+ | |
+static int | |
+yaml_emitter_analyze_anchor(yaml_emitter_t *emitter, | |
+ yaml_char_t *anchor, int alias); | |
+ | |
+static int | |
+yaml_emitter_analyze_tag(yaml_emitter_t *emitter, | |
+ yaml_char_t *tag); | |
+ | |
+static int | |
+yaml_emitter_analyze_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length); | |
+ | |
+static int | |
+yaml_emitter_analyze_event(yaml_emitter_t *emitter, | |
+ yaml_event_t *event); | |
+ | |
+/* | |
+ * Writers. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_write_bom(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_write_indent(yaml_emitter_t *emitter); | |
+ | |
+static int | |
+yaml_emitter_write_indicator(yaml_emitter_t *emitter, | |
+ const char *indicator, int need_whitespace, | |
+ int is_whitespace, int is_indention); | |
+ | |
+static int | |
+yaml_emitter_write_anchor(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length); | |
+ | |
+static int | |
+yaml_emitter_write_tag_handle(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length); | |
+ | |
+static int | |
+yaml_emitter_write_tag_content(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, int need_whitespace); | |
+ | |
+static int | |
+yaml_emitter_write_plain_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, int allow_breaks); | |
+ | |
+static int | |
+yaml_emitter_write_single_quoted_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, int allow_breaks); | |
+ | |
+static int | |
+yaml_emitter_write_double_quoted_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, int allow_breaks); | |
+ | |
+static int | |
+yaml_emitter_write_block_scalar_hints(yaml_emitter_t *emitter, | |
+ yaml_string_t string); | |
+ | |
+static int | |
+yaml_emitter_write_literal_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length); | |
+ | |
+static int | |
+yaml_emitter_write_folded_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length); | |
+ | |
+/* | |
+ * Set an emitter error and return 0. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_set_emitter_error(yaml_emitter_t *emitter, const char *problem) | |
+{ | |
+ emitter->error = YAML_EMITTER_ERROR; | |
+ emitter->problem = problem; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Emit an event. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event) | |
+{ | |
+ if (!ENQUEUE(emitter, emitter->events, *event)) { | |
+ yaml_event_delete(event); | |
+ return 0; | |
+ } | |
+ | |
+ while (!yaml_emitter_need_more_events(emitter)) { | |
+ if (!yaml_emitter_analyze_event(emitter, emitter->events.head)) | |
+ return 0; | |
+ if (!yaml_emitter_state_machine(emitter, emitter->events.head)) | |
+ return 0; | |
+ yaml_event_delete(&DEQUEUE(emitter, emitter->events)); | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if we need to accumulate more events before emitting. | |
+ * | |
+ * We accumulate extra | |
+ * - 1 event for DOCUMENT-START | |
+ * - 2 events for SEQUENCE-START | |
+ * - 3 events for MAPPING-START | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_need_more_events(yaml_emitter_t *emitter) | |
+{ | |
+ int level = 0; | |
+ int accumulate = 0; | |
+ yaml_event_t *event; | |
+ | |
+ if (QUEUE_EMPTY(emitter, emitter->events)) | |
+ return 1; | |
+ | |
+ switch (emitter->events.head->type) { | |
+ case YAML_DOCUMENT_START_EVENT: | |
+ accumulate = 1; | |
+ break; | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ accumulate = 2; | |
+ break; | |
+ case YAML_MAPPING_START_EVENT: | |
+ accumulate = 3; | |
+ break; | |
+ default: | |
+ return 0; | |
+ } | |
+ | |
+ if (emitter->events.tail - emitter->events.head > accumulate) | |
+ return 0; | |
+ | |
+ for (event = emitter->events.head; event != emitter->events.tail; event ++) { | |
+ switch (event->type) { | |
+ case YAML_STREAM_START_EVENT: | |
+ case YAML_DOCUMENT_START_EVENT: | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ case YAML_MAPPING_START_EVENT: | |
+ level += 1; | |
+ break; | |
+ case YAML_STREAM_END_EVENT: | |
+ case YAML_DOCUMENT_END_EVENT: | |
+ case YAML_SEQUENCE_END_EVENT: | |
+ case YAML_MAPPING_END_EVENT: | |
+ level -= 1; | |
+ break; | |
+ default: | |
+ break; | |
+ } | |
+ if (!level) | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Append a directive to the directives stack. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_append_tag_directive(yaml_emitter_t *emitter, | |
+ yaml_tag_directive_t value, int allow_duplicates) | |
+{ | |
+ yaml_tag_directive_t *tag_directive; | |
+ yaml_tag_directive_t copy = { NULL, NULL }; | |
+ | |
+ for (tag_directive = emitter->tag_directives.start; | |
+ tag_directive != emitter->tag_directives.top; tag_directive ++) { | |
+ if (strcmp((char *)value.handle, (char *)tag_directive->handle) == 0) { | |
+ if (allow_duplicates) | |
+ return 1; | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "duplicate %TAG directive"); | |
+ } | |
+ } | |
+ | |
+ copy.handle = yaml_strdup(value.handle); | |
+ copy.prefix = yaml_strdup(value.prefix); | |
+ if (!copy.handle || !copy.prefix) { | |
+ emitter->error = YAML_MEMORY_ERROR; | |
+ goto error; | |
+ } | |
+ | |
+ if (!PUSH(emitter, emitter->tag_directives, copy)) | |
+ goto error; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(copy.handle); | |
+ yaml_free(copy.prefix); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Increase the indentation level. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_increase_indent(yaml_emitter_t *emitter, | |
+ int flow, int indentless) | |
+{ | |
+ if (!PUSH(emitter, emitter->indents, emitter->indent)) | |
+ return 0; | |
+ | |
+ if (emitter->indent < 0) { | |
+ emitter->indent = flow ? emitter->best_indent : 0; | |
+ } | |
+ else if (!indentless) { | |
+ emitter->indent += emitter->best_indent; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * State dispatcher. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_state_machine(yaml_emitter_t *emitter, yaml_event_t *event) | |
+{ | |
+ switch (emitter->state) | |
+ { | |
+ case YAML_EMIT_STREAM_START_STATE: | |
+ return yaml_emitter_emit_stream_start(emitter, event); | |
+ | |
+ case YAML_EMIT_FIRST_DOCUMENT_START_STATE: | |
+ return yaml_emitter_emit_document_start(emitter, event, 1); | |
+ | |
+ case YAML_EMIT_DOCUMENT_START_STATE: | |
+ return yaml_emitter_emit_document_start(emitter, event, 0); | |
+ | |
+ case YAML_EMIT_DOCUMENT_CONTENT_STATE: | |
+ return yaml_emitter_emit_document_content(emitter, event); | |
+ | |
+ case YAML_EMIT_DOCUMENT_END_STATE: | |
+ return yaml_emitter_emit_document_end(emitter, event); | |
+ | |
+ case YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: | |
+ return yaml_emitter_emit_flow_sequence_item(emitter, event, 1); | |
+ | |
+ case YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE: | |
+ return yaml_emitter_emit_flow_sequence_item(emitter, event, 0); | |
+ | |
+ case YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: | |
+ return yaml_emitter_emit_flow_mapping_key(emitter, event, 1); | |
+ | |
+ case YAML_EMIT_FLOW_MAPPING_KEY_STATE: | |
+ return yaml_emitter_emit_flow_mapping_key(emitter, event, 0); | |
+ | |
+ case YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: | |
+ return yaml_emitter_emit_flow_mapping_value(emitter, event, 1); | |
+ | |
+ case YAML_EMIT_FLOW_MAPPING_VALUE_STATE: | |
+ return yaml_emitter_emit_flow_mapping_value(emitter, event, 0); | |
+ | |
+ case YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: | |
+ return yaml_emitter_emit_block_sequence_item(emitter, event, 1); | |
+ | |
+ case YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE: | |
+ return yaml_emitter_emit_block_sequence_item(emitter, event, 0); | |
+ | |
+ case YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: | |
+ return yaml_emitter_emit_block_mapping_key(emitter, event, 1); | |
+ | |
+ case YAML_EMIT_BLOCK_MAPPING_KEY_STATE: | |
+ return yaml_emitter_emit_block_mapping_key(emitter, event, 0); | |
+ | |
+ case YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: | |
+ return yaml_emitter_emit_block_mapping_value(emitter, event, 1); | |
+ | |
+ case YAML_EMIT_BLOCK_MAPPING_VALUE_STATE: | |
+ return yaml_emitter_emit_block_mapping_value(emitter, event, 0); | |
+ | |
+ case YAML_EMIT_END_STATE: | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "expected nothing after STREAM-END"); | |
+ | |
+ default: | |
+ assert(1); /* Invalid state. */ | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Expect STREAM-START. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_stream_start(yaml_emitter_t *emitter, | |
+ yaml_event_t *event) | |
+{ | |
+ if (event->type == YAML_STREAM_START_EVENT) | |
+ { | |
+ if (!emitter->encoding) { | |
+ emitter->encoding = event->data.stream_start.encoding; | |
+ } | |
+ | |
+ if (!emitter->encoding) { | |
+ emitter->encoding = YAML_UTF8_ENCODING; | |
+ } | |
+ | |
+ if (emitter->best_indent < 2 || emitter->best_indent > 9) { | |
+ emitter->best_indent = 2; | |
+ } | |
+ | |
+ if (emitter->best_width >= 0 | |
+ && emitter->best_width <= emitter->best_indent*2) { | |
+ emitter->best_width = 80; | |
+ } | |
+ | |
+ if (emitter->best_width < 0) { | |
+ emitter->best_width = INT_MAX; | |
+ } | |
+ | |
+ if (!emitter->line_break) { | |
+ emitter->line_break = YAML_LN_BREAK; | |
+ } | |
+ | |
+ emitter->indent = -1; | |
+ | |
+ emitter->line = 0; | |
+ emitter->column = 0; | |
+ emitter->whitespace = 1; | |
+ emitter->indention = 1; | |
+ | |
+ if (emitter->encoding != YAML_UTF8_ENCODING) { | |
+ if (!yaml_emitter_write_bom(emitter)) | |
+ return 0; | |
+ } | |
+ | |
+ emitter->state = YAML_EMIT_FIRST_DOCUMENT_START_STATE; | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "expected STREAM-START"); | |
+} | |
+ | |
+/* | |
+ * Expect DOCUMENT-START or STREAM-END. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_document_start(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ if (event->type == YAML_DOCUMENT_START_EVENT) | |
+ { | |
+ yaml_tag_directive_t default_tag_directives[] = { | |
+ {(yaml_char_t *)"!", (yaml_char_t *)"!"}, | |
+ {(yaml_char_t *)"!!", (yaml_char_t *)"tag:yaml.org,2002:"}, | |
+ {NULL, NULL} | |
+ }; | |
+ yaml_tag_directive_t *tag_directive; | |
+ int implicit; | |
+ | |
+ if (event->data.document_start.version_directive) { | |
+ if (!yaml_emitter_analyze_version_directive(emitter, | |
+ *event->data.document_start.version_directive)) | |
+ return 0; | |
+ } | |
+ | |
+ for (tag_directive = event->data.document_start.tag_directives.start; | |
+ tag_directive != event->data.document_start.tag_directives.end; | |
+ tag_directive ++) { | |
+ if (!yaml_emitter_analyze_tag_directive(emitter, *tag_directive)) | |
+ return 0; | |
+ if (!yaml_emitter_append_tag_directive(emitter, *tag_directive, 0)) | |
+ return 0; | |
+ } | |
+ | |
+ for (tag_directive = default_tag_directives; | |
+ tag_directive->handle; tag_directive ++) { | |
+ if (!yaml_emitter_append_tag_directive(emitter, *tag_directive, 1)) | |
+ return 0; | |
+ } | |
+ | |
+ implicit = event->data.document_start.implicit; | |
+ if (!first || emitter->canonical) { | |
+ implicit = 0; | |
+ } | |
+ | |
+ if ((event->data.document_start.version_directive || | |
+ (event->data.document_start.tag_directives.start | |
+ != event->data.document_start.tag_directives.end)) && | |
+ emitter->open_ended) | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ | |
+ if (event->data.document_start.version_directive) { | |
+ implicit = 0; | |
+ if (!yaml_emitter_write_indicator(emitter, "%YAML", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indicator(emitter, "1.1", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ | |
+ if (event->data.document_start.tag_directives.start | |
+ != event->data.document_start.tag_directives.end) { | |
+ implicit = 0; | |
+ for (tag_directive = event->data.document_start.tag_directives.start; | |
+ tag_directive != event->data.document_start.tag_directives.end; | |
+ tag_directive ++) { | |
+ if (!yaml_emitter_write_indicator(emitter, "%TAG", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_tag_handle(emitter, tag_directive->handle, | |
+ strlen((char *)tag_directive->handle))) | |
+ return 0; | |
+ if (!yaml_emitter_write_tag_content(emitter, tag_directive->prefix, | |
+ strlen((char *)tag_directive->prefix), 1)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ } | |
+ | |
+ if (yaml_emitter_check_empty_document(emitter)) { | |
+ implicit = 0; | |
+ } | |
+ | |
+ if (!implicit) { | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indicator(emitter, "---", 1, 0, 0)) | |
+ return 0; | |
+ if (emitter->canonical) { | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ } | |
+ | |
+ emitter->state = YAML_EMIT_DOCUMENT_CONTENT_STATE; | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ else if (event->type == YAML_STREAM_END_EVENT) | |
+ { | |
+ if (emitter->open_ended) | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ | |
+ if (!yaml_emitter_flush(emitter)) | |
+ return 0; | |
+ | |
+ emitter->state = YAML_EMIT_END_STATE; | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "expected DOCUMENT-START or STREAM-END"); | |
+} | |
+ | |
+/* | |
+ * Expect the root node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_document_content(yaml_emitter_t *emitter, | |
+ yaml_event_t *event) | |
+{ | |
+ if (!PUSH(emitter, emitter->states, YAML_EMIT_DOCUMENT_END_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 1, 0, 0, 0); | |
+} | |
+ | |
+/* | |
+ * Expect DOCUMENT-END. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_document_end(yaml_emitter_t *emitter, | |
+ yaml_event_t *event) | |
+{ | |
+ if (event->type == YAML_DOCUMENT_END_EVENT) | |
+ { | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ if (!event->data.document_end.implicit) { | |
+ if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ if (!yaml_emitter_flush(emitter)) | |
+ return 0; | |
+ | |
+ emitter->state = YAML_EMIT_DOCUMENT_START_STATE; | |
+ | |
+ while (!STACK_EMPTY(emitter, emitter->tag_directives)) { | |
+ yaml_tag_directive_t tag_directive = POP(emitter, | |
+ emitter->tag_directives); | |
+ yaml_free(tag_directive.handle); | |
+ yaml_free(tag_directive.prefix); | |
+ } | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "expected DOCUMENT-END"); | |
+} | |
+ | |
+/* | |
+ * | |
+ * Expect a flow item node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_flow_sequence_item(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ if (first) | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, "[", 1, 1, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_increase_indent(emitter, 1, 0)) | |
+ return 0; | |
+ emitter->flow_level ++; | |
+ } | |
+ | |
+ if (event->type == YAML_SEQUENCE_END_EVENT) | |
+ { | |
+ emitter->flow_level --; | |
+ emitter->indent = POP(emitter, emitter->indents); | |
+ if (emitter->canonical && !first) { | |
+ if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ if (!yaml_emitter_write_indicator(emitter, "]", 0, 0, 0)) | |
+ return 0; | |
+ emitter->state = POP(emitter, emitter->states); | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ if (!first) { | |
+ if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) | |
+ return 0; | |
+ } | |
+ | |
+ if (emitter->canonical || emitter->column > emitter->best_width) { | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 0, 1, 0, 0); | |
+} | |
+ | |
+/* | |
+ * Expect a flow key node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_flow_mapping_key(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ if (first) | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, "{", 1, 1, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_increase_indent(emitter, 1, 0)) | |
+ return 0; | |
+ emitter->flow_level ++; | |
+ } | |
+ | |
+ if (event->type == YAML_MAPPING_END_EVENT) | |
+ { | |
+ emitter->flow_level --; | |
+ emitter->indent = POP(emitter, emitter->indents); | |
+ if (emitter->canonical && !first) { | |
+ if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ if (!yaml_emitter_write_indicator(emitter, "}", 0, 0, 0)) | |
+ return 0; | |
+ emitter->state = POP(emitter, emitter->states); | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ if (!first) { | |
+ if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) | |
+ return 0; | |
+ } | |
+ if (emitter->canonical || emitter->column > emitter->best_width) { | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ | |
+ if (!emitter->canonical && yaml_emitter_check_simple_key(emitter)) | |
+ { | |
+ if (!PUSH(emitter, emitter->states, | |
+ YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 1); | |
+ } | |
+ else | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, "?", 1, 0, 0)) | |
+ return 0; | |
+ if (!PUSH(emitter, emitter->states, | |
+ YAML_EMIT_FLOW_MAPPING_VALUE_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Expect a flow value node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_flow_mapping_value(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int simple) | |
+{ | |
+ if (simple) { | |
+ if (!yaml_emitter_write_indicator(emitter, ":", 0, 0, 0)) | |
+ return 0; | |
+ } | |
+ else { | |
+ if (emitter->canonical || emitter->column > emitter->best_width) { | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ } | |
+ if (!yaml_emitter_write_indicator(emitter, ":", 1, 0, 0)) | |
+ return 0; | |
+ } | |
+ if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_MAPPING_KEY_STATE)) | |
+ return 0; | |
+ return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); | |
+} | |
+ | |
+/* | |
+ * Expect a block item node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_block_sequence_item(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ if (first) | |
+ { | |
+ if (!yaml_emitter_increase_indent(emitter, 0, | |
+ (emitter->mapping_context && !emitter->indention))) | |
+ return 0; | |
+ } | |
+ | |
+ if (event->type == YAML_SEQUENCE_END_EVENT) | |
+ { | |
+ emitter->indent = POP(emitter, emitter->indents); | |
+ emitter->state = POP(emitter, emitter->states); | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indicator(emitter, "-", 1, 0, 1)) | |
+ return 0; | |
+ if (!PUSH(emitter, emitter->states, | |
+ YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 0, 1, 0, 0); | |
+} | |
+ | |
+/* | |
+ * Expect a block key node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_block_mapping_key(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ if (first) | |
+ { | |
+ if (!yaml_emitter_increase_indent(emitter, 0, 0)) | |
+ return 0; | |
+ } | |
+ | |
+ if (event->type == YAML_MAPPING_END_EVENT) | |
+ { | |
+ emitter->indent = POP(emitter, emitter->indents); | |
+ emitter->state = POP(emitter, emitter->states); | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ | |
+ if (yaml_emitter_check_simple_key(emitter)) | |
+ { | |
+ if (!PUSH(emitter, emitter->states, | |
+ YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 1); | |
+ } | |
+ else | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, "?", 1, 0, 1)) | |
+ return 0; | |
+ if (!PUSH(emitter, emitter->states, | |
+ YAML_EMIT_BLOCK_MAPPING_VALUE_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Expect a block value node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_block_mapping_value(yaml_emitter_t *emitter, | |
+ yaml_event_t *event, int simple) | |
+{ | |
+ if (simple) { | |
+ if (!yaml_emitter_write_indicator(emitter, ":", 0, 0, 0)) | |
+ return 0; | |
+ } | |
+ else { | |
+ if (!yaml_emitter_write_indent(emitter)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indicator(emitter, ":", 1, 0, 1)) | |
+ return 0; | |
+ } | |
+ if (!PUSH(emitter, emitter->states, | |
+ YAML_EMIT_BLOCK_MAPPING_KEY_STATE)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); | |
+} | |
+ | |
+/* | |
+ * Expect a node. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_node(yaml_emitter_t *emitter, yaml_event_t *event, | |
+ int root, int sequence, int mapping, int simple_key) | |
+{ | |
+ emitter->root_context = root; | |
+ emitter->sequence_context = sequence; | |
+ emitter->mapping_context = mapping; | |
+ emitter->simple_key_context = simple_key; | |
+ | |
+ switch (event->type) | |
+ { | |
+ case YAML_ALIAS_EVENT: | |
+ return yaml_emitter_emit_alias(emitter, event); | |
+ | |
+ case YAML_SCALAR_EVENT: | |
+ return yaml_emitter_emit_scalar(emitter, event); | |
+ | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ return yaml_emitter_emit_sequence_start(emitter, event); | |
+ | |
+ case YAML_MAPPING_START_EVENT: | |
+ return yaml_emitter_emit_mapping_start(emitter, event); | |
+ | |
+ default: | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS"); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Expect ALIAS. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_alias(yaml_emitter_t *emitter, yaml_event_t *event) | |
+{ | |
+ if (!yaml_emitter_process_anchor(emitter)) | |
+ return 0; | |
+ emitter->state = POP(emitter, emitter->states); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Expect SCALAR. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_scalar(yaml_emitter_t *emitter, yaml_event_t *event) | |
+{ | |
+ if (!yaml_emitter_select_scalar_style(emitter, event)) | |
+ return 0; | |
+ if (!yaml_emitter_process_anchor(emitter)) | |
+ return 0; | |
+ if (!yaml_emitter_process_tag(emitter)) | |
+ return 0; | |
+ if (!yaml_emitter_increase_indent(emitter, 1, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_process_scalar(emitter)) | |
+ return 0; | |
+ emitter->indent = POP(emitter, emitter->indents); | |
+ emitter->state = POP(emitter, emitter->states); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Expect SEQUENCE-START. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_sequence_start(yaml_emitter_t *emitter, yaml_event_t *event) | |
+{ | |
+ if (!yaml_emitter_process_anchor(emitter)) | |
+ return 0; | |
+ if (!yaml_emitter_process_tag(emitter)) | |
+ return 0; | |
+ | |
+ if (emitter->flow_level || emitter->canonical | |
+ || event->data.sequence_start.style == YAML_FLOW_SEQUENCE_STYLE | |
+ || yaml_emitter_check_empty_sequence(emitter)) { | |
+ emitter->state = YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE; | |
+ } | |
+ else { | |
+ emitter->state = YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Expect MAPPING-START. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_emit_mapping_start(yaml_emitter_t *emitter, yaml_event_t *event) | |
+{ | |
+ if (!yaml_emitter_process_anchor(emitter)) | |
+ return 0; | |
+ if (!yaml_emitter_process_tag(emitter)) | |
+ return 0; | |
+ | |
+ if (emitter->flow_level || emitter->canonical | |
+ || event->data.mapping_start.style == YAML_FLOW_MAPPING_STYLE | |
+ || yaml_emitter_check_empty_mapping(emitter)) { | |
+ emitter->state = YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE; | |
+ } | |
+ else { | |
+ emitter->state = YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if the document content is an empty scalar. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_check_empty_document(yaml_emitter_t *emitter) | |
+{ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Check if the next events represent an empty sequence. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_check_empty_sequence(yaml_emitter_t *emitter) | |
+{ | |
+ if (emitter->events.tail - emitter->events.head < 2) | |
+ return 0; | |
+ | |
+ return (emitter->events.head[0].type == YAML_SEQUENCE_START_EVENT | |
+ && emitter->events.head[1].type == YAML_SEQUENCE_END_EVENT); | |
+} | |
+ | |
+/* | |
+ * Check if the next events represent an empty mapping. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_check_empty_mapping(yaml_emitter_t *emitter) | |
+{ | |
+ if (emitter->events.tail - emitter->events.head < 2) | |
+ return 0; | |
+ | |
+ return (emitter->events.head[0].type == YAML_MAPPING_START_EVENT | |
+ && emitter->events.head[1].type == YAML_MAPPING_END_EVENT); | |
+} | |
+ | |
+/* | |
+ * Check if the next node can be expressed as a simple key. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_check_simple_key(yaml_emitter_t *emitter) | |
+{ | |
+ yaml_event_t *event = emitter->events.head; | |
+ size_t length = 0; | |
+ | |
+ switch (event->type) | |
+ { | |
+ case YAML_ALIAS_EVENT: | |
+ length += emitter->anchor_data.anchor_length; | |
+ break; | |
+ | |
+ case YAML_SCALAR_EVENT: | |
+ if (emitter->scalar_data.multiline) | |
+ return 0; | |
+ length += emitter->anchor_data.anchor_length | |
+ + emitter->tag_data.handle_length | |
+ + emitter->tag_data.suffix_length | |
+ + emitter->scalar_data.length; | |
+ break; | |
+ | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ if (!yaml_emitter_check_empty_sequence(emitter)) | |
+ return 0; | |
+ length += emitter->anchor_data.anchor_length | |
+ + emitter->tag_data.handle_length | |
+ + emitter->tag_data.suffix_length; | |
+ break; | |
+ | |
+ case YAML_MAPPING_START_EVENT: | |
+ if (!yaml_emitter_check_empty_mapping(emitter)) | |
+ return 0; | |
+ length += emitter->anchor_data.anchor_length | |
+ + emitter->tag_data.handle_length | |
+ + emitter->tag_data.suffix_length; | |
+ break; | |
+ | |
+ default: | |
+ return 0; | |
+ } | |
+ | |
+ if (length > 128) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Determine an acceptable scalar style. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_select_scalar_style(yaml_emitter_t *emitter, yaml_event_t *event) | |
+{ | |
+ yaml_scalar_style_t style = event->data.scalar.style; | |
+ int no_tag = (!emitter->tag_data.handle && !emitter->tag_data.suffix); | |
+ | |
+ if (no_tag && !event->data.scalar.plain_implicit | |
+ && !event->data.scalar.quoted_implicit) { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "neither tag nor implicit flags are specified"); | |
+ } | |
+ | |
+ if (style == YAML_ANY_SCALAR_STYLE) | |
+ style = YAML_PLAIN_SCALAR_STYLE; | |
+ | |
+ if (emitter->canonical) | |
+ style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; | |
+ | |
+ if (emitter->simple_key_context && emitter->scalar_data.multiline) | |
+ style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; | |
+ | |
+ if (style == YAML_PLAIN_SCALAR_STYLE) | |
+ { | |
+ if ((emitter->flow_level && !emitter->scalar_data.flow_plain_allowed) | |
+ || (!emitter->flow_level && !emitter->scalar_data.block_plain_allowed)) | |
+ style = YAML_SINGLE_QUOTED_SCALAR_STYLE; | |
+ if (!emitter->scalar_data.length | |
+ && (emitter->flow_level || emitter->simple_key_context)) | |
+ style = YAML_SINGLE_QUOTED_SCALAR_STYLE; | |
+ if (no_tag && !event->data.scalar.plain_implicit) | |
+ style = YAML_SINGLE_QUOTED_SCALAR_STYLE; | |
+ } | |
+ | |
+ if (style == YAML_SINGLE_QUOTED_SCALAR_STYLE) | |
+ { | |
+ if (!emitter->scalar_data.single_quoted_allowed) | |
+ style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; | |
+ } | |
+ | |
+ if (style == YAML_LITERAL_SCALAR_STYLE || style == YAML_FOLDED_SCALAR_STYLE) | |
+ { | |
+ if (!emitter->scalar_data.block_allowed | |
+ || emitter->flow_level || emitter->simple_key_context) | |
+ style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; | |
+ } | |
+ | |
+ if (no_tag && !event->data.scalar.quoted_implicit | |
+ && style != YAML_PLAIN_SCALAR_STYLE) | |
+ { | |
+ emitter->tag_data.handle = (yaml_char_t *)"!"; | |
+ emitter->tag_data.handle_length = 1; | |
+ } | |
+ | |
+ emitter->scalar_data.style = style; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Write an achor. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_process_anchor(yaml_emitter_t *emitter) | |
+{ | |
+ if (!emitter->anchor_data.anchor) | |
+ return 1; | |
+ | |
+ if (!yaml_emitter_write_indicator(emitter, | |
+ (emitter->anchor_data.alias ? "*" : "&"), 1, 0, 0)) | |
+ return 0; | |
+ | |
+ return yaml_emitter_write_anchor(emitter, | |
+ emitter->anchor_data.anchor, emitter->anchor_data.anchor_length); | |
+} | |
+ | |
+/* | |
+ * Write a tag. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_process_tag(yaml_emitter_t *emitter) | |
+{ | |
+ if (!emitter->tag_data.handle && !emitter->tag_data.suffix) | |
+ return 1; | |
+ | |
+ if (emitter->tag_data.handle) | |
+ { | |
+ if (!yaml_emitter_write_tag_handle(emitter, emitter->tag_data.handle, | |
+ emitter->tag_data.handle_length)) | |
+ return 0; | |
+ if (emitter->tag_data.suffix) { | |
+ if (!yaml_emitter_write_tag_content(emitter, emitter->tag_data.suffix, | |
+ emitter->tag_data.suffix_length, 0)) | |
+ return 0; | |
+ } | |
+ } | |
+ else | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, "!<", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_tag_content(emitter, emitter->tag_data.suffix, | |
+ emitter->tag_data.suffix_length, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_indicator(emitter, ">", 0, 0, 0)) | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Write a scalar. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_process_scalar(yaml_emitter_t *emitter) | |
+{ | |
+ switch (emitter->scalar_data.style) | |
+ { | |
+ case YAML_PLAIN_SCALAR_STYLE: | |
+ return yaml_emitter_write_plain_scalar(emitter, | |
+ emitter->scalar_data.value, emitter->scalar_data.length, | |
+ !emitter->simple_key_context); | |
+ | |
+ case YAML_SINGLE_QUOTED_SCALAR_STYLE: | |
+ return yaml_emitter_write_single_quoted_scalar(emitter, | |
+ emitter->scalar_data.value, emitter->scalar_data.length, | |
+ !emitter->simple_key_context); | |
+ | |
+ case YAML_DOUBLE_QUOTED_SCALAR_STYLE: | |
+ return yaml_emitter_write_double_quoted_scalar(emitter, | |
+ emitter->scalar_data.value, emitter->scalar_data.length, | |
+ !emitter->simple_key_context); | |
+ | |
+ case YAML_LITERAL_SCALAR_STYLE: | |
+ return yaml_emitter_write_literal_scalar(emitter, | |
+ emitter->scalar_data.value, emitter->scalar_data.length); | |
+ | |
+ case YAML_FOLDED_SCALAR_STYLE: | |
+ return yaml_emitter_write_folded_scalar(emitter, | |
+ emitter->scalar_data.value, emitter->scalar_data.length); | |
+ | |
+ default: | |
+ assert(1); /* Impossible. */ | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Check if a %YAML directive is valid. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_analyze_version_directive(yaml_emitter_t *emitter, | |
+ yaml_version_directive_t version_directive) | |
+{ | |
+ if (version_directive.major != 1 || version_directive.minor != 1) { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "incompatible %YAML directive"); | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if a %TAG directive is valid. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_analyze_tag_directive(yaml_emitter_t *emitter, | |
+ yaml_tag_directive_t tag_directive) | |
+{ | |
+ yaml_string_t handle; | |
+ yaml_string_t prefix; | |
+ size_t handle_length; | |
+ size_t prefix_length; | |
+ | |
+ handle_length = strlen((char *)tag_directive.handle); | |
+ prefix_length = strlen((char *)tag_directive.prefix); | |
+ STRING_ASSIGN(handle, tag_directive.handle, handle_length); | |
+ STRING_ASSIGN(prefix, tag_directive.prefix, prefix_length); | |
+ | |
+ if (handle.start == handle.end) { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "tag handle must not be empty"); | |
+ } | |
+ | |
+ if (handle.start[0] != '!') { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "tag handle must start with '!'"); | |
+ } | |
+ | |
+ if (handle.end[-1] != '!') { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "tag handle must end with '!'"); | |
+ } | |
+ | |
+ handle.pointer ++; | |
+ | |
+ while (handle.pointer < handle.end-1) { | |
+ if (!IS_ALPHA(handle)) { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "tag handle must contain alphanumerical characters only"); | |
+ } | |
+ MOVE(handle); | |
+ } | |
+ | |
+ if (prefix.start == prefix.end) { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "tag prefix must not be empty"); | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if an anchor is valid. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_analyze_anchor(yaml_emitter_t *emitter, | |
+ yaml_char_t *anchor, int alias) | |
+{ | |
+ size_t anchor_length; | |
+ yaml_string_t string; | |
+ | |
+ anchor_length = strlen((char *)anchor); | |
+ STRING_ASSIGN(string, anchor, anchor_length); | |
+ | |
+ if (string.start == string.end) { | |
+ return yaml_emitter_set_emitter_error(emitter, alias ? | |
+ "alias value must not be empty" : | |
+ "anchor value must not be empty"); | |
+ } | |
+ | |
+ while (string.pointer != string.end) { | |
+ if (!IS_ALPHA(string)) { | |
+ return yaml_emitter_set_emitter_error(emitter, alias ? | |
+ "alias value must contain alphanumerical characters only" : | |
+ "anchor value must contain alphanumerical characters only"); | |
+ } | |
+ MOVE(string); | |
+ } | |
+ | |
+ emitter->anchor_data.anchor = string.start; | |
+ emitter->anchor_data.anchor_length = string.end - string.start; | |
+ emitter->anchor_data.alias = alias; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if a tag is valid. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_analyze_tag(yaml_emitter_t *emitter, | |
+ yaml_char_t *tag) | |
+{ | |
+ size_t tag_length; | |
+ yaml_string_t string; | |
+ yaml_tag_directive_t *tag_directive; | |
+ | |
+ tag_length = strlen((char *)tag); | |
+ STRING_ASSIGN(string, tag, tag_length); | |
+ | |
+ if (string.start == string.end) { | |
+ return yaml_emitter_set_emitter_error(emitter, | |
+ "tag value must not be empty"); | |
+ } | |
+ | |
+ for (tag_directive = emitter->tag_directives.start; | |
+ tag_directive != emitter->tag_directives.top; tag_directive ++) { | |
+ size_t prefix_length = strlen((char *)tag_directive->prefix); | |
+ if (prefix_length < (size_t)(string.end - string.start) | |
+ && strncmp((char *)tag_directive->prefix, (char *)string.start, | |
+ prefix_length) == 0) | |
+ { | |
+ emitter->tag_data.handle = tag_directive->handle; | |
+ emitter->tag_data.handle_length = | |
+ strlen((char *)tag_directive->handle); | |
+ emitter->tag_data.suffix = string.start + prefix_length; | |
+ emitter->tag_data.suffix_length = | |
+ (string.end - string.start) - prefix_length; | |
+ return 1; | |
+ } | |
+ } | |
+ | |
+ emitter->tag_data.suffix = string.start; | |
+ emitter->tag_data.suffix_length = string.end - string.start; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if a scalar is valid. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_analyze_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length) | |
+{ | |
+ yaml_string_t string; | |
+ | |
+ int block_indicators = 0; | |
+ int flow_indicators = 0; | |
+ int line_breaks = 0; | |
+ int special_characters = 0; | |
+ | |
+ int leading_space = 0; | |
+ int leading_break = 0; | |
+ int trailing_space = 0; | |
+ int trailing_break = 0; | |
+ int break_space = 0; | |
+ int space_break = 0; | |
+ | |
+ int preceeded_by_whitespace = 0; | |
+ int followed_by_whitespace = 0; | |
+ int previous_space = 0; | |
+ int previous_break = 0; | |
+ | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ emitter->scalar_data.value = value; | |
+ emitter->scalar_data.length = length; | |
+ | |
+ if (string.start == string.end) | |
+ { | |
+ emitter->scalar_data.multiline = 0; | |
+ emitter->scalar_data.flow_plain_allowed = 0; | |
+ emitter->scalar_data.block_plain_allowed = 1; | |
+ emitter->scalar_data.single_quoted_allowed = 1; | |
+ emitter->scalar_data.block_allowed = 0; | |
+ | |
+ return 1; | |
+ } | |
+ | |
+ if ((CHECK_AT(string, '-', 0) | |
+ && CHECK_AT(string, '-', 1) | |
+ && CHECK_AT(string, '-', 2)) | |
+ || (CHECK_AT(string, '.', 0) | |
+ && CHECK_AT(string, '.', 1) | |
+ && CHECK_AT(string, '.', 2))) { | |
+ block_indicators = 1; | |
+ flow_indicators = 1; | |
+ } | |
+ | |
+ preceeded_by_whitespace = 1; | |
+ followed_by_whitespace = IS_BLANKZ_AT(string, WIDTH(string)); | |
+ | |
+ while (string.pointer != string.end) | |
+ { | |
+ if (string.start == string.pointer) | |
+ { | |
+ if (CHECK(string, '#') || CHECK(string, ',') | |
+ || CHECK(string, '[') || CHECK(string, ']') | |
+ || CHECK(string, '{') || CHECK(string, '}') | |
+ || CHECK(string, '&') || CHECK(string, '*') | |
+ || CHECK(string, '!') || CHECK(string, '|') | |
+ || CHECK(string, '>') || CHECK(string, '\'') | |
+ || CHECK(string, '"') || CHECK(string, '%') | |
+ || CHECK(string, '@') || CHECK(string, '`')) { | |
+ flow_indicators = 1; | |
+ block_indicators = 1; | |
+ } | |
+ | |
+ if (CHECK(string, '?') || CHECK(string, ':')) { | |
+ flow_indicators = 1; | |
+ if (followed_by_whitespace) { | |
+ block_indicators = 1; | |
+ } | |
+ } | |
+ | |
+ if (CHECK(string, '-') && followed_by_whitespace) { | |
+ flow_indicators = 1; | |
+ block_indicators = 1; | |
+ } | |
+ } | |
+ else | |
+ { | |
+ if (CHECK(string, ',') || CHECK(string, '?') | |
+ || CHECK(string, '[') || CHECK(string, ']') | |
+ || CHECK(string, '{') || CHECK(string, '}')) { | |
+ flow_indicators = 1; | |
+ } | |
+ | |
+ if (CHECK(string, ':')) { | |
+ flow_indicators = 1; | |
+ if (followed_by_whitespace) { | |
+ block_indicators = 1; | |
+ } | |
+ } | |
+ | |
+ if (CHECK(string, '#') && preceeded_by_whitespace) { | |
+ flow_indicators = 1; | |
+ block_indicators = 1; | |
+ } | |
+ } | |
+ | |
+ if (!IS_PRINTABLE(string) | |
+ || (!IS_ASCII(string) && !emitter->unicode)) { | |
+ special_characters = 1; | |
+ } | |
+ | |
+ if (IS_BREAK(string)) { | |
+ line_breaks = 1; | |
+ } | |
+ | |
+ if (IS_SPACE(string)) | |
+ { | |
+ if (string.start == string.pointer) { | |
+ leading_space = 1; | |
+ } | |
+ if (string.pointer+WIDTH(string) == string.end) { | |
+ trailing_space = 1; | |
+ } | |
+ if (previous_break) { | |
+ break_space = 1; | |
+ } | |
+ previous_space = 1; | |
+ previous_break = 0; | |
+ } | |
+ else if (IS_BREAK(string)) | |
+ { | |
+ if (string.start == string.pointer) { | |
+ leading_break = 1; | |
+ } | |
+ if (string.pointer+WIDTH(string) == string.end) { | |
+ trailing_break = 1; | |
+ } | |
+ if (previous_space) { | |
+ space_break = 1; | |
+ } | |
+ previous_space = 0; | |
+ previous_break = 1; | |
+ } | |
+ else | |
+ { | |
+ previous_space = 0; | |
+ previous_break = 0; | |
+ } | |
+ | |
+ preceeded_by_whitespace = IS_BLANKZ(string); | |
+ MOVE(string); | |
+ if (string.pointer != string.end) { | |
+ followed_by_whitespace = IS_BLANKZ_AT(string, WIDTH(string)); | |
+ } | |
+ } | |
+ | |
+ emitter->scalar_data.multiline = line_breaks; | |
+ | |
+ emitter->scalar_data.flow_plain_allowed = 1; | |
+ emitter->scalar_data.block_plain_allowed = 1; | |
+ emitter->scalar_data.single_quoted_allowed = 1; | |
+ emitter->scalar_data.block_allowed = 1; | |
+ | |
+ if (leading_space || leading_break || trailing_space || trailing_break) { | |
+ emitter->scalar_data.flow_plain_allowed = 0; | |
+ emitter->scalar_data.block_plain_allowed = 0; | |
+ } | |
+ | |
+ if (trailing_space) { | |
+ emitter->scalar_data.block_allowed = 0; | |
+ } | |
+ | |
+ if (break_space) { | |
+ emitter->scalar_data.flow_plain_allowed = 0; | |
+ emitter->scalar_data.block_plain_allowed = 0; | |
+ emitter->scalar_data.single_quoted_allowed = 0; | |
+ } | |
+ | |
+ if (space_break || special_characters) { | |
+ emitter->scalar_data.flow_plain_allowed = 0; | |
+ emitter->scalar_data.block_plain_allowed = 0; | |
+ emitter->scalar_data.single_quoted_allowed = 0; | |
+ emitter->scalar_data.block_allowed = 0; | |
+ } | |
+ | |
+ if (line_breaks) { | |
+ emitter->scalar_data.flow_plain_allowed = 0; | |
+ emitter->scalar_data.block_plain_allowed = 0; | |
+ } | |
+ | |
+ if (flow_indicators) { | |
+ emitter->scalar_data.flow_plain_allowed = 0; | |
+ } | |
+ | |
+ if (block_indicators) { | |
+ emitter->scalar_data.block_plain_allowed = 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if the event data is valid. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_analyze_event(yaml_emitter_t *emitter, | |
+ yaml_event_t *event) | |
+{ | |
+ emitter->anchor_data.anchor = NULL; | |
+ emitter->anchor_data.anchor_length = 0; | |
+ emitter->tag_data.handle = NULL; | |
+ emitter->tag_data.handle_length = 0; | |
+ emitter->tag_data.suffix = NULL; | |
+ emitter->tag_data.suffix_length = 0; | |
+ emitter->scalar_data.value = NULL; | |
+ emitter->scalar_data.length = 0; | |
+ | |
+ switch (event->type) | |
+ { | |
+ case YAML_ALIAS_EVENT: | |
+ if (!yaml_emitter_analyze_anchor(emitter, | |
+ event->data.alias.anchor, 1)) | |
+ return 0; | |
+ return 1; | |
+ | |
+ case YAML_SCALAR_EVENT: | |
+ if (event->data.scalar.anchor) { | |
+ if (!yaml_emitter_analyze_anchor(emitter, | |
+ event->data.scalar.anchor, 0)) | |
+ return 0; | |
+ } | |
+ if (event->data.scalar.tag && (emitter->canonical || | |
+ (!event->data.scalar.plain_implicit | |
+ && !event->data.scalar.quoted_implicit))) { | |
+ if (!yaml_emitter_analyze_tag(emitter, event->data.scalar.tag)) | |
+ return 0; | |
+ } | |
+ if (!yaml_emitter_analyze_scalar(emitter, | |
+ event->data.scalar.value, event->data.scalar.length)) | |
+ return 0; | |
+ return 1; | |
+ | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ if (event->data.sequence_start.anchor) { | |
+ if (!yaml_emitter_analyze_anchor(emitter, | |
+ event->data.sequence_start.anchor, 0)) | |
+ return 0; | |
+ } | |
+ if (event->data.sequence_start.tag && (emitter->canonical || | |
+ !event->data.sequence_start.implicit)) { | |
+ if (!yaml_emitter_analyze_tag(emitter, | |
+ event->data.sequence_start.tag)) | |
+ return 0; | |
+ } | |
+ return 1; | |
+ | |
+ case YAML_MAPPING_START_EVENT: | |
+ if (event->data.mapping_start.anchor) { | |
+ if (!yaml_emitter_analyze_anchor(emitter, | |
+ event->data.mapping_start.anchor, 0)) | |
+ return 0; | |
+ } | |
+ if (event->data.mapping_start.tag && (emitter->canonical || | |
+ !event->data.mapping_start.implicit)) { | |
+ if (!yaml_emitter_analyze_tag(emitter, | |
+ event->data.mapping_start.tag)) | |
+ return 0; | |
+ } | |
+ return 1; | |
+ | |
+ default: | |
+ return 1; | |
+ } | |
+} | |
+ | |
+/* | |
+ * Write the BOM character. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_write_bom(yaml_emitter_t *emitter) | |
+{ | |
+ if (!FLUSH(emitter)) return 0; | |
+ | |
+ *(emitter->buffer.pointer++) = (yaml_char_t) '\xEF'; | |
+ *(emitter->buffer.pointer++) = (yaml_char_t) '\xBB'; | |
+ *(emitter->buffer.pointer++) = (yaml_char_t) '\xBF'; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_indent(yaml_emitter_t *emitter) | |
+{ | |
+ int indent = (emitter->indent >= 0) ? emitter->indent : 0; | |
+ | |
+ if (!emitter->indention || emitter->column > indent | |
+ || (emitter->column == indent && !emitter->whitespace)) { | |
+ if (!PUT_BREAK(emitter)) return 0; | |
+ } | |
+ | |
+ while (emitter->column < indent) { | |
+ if (!PUT(emitter, ' ')) return 0; | |
+ } | |
+ | |
+ emitter->whitespace = 1; | |
+ emitter->indention = 1; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_indicator(yaml_emitter_t *emitter, | |
+ const char *indicator, int need_whitespace, | |
+ int is_whitespace, int is_indention) | |
+{ | |
+ size_t indicator_length; | |
+ yaml_string_t string; | |
+ | |
+ indicator_length = strlen(indicator); | |
+ STRING_ASSIGN(string, (yaml_char_t *)indicator, indicator_length); | |
+ | |
+ if (need_whitespace && !emitter->whitespace) { | |
+ if (!PUT(emitter, ' ')) return 0; | |
+ } | |
+ | |
+ while (string.pointer != string.end) { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ | |
+ emitter->whitespace = is_whitespace; | |
+ emitter->indention = (emitter->indention && is_indention); | |
+ emitter->open_ended = 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_anchor(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length) | |
+{ | |
+ yaml_string_t string; | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ while (string.pointer != string.end) { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ | |
+ emitter->whitespace = 0; | |
+ emitter->indention = 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_tag_handle(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length) | |
+{ | |
+ yaml_string_t string; | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ if (!emitter->whitespace) { | |
+ if (!PUT(emitter, ' ')) return 0; | |
+ } | |
+ | |
+ while (string.pointer != string.end) { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ | |
+ emitter->whitespace = 0; | |
+ emitter->indention = 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_tag_content(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, | |
+ int need_whitespace) | |
+{ | |
+ yaml_string_t string; | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ if (need_whitespace && !emitter->whitespace) { | |
+ if (!PUT(emitter, ' ')) return 0; | |
+ } | |
+ | |
+ while (string.pointer != string.end) { | |
+ if (IS_ALPHA(string) | |
+ || CHECK(string, ';') || CHECK(string, '/') | |
+ || CHECK(string, '?') || CHECK(string, ':') | |
+ || CHECK(string, '@') || CHECK(string, '&') | |
+ || CHECK(string, '=') || CHECK(string, '+') | |
+ || CHECK(string, '$') || CHECK(string, ',') | |
+ || CHECK(string, '_') || CHECK(string, '.') | |
+ || CHECK(string, '~') || CHECK(string, '*') | |
+ || CHECK(string, '\'') || CHECK(string, '(') | |
+ || CHECK(string, ')') || CHECK(string, '[') | |
+ || CHECK(string, ']')) { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ else { | |
+ int width = WIDTH(string); | |
+ unsigned int value; | |
+ while (width --) { | |
+ value = *(string.pointer++); | |
+ if (!PUT(emitter, '%')) return 0; | |
+ if (!PUT(emitter, (value >> 4) | |
+ + ((value >> 4) < 10 ? '0' : 'A' - 10))) | |
+ return 0; | |
+ if (!PUT(emitter, (value & 0x0F) | |
+ + ((value & 0x0F) < 10 ? '0' : 'A' - 10))) | |
+ return 0; | |
+ } | |
+ } | |
+ } | |
+ | |
+ emitter->whitespace = 0; | |
+ emitter->indention = 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_plain_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, int allow_breaks) | |
+{ | |
+ yaml_string_t string; | |
+ int spaces = 0; | |
+ int breaks = 0; | |
+ | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ if (!emitter->whitespace) { | |
+ if (!PUT(emitter, ' ')) return 0; | |
+ } | |
+ | |
+ while (string.pointer != string.end) | |
+ { | |
+ if (IS_SPACE(string)) | |
+ { | |
+ if (allow_breaks && !spaces | |
+ && emitter->column > emitter->best_width | |
+ && !IS_SPACE_AT(string, 1)) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ MOVE(string); | |
+ } | |
+ else { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ spaces = 1; | |
+ } | |
+ else if (IS_BREAK(string)) | |
+ { | |
+ if (!breaks && CHECK(string, '\n')) { | |
+ if (!PUT_BREAK(emitter)) return 0; | |
+ } | |
+ if (!WRITE_BREAK(emitter, string)) return 0; | |
+ emitter->indention = 1; | |
+ breaks = 1; | |
+ } | |
+ else | |
+ { | |
+ if (breaks) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ } | |
+ if (!WRITE(emitter, string)) return 0; | |
+ emitter->indention = 0; | |
+ spaces = 0; | |
+ breaks = 0; | |
+ } | |
+ } | |
+ | |
+ emitter->whitespace = 0; | |
+ emitter->indention = 0; | |
+ if (emitter->root_context) | |
+ { | |
+ emitter->open_ended = 1; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_single_quoted_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, int allow_breaks) | |
+{ | |
+ yaml_string_t string; | |
+ int spaces = 0; | |
+ int breaks = 0; | |
+ | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ if (!yaml_emitter_write_indicator(emitter, "'", 1, 0, 0)) | |
+ return 0; | |
+ | |
+ while (string.pointer != string.end) | |
+ { | |
+ if (IS_SPACE(string)) | |
+ { | |
+ if (allow_breaks && !spaces | |
+ && emitter->column > emitter->best_width | |
+ && string.pointer != string.start | |
+ && string.pointer != string.end - 1 | |
+ && !IS_SPACE_AT(string, 1)) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ MOVE(string); | |
+ } | |
+ else { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ spaces = 1; | |
+ } | |
+ else if (IS_BREAK(string)) | |
+ { | |
+ if (!breaks && CHECK(string, '\n')) { | |
+ if (!PUT_BREAK(emitter)) return 0; | |
+ } | |
+ if (!WRITE_BREAK(emitter, string)) return 0; | |
+ emitter->indention = 1; | |
+ breaks = 1; | |
+ } | |
+ else | |
+ { | |
+ if (breaks) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ } | |
+ if (CHECK(string, '\'')) { | |
+ if (!PUT(emitter, '\'')) return 0; | |
+ } | |
+ if (!WRITE(emitter, string)) return 0; | |
+ emitter->indention = 0; | |
+ spaces = 0; | |
+ breaks = 0; | |
+ } | |
+ } | |
+ | |
+ if (!yaml_emitter_write_indicator(emitter, "'", 0, 0, 0)) | |
+ return 0; | |
+ | |
+ emitter->whitespace = 0; | |
+ emitter->indention = 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_double_quoted_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length, int allow_breaks) | |
+{ | |
+ yaml_string_t string; | |
+ int spaces = 0; | |
+ | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ if (!yaml_emitter_write_indicator(emitter, "\"", 1, 0, 0)) | |
+ return 0; | |
+ | |
+ while (string.pointer != string.end) | |
+ { | |
+ if (!IS_PRINTABLE(string) || (!emitter->unicode && !IS_ASCII(string)) | |
+ || IS_BOM(string) || IS_BREAK(string) | |
+ || CHECK(string, '"') || CHECK(string, '\\')) | |
+ { | |
+ unsigned char octet; | |
+ unsigned int width; | |
+ unsigned int value; | |
+ int k; | |
+ | |
+ octet = string.pointer[0]; | |
+ width = (octet & 0x80) == 0x00 ? 1 : | |
+ (octet & 0xE0) == 0xC0 ? 2 : | |
+ (octet & 0xF0) == 0xE0 ? 3 : | |
+ (octet & 0xF8) == 0xF0 ? 4 : 0; | |
+ value = (octet & 0x80) == 0x00 ? octet & 0x7F : | |
+ (octet & 0xE0) == 0xC0 ? octet & 0x1F : | |
+ (octet & 0xF0) == 0xE0 ? octet & 0x0F : | |
+ (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; | |
+ for (k = 1; k < (int)width; k ++) { | |
+ octet = string.pointer[k]; | |
+ value = (value << 6) + (octet & 0x3F); | |
+ } | |
+ string.pointer += width; | |
+ | |
+ if (!PUT(emitter, '\\')) return 0; | |
+ | |
+ switch (value) | |
+ { | |
+ case 0x00: | |
+ if (!PUT(emitter, '0')) return 0; | |
+ break; | |
+ | |
+ case 0x07: | |
+ if (!PUT(emitter, 'a')) return 0; | |
+ break; | |
+ | |
+ case 0x08: | |
+ if (!PUT(emitter, 'b')) return 0; | |
+ break; | |
+ | |
+ case 0x09: | |
+ if (!PUT(emitter, 't')) return 0; | |
+ break; | |
+ | |
+ case 0x0A: | |
+ if (!PUT(emitter, 'n')) return 0; | |
+ break; | |
+ | |
+ case 0x0B: | |
+ if (!PUT(emitter, 'v')) return 0; | |
+ break; | |
+ | |
+ case 0x0C: | |
+ if (!PUT(emitter, 'f')) return 0; | |
+ break; | |
+ | |
+ case 0x0D: | |
+ if (!PUT(emitter, 'r')) return 0; | |
+ break; | |
+ | |
+ case 0x1B: | |
+ if (!PUT(emitter, 'e')) return 0; | |
+ break; | |
+ | |
+ case 0x22: | |
+ if (!PUT(emitter, '\"')) return 0; | |
+ break; | |
+ | |
+ case 0x5C: | |
+ if (!PUT(emitter, '\\')) return 0; | |
+ break; | |
+ | |
+ case 0x85: | |
+ if (!PUT(emitter, 'N')) return 0; | |
+ break; | |
+ | |
+ case 0xA0: | |
+ if (!PUT(emitter, '_')) return 0; | |
+ break; | |
+ | |
+ case 0x2028: | |
+ if (!PUT(emitter, 'L')) return 0; | |
+ break; | |
+ | |
+ case 0x2029: | |
+ if (!PUT(emitter, 'P')) return 0; | |
+ break; | |
+ | |
+ default: | |
+ if (value <= 0xFF) { | |
+ if (!PUT(emitter, 'x')) return 0; | |
+ width = 2; | |
+ } | |
+ else if (value <= 0xFFFF) { | |
+ if (!PUT(emitter, 'u')) return 0; | |
+ width = 4; | |
+ } | |
+ else { | |
+ if (!PUT(emitter, 'U')) return 0; | |
+ width = 8; | |
+ } | |
+ for (k = (width-1)*4; k >= 0; k -= 4) { | |
+ int digit = (value >> k) & 0x0F; | |
+ if (!PUT(emitter, digit + (digit < 10 ? '0' : 'A'-10))) | |
+ return 0; | |
+ } | |
+ } | |
+ spaces = 0; | |
+ } | |
+ else if (IS_SPACE(string)) | |
+ { | |
+ if (allow_breaks && !spaces | |
+ && emitter->column > emitter->best_width | |
+ && string.pointer != string.start | |
+ && string.pointer != string.end - 1) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ if (IS_SPACE_AT(string, 1)) { | |
+ if (!PUT(emitter, '\\')) return 0; | |
+ } | |
+ MOVE(string); | |
+ } | |
+ else { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ spaces = 1; | |
+ } | |
+ else | |
+ { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ spaces = 0; | |
+ } | |
+ } | |
+ | |
+ if (!yaml_emitter_write_indicator(emitter, "\"", 0, 0, 0)) | |
+ return 0; | |
+ | |
+ emitter->whitespace = 0; | |
+ emitter->indention = 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_block_scalar_hints(yaml_emitter_t *emitter, | |
+ yaml_string_t string) | |
+{ | |
+ char indent_hint[2]; | |
+ const char *chomp_hint = NULL; | |
+ | |
+ if (IS_SPACE(string) || IS_BREAK(string)) | |
+ { | |
+ indent_hint[0] = '0' + (char)emitter->best_indent; | |
+ indent_hint[1] = '\0'; | |
+ if (!yaml_emitter_write_indicator(emitter, indent_hint, 0, 0, 0)) | |
+ return 0; | |
+ } | |
+ | |
+ emitter->open_ended = 0; | |
+ | |
+ string.pointer = string.end; | |
+ if (string.start == string.pointer) | |
+ { | |
+ chomp_hint = "-"; | |
+ } | |
+ else | |
+ { | |
+ do { | |
+ string.pointer --; | |
+ } while ((*string.pointer & 0xC0) == 0x80); | |
+ if (!IS_BREAK(string)) | |
+ { | |
+ chomp_hint = "-"; | |
+ } | |
+ else if (string.start == string.pointer) | |
+ { | |
+ chomp_hint = "+"; | |
+ emitter->open_ended = 1; | |
+ } | |
+ else | |
+ { | |
+ do { | |
+ string.pointer --; | |
+ } while ((*string.pointer & 0xC0) == 0x80); | |
+ if (IS_BREAK(string)) | |
+ { | |
+ chomp_hint = "+"; | |
+ emitter->open_ended = 1; | |
+ } | |
+ } | |
+ } | |
+ | |
+ if (chomp_hint) | |
+ { | |
+ if (!yaml_emitter_write_indicator(emitter, chomp_hint, 0, 0, 0)) | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_literal_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length) | |
+{ | |
+ yaml_string_t string; | |
+ int breaks = 1; | |
+ | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ if (!yaml_emitter_write_indicator(emitter, "|", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_block_scalar_hints(emitter, string)) | |
+ return 0; | |
+ if (!PUT_BREAK(emitter)) return 0; | |
+ emitter->indention = 1; | |
+ emitter->whitespace = 1; | |
+ | |
+ while (string.pointer != string.end) | |
+ { | |
+ if (IS_BREAK(string)) | |
+ { | |
+ if (!WRITE_BREAK(emitter, string)) return 0; | |
+ emitter->indention = 1; | |
+ breaks = 1; | |
+ } | |
+ else | |
+ { | |
+ if (breaks) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ } | |
+ if (!WRITE(emitter, string)) return 0; | |
+ emitter->indention = 0; | |
+ breaks = 0; | |
+ } | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+static int | |
+yaml_emitter_write_folded_scalar(yaml_emitter_t *emitter, | |
+ yaml_char_t *value, size_t length) | |
+{ | |
+ yaml_string_t string; | |
+ int breaks = 1; | |
+ int leading_spaces = 1; | |
+ | |
+ STRING_ASSIGN(string, value, length); | |
+ | |
+ if (!yaml_emitter_write_indicator(emitter, ">", 1, 0, 0)) | |
+ return 0; | |
+ if (!yaml_emitter_write_block_scalar_hints(emitter, string)) | |
+ return 0; | |
+ if (!PUT_BREAK(emitter)) return 0; | |
+ emitter->indention = 1; | |
+ emitter->whitespace = 1; | |
+ | |
+ while (string.pointer != string.end) | |
+ { | |
+ if (IS_BREAK(string)) | |
+ { | |
+ if (!breaks && !leading_spaces && CHECK(string, '\n')) { | |
+ int k = 0; | |
+ while (IS_BREAK_AT(string, k)) { | |
+ k += WIDTH_AT(string, k); | |
+ } | |
+ if (!IS_BLANKZ_AT(string, k)) { | |
+ if (!PUT_BREAK(emitter)) return 0; | |
+ } | |
+ } | |
+ if (!WRITE_BREAK(emitter, string)) return 0; | |
+ emitter->indention = 1; | |
+ breaks = 1; | |
+ } | |
+ else | |
+ { | |
+ if (breaks) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ leading_spaces = IS_BLANK(string); | |
+ } | |
+ if (!breaks && IS_SPACE(string) && !IS_SPACE_AT(string, 1) | |
+ && emitter->column > emitter->best_width) { | |
+ if (!yaml_emitter_write_indent(emitter)) return 0; | |
+ MOVE(string); | |
+ } | |
+ else { | |
+ if (!WRITE(emitter, string)) return 0; | |
+ } | |
+ emitter->indention = 0; | |
+ breaks = 0; | |
+ } | |
+ } | |
+ | |
+ return 1; | |
+} | |
diff --git a/ext/psych/yaml/loader.c b/ext/psych/yaml/loader.c | |
new file mode 100644 | |
index 0000000..867b299 | |
--- /dev/null | |
+++ b/ext/psych/yaml/loader.c | |
@@ -0,0 +1,431 @@ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * API functions. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document); | |
+ | |
+/* | |
+ * Error handling. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_composer_error(yaml_parser_t *parser, | |
+ const char *problem, yaml_mark_t problem_mark); | |
+ | |
+static int | |
+yaml_parser_set_composer_error_context(yaml_parser_t *parser, | |
+ const char *context, yaml_mark_t context_mark, | |
+ const char *problem, yaml_mark_t problem_mark); | |
+ | |
+ | |
+/* | |
+ * Alias handling. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_register_anchor(yaml_parser_t *parser, | |
+ int index, yaml_char_t *anchor); | |
+ | |
+/* | |
+ * Clean up functions. | |
+ */ | |
+ | |
+static void | |
+yaml_parser_delete_aliases(yaml_parser_t *parser); | |
+ | |
+/* | |
+ * Composer functions. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_load_document(yaml_parser_t *parser, yaml_event_t *first_event); | |
+ | |
+static int | |
+yaml_parser_load_node(yaml_parser_t *parser, yaml_event_t *first_event); | |
+ | |
+static int | |
+yaml_parser_load_alias(yaml_parser_t *parser, yaml_event_t *first_event); | |
+ | |
+static int | |
+yaml_parser_load_scalar(yaml_parser_t *parser, yaml_event_t *first_event); | |
+ | |
+static int | |
+yaml_parser_load_sequence(yaml_parser_t *parser, yaml_event_t *first_event); | |
+ | |
+static int | |
+yaml_parser_load_mapping(yaml_parser_t *parser, yaml_event_t *first_event); | |
+ | |
+/* | |
+ * Load the next document of the stream. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document) | |
+{ | |
+ yaml_event_t event; | |
+ | |
+ assert(parser); /* Non-NULL parser object is expected. */ | |
+ assert(document); /* Non-NULL document object is expected. */ | |
+ | |
+ memset(document, 0, sizeof(yaml_document_t)); | |
+ if (!STACK_INIT(parser, document->nodes, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ | |
+ if (!parser->stream_start_produced) { | |
+ if (!yaml_parser_parse(parser, &event)) goto error; | |
+ assert(event.type == YAML_STREAM_START_EVENT); | |
+ /* STREAM-START is expected. */ | |
+ } | |
+ | |
+ if (parser->stream_end_produced) { | |
+ return 1; | |
+ } | |
+ | |
+ if (!yaml_parser_parse(parser, &event)) goto error; | |
+ if (event.type == YAML_STREAM_END_EVENT) { | |
+ return 1; | |
+ } | |
+ | |
+ if (!STACK_INIT(parser, parser->aliases, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ | |
+ parser->document = document; | |
+ | |
+ if (!yaml_parser_load_document(parser, &event)) goto error; | |
+ | |
+ yaml_parser_delete_aliases(parser); | |
+ parser->document = NULL; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ | |
+ yaml_parser_delete_aliases(parser); | |
+ yaml_document_delete(document); | |
+ parser->document = NULL; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Set composer error. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_composer_error(yaml_parser_t *parser, | |
+ const char *problem, yaml_mark_t problem_mark) | |
+{ | |
+ parser->error = YAML_COMPOSER_ERROR; | |
+ parser->problem = problem; | |
+ parser->problem_mark = problem_mark; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Set composer error with context. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_composer_error_context(yaml_parser_t *parser, | |
+ const char *context, yaml_mark_t context_mark, | |
+ const char *problem, yaml_mark_t problem_mark) | |
+{ | |
+ parser->error = YAML_COMPOSER_ERROR; | |
+ parser->context = context; | |
+ parser->context_mark = context_mark; | |
+ parser->problem = problem; | |
+ parser->problem_mark = problem_mark; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Delete the stack of aliases. | |
+ */ | |
+ | |
+static void | |
+yaml_parser_delete_aliases(yaml_parser_t *parser) | |
+{ | |
+ while (!STACK_EMPTY(parser, parser->aliases)) { | |
+ yaml_free(POP(parser, parser->aliases).anchor); | |
+ } | |
+ STACK_DEL(parser, parser->aliases); | |
+} | |
+ | |
+/* | |
+ * Compose a document object. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_load_document(yaml_parser_t *parser, yaml_event_t *first_event) | |
+{ | |
+ yaml_event_t event; | |
+ | |
+ assert(first_event->type == YAML_DOCUMENT_START_EVENT); | |
+ /* DOCUMENT-START is expected. */ | |
+ | |
+ parser->document->version_directive | |
+ = first_event->data.document_start.version_directive; | |
+ parser->document->tag_directives.start | |
+ = first_event->data.document_start.tag_directives.start; | |
+ parser->document->tag_directives.end | |
+ = first_event->data.document_start.tag_directives.end; | |
+ parser->document->start_implicit | |
+ = first_event->data.document_start.implicit; | |
+ parser->document->start_mark = first_event->start_mark; | |
+ | |
+ if (!yaml_parser_parse(parser, &event)) return 0; | |
+ | |
+ if (!yaml_parser_load_node(parser, &event)) return 0; | |
+ | |
+ if (!yaml_parser_parse(parser, &event)) return 0; | |
+ assert(event.type == YAML_DOCUMENT_END_EVENT); | |
+ /* DOCUMENT-END is expected. */ | |
+ | |
+ parser->document->end_implicit = event.data.document_end.implicit; | |
+ parser->document->end_mark = event.end_mark; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Compose a node. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_load_node(yaml_parser_t *parser, yaml_event_t *first_event) | |
+{ | |
+ switch (first_event->type) { | |
+ case YAML_ALIAS_EVENT: | |
+ return yaml_parser_load_alias(parser, first_event); | |
+ case YAML_SCALAR_EVENT: | |
+ return yaml_parser_load_scalar(parser, first_event); | |
+ case YAML_SEQUENCE_START_EVENT: | |
+ return yaml_parser_load_sequence(parser, first_event); | |
+ case YAML_MAPPING_START_EVENT: | |
+ return yaml_parser_load_mapping(parser, first_event); | |
+ default: | |
+ assert(0); /* Could not happen. */ | |
+ return 0; | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Add an anchor. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_register_anchor(yaml_parser_t *parser, | |
+ int index, yaml_char_t *anchor) | |
+{ | |
+ yaml_alias_data_t data; | |
+ yaml_alias_data_t *alias_data; | |
+ | |
+ if (!anchor) return 1; | |
+ | |
+ data.anchor = anchor; | |
+ data.index = index; | |
+ data.mark = parser->document->nodes.start[index-1].start_mark; | |
+ | |
+ for (alias_data = parser->aliases.start; | |
+ alias_data != parser->aliases.top; alias_data ++) { | |
+ if (strcmp((char *)alias_data->anchor, (char *)anchor) == 0) { | |
+ yaml_free(anchor); | |
+ return yaml_parser_set_composer_error_context(parser, | |
+ "found duplicate anchor; first occurence", | |
+ alias_data->mark, "second occurence", data.mark); | |
+ } | |
+ } | |
+ | |
+ if (!PUSH(parser, parser->aliases, data)) { | |
+ yaml_free(anchor); | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Compose a node corresponding to an alias. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_load_alias(yaml_parser_t *parser, yaml_event_t *first_event) | |
+{ | |
+ yaml_char_t *anchor = first_event->data.alias.anchor; | |
+ yaml_alias_data_t *alias_data; | |
+ | |
+ for (alias_data = parser->aliases.start; | |
+ alias_data != parser->aliases.top; alias_data ++) { | |
+ if (strcmp((char *)alias_data->anchor, (char *)anchor) == 0) { | |
+ yaml_free(anchor); | |
+ return alias_data->index; | |
+ } | |
+ } | |
+ | |
+ yaml_free(anchor); | |
+ return yaml_parser_set_composer_error(parser, "found undefined alias", | |
+ first_event->start_mark); | |
+} | |
+ | |
+/* | |
+ * Compose a scalar node. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_load_scalar(yaml_parser_t *parser, yaml_event_t *first_event) | |
+{ | |
+ yaml_node_t node; | |
+ int index; | |
+ yaml_char_t *tag = first_event->data.scalar.tag; | |
+ | |
+ if (!tag || strcmp((char *)tag, "!") == 0) { | |
+ yaml_free(tag); | |
+ tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_SCALAR_TAG); | |
+ if (!tag) goto error; | |
+ } | |
+ | |
+ SCALAR_NODE_INIT(node, tag, first_event->data.scalar.value, | |
+ first_event->data.scalar.length, first_event->data.scalar.style, | |
+ first_event->start_mark, first_event->end_mark); | |
+ | |
+ if (!PUSH(parser, parser->document->nodes, node)) goto error; | |
+ | |
+ index = parser->document->nodes.top - parser->document->nodes.start; | |
+ | |
+ if (!yaml_parser_register_anchor(parser, index, | |
+ first_event->data.scalar.anchor)) return 0; | |
+ | |
+ return index; | |
+ | |
+error: | |
+ yaml_free(tag); | |
+ yaml_free(first_event->data.scalar.anchor); | |
+ yaml_free(first_event->data.scalar.value); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Compose a sequence node. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_load_sequence(yaml_parser_t *parser, yaml_event_t *first_event) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_node_t node; | |
+ struct { | |
+ yaml_node_item_t *start; | |
+ yaml_node_item_t *end; | |
+ yaml_node_item_t *top; | |
+ } items = { NULL, NULL, NULL }; | |
+ int index, item_index; | |
+ yaml_char_t *tag = first_event->data.sequence_start.tag; | |
+ | |
+ if (!tag || strcmp((char *)tag, "!") == 0) { | |
+ yaml_free(tag); | |
+ tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG); | |
+ if (!tag) goto error; | |
+ } | |
+ | |
+ if (!STACK_INIT(parser, items, INITIAL_STACK_SIZE)) goto error; | |
+ | |
+ SEQUENCE_NODE_INIT(node, tag, items.start, items.end, | |
+ first_event->data.sequence_start.style, | |
+ first_event->start_mark, first_event->end_mark); | |
+ | |
+ if (!PUSH(parser, parser->document->nodes, node)) goto error; | |
+ | |
+ index = parser->document->nodes.top - parser->document->nodes.start; | |
+ | |
+ if (!yaml_parser_register_anchor(parser, index, | |
+ first_event->data.sequence_start.anchor)) return 0; | |
+ | |
+ if (!yaml_parser_parse(parser, &event)) return 0; | |
+ | |
+ while (event.type != YAML_SEQUENCE_END_EVENT) { | |
+ item_index = yaml_parser_load_node(parser, &event); | |
+ if (!item_index) return 0; | |
+ if (!PUSH(parser, | |
+ parser->document->nodes.start[index-1].data.sequence.items, | |
+ item_index)) return 0; | |
+ if (!yaml_parser_parse(parser, &event)) return 0; | |
+ } | |
+ | |
+ parser->document->nodes.start[index-1].end_mark = event.end_mark; | |
+ | |
+ return index; | |
+ | |
+error: | |
+ yaml_free(tag); | |
+ yaml_free(first_event->data.sequence_start.anchor); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Compose a mapping node. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_load_mapping(yaml_parser_t *parser, yaml_event_t *first_event) | |
+{ | |
+ yaml_event_t event; | |
+ yaml_node_t node; | |
+ struct { | |
+ yaml_node_pair_t *start; | |
+ yaml_node_pair_t *end; | |
+ yaml_node_pair_t *top; | |
+ } pairs = { NULL, NULL, NULL }; | |
+ int index; | |
+ yaml_node_pair_t pair; | |
+ yaml_char_t *tag = first_event->data.mapping_start.tag; | |
+ | |
+ if (!tag || strcmp((char *)tag, "!") == 0) { | |
+ yaml_free(tag); | |
+ tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_MAPPING_TAG); | |
+ if (!tag) goto error; | |
+ } | |
+ | |
+ if (!STACK_INIT(parser, pairs, INITIAL_STACK_SIZE)) goto error; | |
+ | |
+ MAPPING_NODE_INIT(node, tag, pairs.start, pairs.end, | |
+ first_event->data.mapping_start.style, | |
+ first_event->start_mark, first_event->end_mark); | |
+ | |
+ if (!PUSH(parser, parser->document->nodes, node)) goto error; | |
+ | |
+ index = parser->document->nodes.top - parser->document->nodes.start; | |
+ | |
+ if (!yaml_parser_register_anchor(parser, index, | |
+ first_event->data.mapping_start.anchor)) return 0; | |
+ | |
+ if (!yaml_parser_parse(parser, &event)) return 0; | |
+ | |
+ while (event.type != YAML_MAPPING_END_EVENT) { | |
+ pair.key = yaml_parser_load_node(parser, &event); | |
+ if (!pair.key) return 0; | |
+ if (!yaml_parser_parse(parser, &event)) return 0; | |
+ pair.value = yaml_parser_load_node(parser, &event); | |
+ if (!pair.value) return 0; | |
+ if (!PUSH(parser, | |
+ parser->document->nodes.start[index-1].data.mapping.pairs, | |
+ pair)) return 0; | |
+ if (!yaml_parser_parse(parser, &event)) return 0; | |
+ } | |
+ | |
+ parser->document->nodes.start[index-1].end_mark = event.end_mark; | |
+ | |
+ return index; | |
+ | |
+error: | |
+ yaml_free(tag); | |
+ yaml_free(first_event->data.mapping_start.anchor); | |
+ return 0; | |
+} | |
diff --git a/ext/psych/yaml/parser.c b/ext/psych/yaml/parser.c | |
new file mode 100644 | |
index 0000000..5013339 | |
--- /dev/null | |
+++ b/ext/psych/yaml/parser.c | |
@@ -0,0 +1,1373 @@ | |
+ | |
+/* | |
+ * The parser implements the following grammar: | |
+ * | |
+ * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END | |
+ * implicit_document ::= block_node DOCUMENT-END* | |
+ * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* | |
+ * block_node_or_indentless_sequence ::= | |
+ * ALIAS | |
+ * | properties (block_content | indentless_block_sequence)? | |
+ * | block_content | |
+ * | indentless_block_sequence | |
+ * block_node ::= ALIAS | |
+ * | properties block_content? | |
+ * | block_content | |
+ * flow_node ::= ALIAS | |
+ * | properties flow_content? | |
+ * | flow_content | |
+ * properties ::= TAG ANCHOR? | ANCHOR TAG? | |
+ * block_content ::= block_collection | flow_collection | SCALAR | |
+ * flow_content ::= flow_collection | SCALAR | |
+ * block_collection ::= block_sequence | block_mapping | |
+ * flow_collection ::= flow_sequence | flow_mapping | |
+ * block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END | |
+ * indentless_sequence ::= (BLOCK-ENTRY block_node?)+ | |
+ * block_mapping ::= BLOCK-MAPPING_START | |
+ * ((KEY block_node_or_indentless_sequence?)? | |
+ * (VALUE block_node_or_indentless_sequence?)?)* | |
+ * BLOCK-END | |
+ * flow_sequence ::= FLOW-SEQUENCE-START | |
+ * (flow_sequence_entry FLOW-ENTRY)* | |
+ * flow_sequence_entry? | |
+ * FLOW-SEQUENCE-END | |
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ * flow_mapping ::= FLOW-MAPPING-START | |
+ * (flow_mapping_entry FLOW-ENTRY)* | |
+ * flow_mapping_entry? | |
+ * FLOW-MAPPING-END | |
+ * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ */ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * Peek the next token in the token queue. | |
+ */ | |
+ | |
+#define PEEK_TOKEN(parser) \ | |
+ ((parser->token_available || yaml_parser_fetch_more_tokens(parser)) ? \ | |
+ parser->tokens.head : NULL) | |
+ | |
+/* | |
+ * Remove the next token from the queue (must be called after PEEK_TOKEN). | |
+ */ | |
+ | |
+#define SKIP_TOKEN(parser) \ | |
+ (parser->token_available = 0, \ | |
+ parser->tokens_parsed ++, \ | |
+ parser->stream_end_produced = \ | |
+ (parser->tokens.head->type == YAML_STREAM_END_TOKEN), \ | |
+ parser->tokens.head ++) | |
+ | |
+/* | |
+ * Public API declarations. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event); | |
+ | |
+/* | |
+ * Error handling. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_parser_error(yaml_parser_t *parser, | |
+ const char *problem, yaml_mark_t problem_mark); | |
+ | |
+static int | |
+yaml_parser_set_parser_error_context(yaml_parser_t *parser, | |
+ const char *context, yaml_mark_t context_mark, | |
+ const char *problem, yaml_mark_t problem_mark); | |
+ | |
+/* | |
+ * State functions. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_state_machine(yaml_parser_t *parser, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_stream_start(yaml_parser_t *parser, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_document_start(yaml_parser_t *parser, yaml_event_t *event, | |
+ int implicit); | |
+ | |
+static int | |
+yaml_parser_parse_document_content(yaml_parser_t *parser, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_document_end(yaml_parser_t *parser, yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, | |
+ int block, int indentless_sequence); | |
+ | |
+static int | |
+yaml_parser_parse_block_sequence_entry(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_parser_parse_indentless_sequence_entry(yaml_parser_t *parser, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_block_mapping_key(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_parser_parse_block_mapping_value(yaml_parser_t *parser, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry_mapping_key(yaml_parser_t *parser, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry_mapping_value(yaml_parser_t *parser, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry_mapping_end(yaml_parser_t *parser, | |
+ yaml_event_t *event); | |
+ | |
+static int | |
+yaml_parser_parse_flow_mapping_key(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first); | |
+ | |
+static int | |
+yaml_parser_parse_flow_mapping_value(yaml_parser_t *parser, | |
+ yaml_event_t *event, int empty); | |
+ | |
+/* | |
+ * Utility functions. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_process_empty_scalar(yaml_parser_t *parser, | |
+ yaml_event_t *event, yaml_mark_t mark); | |
+ | |
+static int | |
+yaml_parser_process_directives(yaml_parser_t *parser, | |
+ yaml_version_directive_t **version_directive_ref, | |
+ yaml_tag_directive_t **tag_directives_start_ref, | |
+ yaml_tag_directive_t **tag_directives_end_ref); | |
+ | |
+static int | |
+yaml_parser_append_tag_directive(yaml_parser_t *parser, | |
+ yaml_tag_directive_t value, int allow_duplicates, yaml_mark_t mark); | |
+ | |
+/* | |
+ * Get the next event. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event) | |
+{ | |
+ assert(parser); /* Non-NULL parser object is expected. */ | |
+ assert(event); /* Non-NULL event object is expected. */ | |
+ | |
+ /* Erase the event object. */ | |
+ | |
+ memset(event, 0, sizeof(yaml_event_t)); | |
+ | |
+ /* No events after the end of the stream or error. */ | |
+ | |
+ if (parser->stream_end_produced || parser->error || | |
+ parser->state == YAML_PARSE_END_STATE) { | |
+ return 1; | |
+ } | |
+ | |
+ /* Generate the next event. */ | |
+ | |
+ return yaml_parser_state_machine(parser, event); | |
+} | |
+ | |
+/* | |
+ * Set parser error. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_parser_error(yaml_parser_t *parser, | |
+ const char *problem, yaml_mark_t problem_mark) | |
+{ | |
+ parser->error = YAML_PARSER_ERROR; | |
+ parser->problem = problem; | |
+ parser->problem_mark = problem_mark; | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int | |
+yaml_parser_set_parser_error_context(yaml_parser_t *parser, | |
+ const char *context, yaml_mark_t context_mark, | |
+ const char *problem, yaml_mark_t problem_mark) | |
+{ | |
+ parser->error = YAML_PARSER_ERROR; | |
+ parser->context = context; | |
+ parser->context_mark = context_mark; | |
+ parser->problem = problem; | |
+ parser->problem_mark = problem_mark; | |
+ | |
+ return 0; | |
+} | |
+ | |
+ | |
+/* | |
+ * State dispatcher. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_state_machine(yaml_parser_t *parser, yaml_event_t *event) | |
+{ | |
+ switch (parser->state) | |
+ { | |
+ case YAML_PARSE_STREAM_START_STATE: | |
+ return yaml_parser_parse_stream_start(parser, event); | |
+ | |
+ case YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE: | |
+ return yaml_parser_parse_document_start(parser, event, 1); | |
+ | |
+ case YAML_PARSE_DOCUMENT_START_STATE: | |
+ return yaml_parser_parse_document_start(parser, event, 0); | |
+ | |
+ case YAML_PARSE_DOCUMENT_CONTENT_STATE: | |
+ return yaml_parser_parse_document_content(parser, event); | |
+ | |
+ case YAML_PARSE_DOCUMENT_END_STATE: | |
+ return yaml_parser_parse_document_end(parser, event); | |
+ | |
+ case YAML_PARSE_BLOCK_NODE_STATE: | |
+ return yaml_parser_parse_node(parser, event, 1, 0); | |
+ | |
+ case YAML_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: | |
+ return yaml_parser_parse_node(parser, event, 1, 1); | |
+ | |
+ case YAML_PARSE_FLOW_NODE_STATE: | |
+ return yaml_parser_parse_node(parser, event, 0, 0); | |
+ | |
+ case YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: | |
+ return yaml_parser_parse_block_sequence_entry(parser, event, 1); | |
+ | |
+ case YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: | |
+ return yaml_parser_parse_block_sequence_entry(parser, event, 0); | |
+ | |
+ case YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: | |
+ return yaml_parser_parse_indentless_sequence_entry(parser, event); | |
+ | |
+ case YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: | |
+ return yaml_parser_parse_block_mapping_key(parser, event, 1); | |
+ | |
+ case YAML_PARSE_BLOCK_MAPPING_KEY_STATE: | |
+ return yaml_parser_parse_block_mapping_key(parser, event, 0); | |
+ | |
+ case YAML_PARSE_BLOCK_MAPPING_VALUE_STATE: | |
+ return yaml_parser_parse_block_mapping_value(parser, event); | |
+ | |
+ case YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: | |
+ return yaml_parser_parse_flow_sequence_entry(parser, event, 1); | |
+ | |
+ case YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE: | |
+ return yaml_parser_parse_flow_sequence_entry(parser, event, 0); | |
+ | |
+ case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: | |
+ return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event); | |
+ | |
+ case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: | |
+ return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event); | |
+ | |
+ case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: | |
+ return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event); | |
+ | |
+ case YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: | |
+ return yaml_parser_parse_flow_mapping_key(parser, event, 1); | |
+ | |
+ case YAML_PARSE_FLOW_MAPPING_KEY_STATE: | |
+ return yaml_parser_parse_flow_mapping_key(parser, event, 0); | |
+ | |
+ case YAML_PARSE_FLOW_MAPPING_VALUE_STATE: | |
+ return yaml_parser_parse_flow_mapping_value(parser, event, 0); | |
+ | |
+ case YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: | |
+ return yaml_parser_parse_flow_mapping_value(parser, event, 1); | |
+ | |
+ default: | |
+ assert(1); /* Invalid state. */ | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Parse the production: | |
+ * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END | |
+ * ************ | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_stream_start(yaml_parser_t *parser, yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type != YAML_STREAM_START_TOKEN) { | |
+ return yaml_parser_set_parser_error(parser, | |
+ "did not find expected <stream-start>", token->start_mark); | |
+ } | |
+ | |
+ parser->state = YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE; | |
+ STREAM_START_EVENT_INIT(*event, token->data.stream_start.encoding, | |
+ token->start_mark, token->start_mark); | |
+ SKIP_TOKEN(parser); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * implicit_document ::= block_node DOCUMENT-END* | |
+ * * | |
+ * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* | |
+ * ************************* | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_document_start(yaml_parser_t *parser, yaml_event_t *event, | |
+ int implicit) | |
+{ | |
+ yaml_token_t *token; | |
+ yaml_version_directive_t *version_directive = NULL; | |
+ struct { | |
+ yaml_tag_directive_t *start; | |
+ yaml_tag_directive_t *end; | |
+ } tag_directives = { NULL, NULL }; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ /* Parse extra document end indicators. */ | |
+ | |
+ if (!implicit) | |
+ { | |
+ while (token->type == YAML_DOCUMENT_END_TOKEN) { | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ } | |
+ } | |
+ | |
+ /* Parse an implicit document. */ | |
+ | |
+ if (implicit && token->type != YAML_VERSION_DIRECTIVE_TOKEN && | |
+ token->type != YAML_TAG_DIRECTIVE_TOKEN && | |
+ token->type != YAML_DOCUMENT_START_TOKEN && | |
+ token->type != YAML_STREAM_END_TOKEN) | |
+ { | |
+ if (!yaml_parser_process_directives(parser, NULL, NULL, NULL)) | |
+ return 0; | |
+ if (!PUSH(parser, parser->states, YAML_PARSE_DOCUMENT_END_STATE)) | |
+ return 0; | |
+ parser->state = YAML_PARSE_BLOCK_NODE_STATE; | |
+ DOCUMENT_START_EVENT_INIT(*event, NULL, NULL, NULL, 1, | |
+ token->start_mark, token->start_mark); | |
+ return 1; | |
+ } | |
+ | |
+ /* Parse an explicit document. */ | |
+ | |
+ else if (token->type != YAML_STREAM_END_TOKEN) | |
+ { | |
+ yaml_mark_t start_mark, end_mark; | |
+ start_mark = token->start_mark; | |
+ if (!yaml_parser_process_directives(parser, &version_directive, | |
+ &tag_directives.start, &tag_directives.end)) | |
+ return 0; | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) goto error; | |
+ if (token->type != YAML_DOCUMENT_START_TOKEN) { | |
+ yaml_parser_set_parser_error(parser, | |
+ "did not find expected <document start>", token->start_mark); | |
+ goto error; | |
+ } | |
+ if (!PUSH(parser, parser->states, YAML_PARSE_DOCUMENT_END_STATE)) | |
+ goto error; | |
+ parser->state = YAML_PARSE_DOCUMENT_CONTENT_STATE; | |
+ end_mark = token->end_mark; | |
+ DOCUMENT_START_EVENT_INIT(*event, version_directive, | |
+ tag_directives.start, tag_directives.end, 0, | |
+ start_mark, end_mark); | |
+ SKIP_TOKEN(parser); | |
+ version_directive = NULL; | |
+ tag_directives.start = tag_directives.end = NULL; | |
+ return 1; | |
+ } | |
+ | |
+ /* Parse the stream end. */ | |
+ | |
+ else | |
+ { | |
+ parser->state = YAML_PARSE_END_STATE; | |
+ STREAM_END_EVENT_INIT(*event, token->start_mark, token->end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+ } | |
+ | |
+error: | |
+ yaml_free(version_directive); | |
+ while (tag_directives.start != tag_directives.end) { | |
+ yaml_free(tag_directives.end[-1].handle); | |
+ yaml_free(tag_directives.end[-1].prefix); | |
+ tag_directives.end --; | |
+ } | |
+ yaml_free(tag_directives.start); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* | |
+ * *********** | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_document_content(yaml_parser_t *parser, yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type == YAML_VERSION_DIRECTIVE_TOKEN || | |
+ token->type == YAML_TAG_DIRECTIVE_TOKEN || | |
+ token->type == YAML_DOCUMENT_START_TOKEN || | |
+ token->type == YAML_DOCUMENT_END_TOKEN || | |
+ token->type == YAML_STREAM_END_TOKEN) { | |
+ parser->state = POP(parser, parser->states); | |
+ return yaml_parser_process_empty_scalar(parser, event, | |
+ token->start_mark); | |
+ } | |
+ else { | |
+ return yaml_parser_parse_node(parser, event, 1, 0); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * implicit_document ::= block_node DOCUMENT-END* | |
+ * ************* | |
+ * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* | |
+ * ************* | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_document_end(yaml_parser_t *parser, yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ yaml_mark_t start_mark, end_mark; | |
+ int implicit = 1; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ start_mark = end_mark = token->start_mark; | |
+ | |
+ if (token->type == YAML_DOCUMENT_END_TOKEN) { | |
+ end_mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ implicit = 0; | |
+ } | |
+ | |
+ while (!STACK_EMPTY(parser, parser->tag_directives)) { | |
+ yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); | |
+ yaml_free(tag_directive.handle); | |
+ yaml_free(tag_directive.prefix); | |
+ } | |
+ | |
+ parser->state = YAML_PARSE_DOCUMENT_START_STATE; | |
+ DOCUMENT_END_EVENT_INIT(*event, implicit, start_mark, end_mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * block_node_or_indentless_sequence ::= | |
+ * ALIAS | |
+ * ***** | |
+ * | properties (block_content | indentless_block_sequence)? | |
+ * ********** * | |
+ * | block_content | indentless_block_sequence | |
+ * * | |
+ * block_node ::= ALIAS | |
+ * ***** | |
+ * | properties block_content? | |
+ * ********** * | |
+ * | block_content | |
+ * * | |
+ * flow_node ::= ALIAS | |
+ * ***** | |
+ * | properties flow_content? | |
+ * ********** * | |
+ * | flow_content | |
+ * * | |
+ * properties ::= TAG ANCHOR? | ANCHOR TAG? | |
+ * ************************* | |
+ * block_content ::= block_collection | flow_collection | SCALAR | |
+ * ****** | |
+ * flow_content ::= flow_collection | SCALAR | |
+ * ****** | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, | |
+ int block, int indentless_sequence) | |
+{ | |
+ yaml_token_t *token; | |
+ yaml_char_t *anchor = NULL; | |
+ yaml_char_t *tag_handle = NULL; | |
+ yaml_char_t *tag_suffix = NULL; | |
+ yaml_char_t *tag = NULL; | |
+ yaml_mark_t start_mark, end_mark, tag_mark; | |
+ int implicit; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type == YAML_ALIAS_TOKEN) | |
+ { | |
+ parser->state = POP(parser, parser->states); | |
+ ALIAS_EVENT_INIT(*event, token->data.alias.value, | |
+ token->start_mark, token->end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+ } | |
+ | |
+ else | |
+ { | |
+ start_mark = end_mark = token->start_mark; | |
+ | |
+ if (token->type == YAML_ANCHOR_TOKEN) | |
+ { | |
+ anchor = token->data.anchor.value; | |
+ start_mark = token->start_mark; | |
+ end_mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) goto error; | |
+ if (token->type == YAML_TAG_TOKEN) | |
+ { | |
+ tag_handle = token->data.tag.handle; | |
+ tag_suffix = token->data.tag.suffix; | |
+ tag_mark = token->start_mark; | |
+ end_mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) goto error; | |
+ } | |
+ } | |
+ else if (token->type == YAML_TAG_TOKEN) | |
+ { | |
+ tag_handle = token->data.tag.handle; | |
+ tag_suffix = token->data.tag.suffix; | |
+ start_mark = tag_mark = token->start_mark; | |
+ end_mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) goto error; | |
+ if (token->type == YAML_ANCHOR_TOKEN) | |
+ { | |
+ anchor = token->data.anchor.value; | |
+ end_mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) goto error; | |
+ } | |
+ } | |
+ | |
+ if (tag_handle) { | |
+ if (!*tag_handle) { | |
+ tag = tag_suffix; | |
+ yaml_free(tag_handle); | |
+ tag_handle = tag_suffix = NULL; | |
+ } | |
+ else { | |
+ yaml_tag_directive_t *tag_directive; | |
+ for (tag_directive = parser->tag_directives.start; | |
+ tag_directive != parser->tag_directives.top; | |
+ tag_directive ++) { | |
+ if (strcmp((char *)tag_directive->handle, (char *)tag_handle) == 0) { | |
+ size_t prefix_len = strlen((char *)tag_directive->prefix); | |
+ size_t suffix_len = strlen((char *)tag_suffix); | |
+ tag = yaml_malloc(prefix_len+suffix_len+1); | |
+ if (!tag) { | |
+ parser->error = YAML_MEMORY_ERROR; | |
+ goto error; | |
+ } | |
+ memcpy(tag, tag_directive->prefix, prefix_len); | |
+ memcpy(tag+prefix_len, tag_suffix, suffix_len); | |
+ tag[prefix_len+suffix_len] = '\0'; | |
+ yaml_free(tag_handle); | |
+ yaml_free(tag_suffix); | |
+ tag_handle = tag_suffix = NULL; | |
+ break; | |
+ } | |
+ } | |
+ if (!tag) { | |
+ yaml_parser_set_parser_error_context(parser, | |
+ "while parsing a node", start_mark, | |
+ "found undefined tag handle", tag_mark); | |
+ goto error; | |
+ } | |
+ } | |
+ } | |
+ | |
+ implicit = (!tag || !*tag); | |
+ if (indentless_sequence && token->type == YAML_BLOCK_ENTRY_TOKEN) { | |
+ end_mark = token->end_mark; | |
+ parser->state = YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE; | |
+ SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, | |
+ YAML_BLOCK_SEQUENCE_STYLE, start_mark, end_mark); | |
+ return 1; | |
+ } | |
+ else { | |
+ if (token->type == YAML_SCALAR_TOKEN) { | |
+ int plain_implicit = 0; | |
+ int quoted_implicit = 0; | |
+ end_mark = token->end_mark; | |
+ if ((token->data.scalar.style == YAML_PLAIN_SCALAR_STYLE && !tag) | |
+ || (tag && strcmp((char *)tag, "!") == 0)) { | |
+ plain_implicit = 1; | |
+ } | |
+ else if (!tag) { | |
+ quoted_implicit = 1; | |
+ } | |
+ parser->state = POP(parser, parser->states); | |
+ SCALAR_EVENT_INIT(*event, anchor, tag, | |
+ token->data.scalar.value, token->data.scalar.length, | |
+ plain_implicit, quoted_implicit, | |
+ token->data.scalar.style, start_mark, end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+ } | |
+ else if (token->type == YAML_FLOW_SEQUENCE_START_TOKEN) { | |
+ end_mark = token->end_mark; | |
+ parser->state = YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE; | |
+ SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, | |
+ YAML_FLOW_SEQUENCE_STYLE, start_mark, end_mark); | |
+ return 1; | |
+ } | |
+ else if (token->type == YAML_FLOW_MAPPING_START_TOKEN) { | |
+ end_mark = token->end_mark; | |
+ parser->state = YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE; | |
+ MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, | |
+ YAML_FLOW_MAPPING_STYLE, start_mark, end_mark); | |
+ return 1; | |
+ } | |
+ else if (block && token->type == YAML_BLOCK_SEQUENCE_START_TOKEN) { | |
+ end_mark = token->end_mark; | |
+ parser->state = YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE; | |
+ SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, | |
+ YAML_BLOCK_SEQUENCE_STYLE, start_mark, end_mark); | |
+ return 1; | |
+ } | |
+ else if (block && token->type == YAML_BLOCK_MAPPING_START_TOKEN) { | |
+ end_mark = token->end_mark; | |
+ parser->state = YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE; | |
+ MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, | |
+ YAML_BLOCK_MAPPING_STYLE, start_mark, end_mark); | |
+ return 1; | |
+ } | |
+ else if (anchor || tag) { | |
+ yaml_char_t *value = yaml_malloc(1); | |
+ if (!value) { | |
+ parser->error = YAML_MEMORY_ERROR; | |
+ goto error; | |
+ } | |
+ value[0] = '\0'; | |
+ parser->state = POP(parser, parser->states); | |
+ SCALAR_EVENT_INIT(*event, anchor, tag, value, 0, | |
+ implicit, 0, YAML_PLAIN_SCALAR_STYLE, | |
+ start_mark, end_mark); | |
+ return 1; | |
+ } | |
+ else { | |
+ yaml_parser_set_parser_error_context(parser, | |
+ (block ? "while parsing a block node" | |
+ : "while parsing a flow node"), start_mark, | |
+ "did not find expected node content", token->start_mark); | |
+ goto error; | |
+ } | |
+ } | |
+ } | |
+ | |
+error: | |
+ yaml_free(anchor); | |
+ yaml_free(tag_handle); | |
+ yaml_free(tag_suffix); | |
+ yaml_free(tag); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END | |
+ * ******************** *********** * ********* | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_block_sequence_entry(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ if (first) { | |
+ token = PEEK_TOKEN(parser); | |
+ if (!PUSH(parser, parser->marks, token->start_mark)) | |
+ return 0; | |
+ SKIP_TOKEN(parser); | |
+ } | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type == YAML_BLOCK_ENTRY_TOKEN) | |
+ { | |
+ yaml_mark_t mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ if (token->type != YAML_BLOCK_ENTRY_TOKEN && | |
+ token->type != YAML_BLOCK_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 1, 0); | |
+ } | |
+ else { | |
+ parser->state = YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, mark); | |
+ } | |
+ } | |
+ | |
+ else if (token->type == YAML_BLOCK_END_TOKEN) | |
+ { | |
+ yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ | |
+ parser->state = POP(parser, parser->states); | |
+ dummy_mark = POP(parser, parser->marks); | |
+ SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+ } | |
+ | |
+ else | |
+ { | |
+ return yaml_parser_set_parser_error_context(parser, | |
+ "while parsing a block collection", POP(parser, parser->marks), | |
+ "did not find expected '-' indicator", token->start_mark); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * indentless_sequence ::= (BLOCK-ENTRY block_node?)+ | |
+ * *********** * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_indentless_sequence_entry(yaml_parser_t *parser, | |
+ yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type == YAML_BLOCK_ENTRY_TOKEN) | |
+ { | |
+ yaml_mark_t mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ if (token->type != YAML_BLOCK_ENTRY_TOKEN && | |
+ token->type != YAML_KEY_TOKEN && | |
+ token->type != YAML_VALUE_TOKEN && | |
+ token->type != YAML_BLOCK_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 1, 0); | |
+ } | |
+ else { | |
+ parser->state = YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, mark); | |
+ } | |
+ } | |
+ | |
+ else | |
+ { | |
+ parser->state = POP(parser, parser->states); | |
+ SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->start_mark); | |
+ return 1; | |
+ } | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * block_mapping ::= BLOCK-MAPPING_START | |
+ * ******************* | |
+ * ((KEY block_node_or_indentless_sequence?)? | |
+ * *** * | |
+ * (VALUE block_node_or_indentless_sequence?)?)* | |
+ * | |
+ * BLOCK-END | |
+ * ********* | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_block_mapping_key(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ if (first) { | |
+ token = PEEK_TOKEN(parser); | |
+ if (!PUSH(parser, parser->marks, token->start_mark)) | |
+ return 0; | |
+ SKIP_TOKEN(parser); | |
+ } | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type == YAML_KEY_TOKEN) | |
+ { | |
+ yaml_mark_t mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ if (token->type != YAML_KEY_TOKEN && | |
+ token->type != YAML_VALUE_TOKEN && | |
+ token->type != YAML_BLOCK_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_BLOCK_MAPPING_VALUE_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 1, 1); | |
+ } | |
+ else { | |
+ parser->state = YAML_PARSE_BLOCK_MAPPING_VALUE_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, mark); | |
+ } | |
+ } | |
+ | |
+ else if (token->type == YAML_BLOCK_END_TOKEN) | |
+ { | |
+ yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ | |
+ parser->state = POP(parser, parser->states); | |
+ dummy_mark = POP(parser, parser->marks); | |
+ MAPPING_END_EVENT_INIT(*event, token->start_mark, token->end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+ } | |
+ | |
+ else | |
+ { | |
+ return yaml_parser_set_parser_error_context(parser, | |
+ "while parsing a block mapping", POP(parser, parser->marks), | |
+ "did not find expected key", token->start_mark); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * block_mapping ::= BLOCK-MAPPING_START | |
+ * | |
+ * ((KEY block_node_or_indentless_sequence?)? | |
+ * | |
+ * (VALUE block_node_or_indentless_sequence?)?)* | |
+ * ***** * | |
+ * BLOCK-END | |
+ * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_block_mapping_value(yaml_parser_t *parser, | |
+ yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type == YAML_VALUE_TOKEN) | |
+ { | |
+ yaml_mark_t mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ if (token->type != YAML_KEY_TOKEN && | |
+ token->type != YAML_VALUE_TOKEN && | |
+ token->type != YAML_BLOCK_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_BLOCK_MAPPING_KEY_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 1, 1); | |
+ } | |
+ else { | |
+ parser->state = YAML_PARSE_BLOCK_MAPPING_KEY_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, mark); | |
+ } | |
+ } | |
+ | |
+ else | |
+ { | |
+ parser->state = YAML_PARSE_BLOCK_MAPPING_KEY_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, token->start_mark); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * flow_sequence ::= FLOW-SEQUENCE-START | |
+ * ******************* | |
+ * (flow_sequence_entry FLOW-ENTRY)* | |
+ * * ********** | |
+ * flow_sequence_entry? | |
+ * * | |
+ * FLOW-SEQUENCE-END | |
+ * ***************** | |
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ * * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ yaml_token_t *token; | |
+ yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ | |
+ | |
+ if (first) { | |
+ token = PEEK_TOKEN(parser); | |
+ if (!PUSH(parser, parser->marks, token->start_mark)) | |
+ return 0; | |
+ SKIP_TOKEN(parser); | |
+ } | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type != YAML_FLOW_SEQUENCE_END_TOKEN) | |
+ { | |
+ if (!first) { | |
+ if (token->type == YAML_FLOW_ENTRY_TOKEN) { | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ } | |
+ else { | |
+ return yaml_parser_set_parser_error_context(parser, | |
+ "while parsing a flow sequence", POP(parser, parser->marks), | |
+ "did not find expected ',' or ']'", token->start_mark); | |
+ } | |
+ } | |
+ | |
+ if (token->type == YAML_KEY_TOKEN) { | |
+ parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE; | |
+ MAPPING_START_EVENT_INIT(*event, NULL, NULL, | |
+ 1, YAML_FLOW_MAPPING_STYLE, | |
+ token->start_mark, token->end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+ } | |
+ | |
+ else if (token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 0, 0); | |
+ } | |
+ } | |
+ | |
+ parser->state = POP(parser, parser->states); | |
+ dummy_mark = POP(parser, parser->marks); | |
+ SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ * *** * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry_mapping_key(yaml_parser_t *parser, | |
+ yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type != YAML_VALUE_TOKEN && token->type != YAML_FLOW_ENTRY_TOKEN | |
+ && token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 0, 0); | |
+ } | |
+ else { | |
+ yaml_mark_t mark = token->end_mark; | |
+ SKIP_TOKEN(parser); | |
+ parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, mark); | |
+ } | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ * ***** * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry_mapping_value(yaml_parser_t *parser, | |
+ yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type == YAML_VALUE_TOKEN) { | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ if (token->type != YAML_FLOW_ENTRY_TOKEN | |
+ && token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 0, 0); | |
+ } | |
+ } | |
+ parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, token->start_mark); | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ * * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_flow_sequence_entry_mapping_end(yaml_parser_t *parser, | |
+ yaml_event_t *event) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE; | |
+ | |
+ MAPPING_END_EVENT_INIT(*event, token->start_mark, token->start_mark); | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * flow_mapping ::= FLOW-MAPPING-START | |
+ * ****************** | |
+ * (flow_mapping_entry FLOW-ENTRY)* | |
+ * * ********** | |
+ * flow_mapping_entry? | |
+ * ****************** | |
+ * FLOW-MAPPING-END | |
+ * **************** | |
+ * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ * * *** * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_flow_mapping_key(yaml_parser_t *parser, | |
+ yaml_event_t *event, int first) | |
+{ | |
+ yaml_token_t *token; | |
+ yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ | |
+ | |
+ if (first) { | |
+ token = PEEK_TOKEN(parser); | |
+ if (!PUSH(parser, parser->marks, token->start_mark)) | |
+ return 0; | |
+ SKIP_TOKEN(parser); | |
+ } | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (token->type != YAML_FLOW_MAPPING_END_TOKEN) | |
+ { | |
+ if (!first) { | |
+ if (token->type == YAML_FLOW_ENTRY_TOKEN) { | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ } | |
+ else { | |
+ return yaml_parser_set_parser_error_context(parser, | |
+ "while parsing a flow mapping", POP(parser, parser->marks), | |
+ "did not find expected ',' or '}'", token->start_mark); | |
+ } | |
+ } | |
+ | |
+ if (token->type == YAML_KEY_TOKEN) { | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ if (token->type != YAML_VALUE_TOKEN | |
+ && token->type != YAML_FLOW_ENTRY_TOKEN | |
+ && token->type != YAML_FLOW_MAPPING_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_FLOW_MAPPING_VALUE_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 0, 0); | |
+ } | |
+ else { | |
+ parser->state = YAML_PARSE_FLOW_MAPPING_VALUE_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, | |
+ token->start_mark); | |
+ } | |
+ } | |
+ else if (token->type != YAML_FLOW_MAPPING_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 0, 0); | |
+ } | |
+ } | |
+ | |
+ parser->state = POP(parser, parser->states); | |
+ dummy_mark = POP(parser, parser->marks); | |
+ MAPPING_END_EVENT_INIT(*event, token->start_mark, token->end_mark); | |
+ SKIP_TOKEN(parser); | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Parse the productions: | |
+ * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? | |
+ * * ***** * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_parse_flow_mapping_value(yaml_parser_t *parser, | |
+ yaml_event_t *event, int empty) | |
+{ | |
+ yaml_token_t *token; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ | |
+ if (empty) { | |
+ parser->state = YAML_PARSE_FLOW_MAPPING_KEY_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, | |
+ token->start_mark); | |
+ } | |
+ | |
+ if (token->type == YAML_VALUE_TOKEN) { | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) return 0; | |
+ if (token->type != YAML_FLOW_ENTRY_TOKEN | |
+ && token->type != YAML_FLOW_MAPPING_END_TOKEN) { | |
+ if (!PUSH(parser, parser->states, | |
+ YAML_PARSE_FLOW_MAPPING_KEY_STATE)) | |
+ return 0; | |
+ return yaml_parser_parse_node(parser, event, 0, 0); | |
+ } | |
+ } | |
+ | |
+ parser->state = YAML_PARSE_FLOW_MAPPING_KEY_STATE; | |
+ return yaml_parser_process_empty_scalar(parser, event, token->start_mark); | |
+} | |
+ | |
+/* | |
+ * Generate an empty scalar event. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_process_empty_scalar(yaml_parser_t *parser, yaml_event_t *event, | |
+ yaml_mark_t mark) | |
+{ | |
+ yaml_char_t *value; | |
+ | |
+ value = yaml_malloc(1); | |
+ if (!value) { | |
+ parser->error = YAML_MEMORY_ERROR; | |
+ return 0; | |
+ } | |
+ value[0] = '\0'; | |
+ | |
+ SCALAR_EVENT_INIT(*event, NULL, NULL, value, 0, | |
+ 1, 0, YAML_PLAIN_SCALAR_STYLE, mark, mark); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Parse directives. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_process_directives(yaml_parser_t *parser, | |
+ yaml_version_directive_t **version_directive_ref, | |
+ yaml_tag_directive_t **tag_directives_start_ref, | |
+ yaml_tag_directive_t **tag_directives_end_ref) | |
+{ | |
+ yaml_tag_directive_t default_tag_directives[] = { | |
+ {(yaml_char_t *)"!", (yaml_char_t *)"!"}, | |
+ {(yaml_char_t *)"!!", (yaml_char_t *)"tag:yaml.org,2002:"}, | |
+ {NULL, NULL} | |
+ }; | |
+ yaml_tag_directive_t *default_tag_directive; | |
+ yaml_version_directive_t *version_directive = NULL; | |
+ struct { | |
+ yaml_tag_directive_t *start; | |
+ yaml_tag_directive_t *end; | |
+ yaml_tag_directive_t *top; | |
+ } tag_directives = { NULL, NULL, NULL }; | |
+ yaml_token_t *token; | |
+ | |
+ if (!STACK_INIT(parser, tag_directives, INITIAL_STACK_SIZE)) | |
+ goto error; | |
+ | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) goto error; | |
+ | |
+ while (token->type == YAML_VERSION_DIRECTIVE_TOKEN || | |
+ token->type == YAML_TAG_DIRECTIVE_TOKEN) | |
+ { | |
+ if (token->type == YAML_VERSION_DIRECTIVE_TOKEN) { | |
+ if (version_directive) { | |
+ yaml_parser_set_parser_error(parser, | |
+ "found duplicate %YAML directive", token->start_mark); | |
+ goto error; | |
+ } | |
+ if (token->data.version_directive.major != 1 | |
+ || token->data.version_directive.minor != 1) { | |
+ yaml_parser_set_parser_error(parser, | |
+ "found incompatible YAML document", token->start_mark); | |
+ goto error; | |
+ } | |
+ version_directive = yaml_malloc(sizeof(yaml_version_directive_t)); | |
+ if (!version_directive) { | |
+ parser->error = YAML_MEMORY_ERROR; | |
+ goto error; | |
+ } | |
+ version_directive->major = token->data.version_directive.major; | |
+ version_directive->minor = token->data.version_directive.minor; | |
+ } | |
+ | |
+ else if (token->type == YAML_TAG_DIRECTIVE_TOKEN) { | |
+ yaml_tag_directive_t value; | |
+ value.handle = token->data.tag_directive.handle; | |
+ value.prefix = token->data.tag_directive.prefix; | |
+ | |
+ if (!yaml_parser_append_tag_directive(parser, value, 0, | |
+ token->start_mark)) | |
+ goto error; | |
+ if (!PUSH(parser, tag_directives, value)) | |
+ goto error; | |
+ } | |
+ | |
+ SKIP_TOKEN(parser); | |
+ token = PEEK_TOKEN(parser); | |
+ if (!token) goto error; | |
+ } | |
+ | |
+ for (default_tag_directive = default_tag_directives; | |
+ default_tag_directive->handle; default_tag_directive++) { | |
+ if (!yaml_parser_append_tag_directive(parser, *default_tag_directive, 1, | |
+ token->start_mark)) | |
+ goto error; | |
+ } | |
+ | |
+ if (version_directive_ref) { | |
+ *version_directive_ref = version_directive; | |
+ } | |
+ if (tag_directives_start_ref) { | |
+ if (STACK_EMPTY(parser, tag_directives)) { | |
+ *tag_directives_start_ref = *tag_directives_end_ref = NULL; | |
+ STACK_DEL(parser, tag_directives); | |
+ } | |
+ else { | |
+ *tag_directives_start_ref = tag_directives.start; | |
+ *tag_directives_end_ref = tag_directives.top; | |
+ } | |
+ } | |
+ else { | |
+ STACK_DEL(parser, tag_directives); | |
+ } | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(version_directive); | |
+ while (!STACK_EMPTY(parser, tag_directives)) { | |
+ yaml_tag_directive_t tag_directive = POP(parser, tag_directives); | |
+ yaml_free(tag_directive.handle); | |
+ yaml_free(tag_directive.prefix); | |
+ } | |
+ STACK_DEL(parser, tag_directives); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Append a tag directive to the directives stack. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_append_tag_directive(yaml_parser_t *parser, | |
+ yaml_tag_directive_t value, int allow_duplicates, yaml_mark_t mark) | |
+{ | |
+ yaml_tag_directive_t *tag_directive; | |
+ yaml_tag_directive_t copy = { NULL, NULL }; | |
+ | |
+ for (tag_directive = parser->tag_directives.start; | |
+ tag_directive != parser->tag_directives.top; tag_directive ++) { | |
+ if (strcmp((char *)value.handle, (char *)tag_directive->handle) == 0) { | |
+ if (allow_duplicates) | |
+ return 1; | |
+ return yaml_parser_set_parser_error(parser, | |
+ "found duplicate %TAG directive", mark); | |
+ } | |
+ } | |
+ | |
+ copy.handle = yaml_strdup(value.handle); | |
+ copy.prefix = yaml_strdup(value.prefix); | |
+ if (!copy.handle || !copy.prefix) { | |
+ parser->error = YAML_MEMORY_ERROR; | |
+ goto error; | |
+ } | |
+ | |
+ if (!PUSH(parser, parser->tag_directives, copy)) | |
+ goto error; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(copy.handle); | |
+ yaml_free(copy.prefix); | |
+ return 0; | |
+} | |
diff --git a/ext/psych/yaml/reader.c b/ext/psych/yaml/reader.c | |
new file mode 100644 | |
index 0000000..124d2d1 | |
--- /dev/null | |
+++ b/ext/psych/yaml/reader.c | |
@@ -0,0 +1,464 @@ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * Declarations. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_reader_error(yaml_parser_t *parser, const char *problem, | |
+ size_t offset, int value); | |
+ | |
+static int | |
+yaml_parser_update_raw_buffer(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_determine_encoding(yaml_parser_t *parser); | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_update_buffer(yaml_parser_t *parser, size_t length); | |
+ | |
+/* | |
+ * Set the reader error and return 0. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_reader_error(yaml_parser_t *parser, const char *problem, | |
+ size_t offset, int value) | |
+{ | |
+ parser->error = YAML_READER_ERROR; | |
+ parser->problem = problem; | |
+ parser->problem_offset = offset; | |
+ parser->problem_value = value; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Byte order marks. | |
+ */ | |
+ | |
+#define BOM_UTF8 "\xef\xbb\xbf" | |
+#define BOM_UTF16LE "\xff\xfe" | |
+#define BOM_UTF16BE "\xfe\xff" | |
+ | |
+/* | |
+ * Determine the input stream encoding by checking the BOM symbol. If no BOM is | |
+ * found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_determine_encoding(yaml_parser_t *parser) | |
+{ | |
+ /* Ensure that we had enough bytes in the raw buffer. */ | |
+ | |
+ while (!parser->eof | |
+ && parser->raw_buffer.last - parser->raw_buffer.pointer < 3) { | |
+ if (!yaml_parser_update_raw_buffer(parser)) { | |
+ return 0; | |
+ } | |
+ } | |
+ | |
+ /* Determine the encoding. */ | |
+ | |
+ if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 2 | |
+ && !memcmp(parser->raw_buffer.pointer, BOM_UTF16LE, 2)) { | |
+ parser->encoding = YAML_UTF16LE_ENCODING; | |
+ parser->raw_buffer.pointer += 2; | |
+ parser->offset += 2; | |
+ } | |
+ else if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 2 | |
+ && !memcmp(parser->raw_buffer.pointer, BOM_UTF16BE, 2)) { | |
+ parser->encoding = YAML_UTF16BE_ENCODING; | |
+ parser->raw_buffer.pointer += 2; | |
+ parser->offset += 2; | |
+ } | |
+ else if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 3 | |
+ && !memcmp(parser->raw_buffer.pointer, BOM_UTF8, 3)) { | |
+ parser->encoding = YAML_UTF8_ENCODING; | |
+ parser->raw_buffer.pointer += 3; | |
+ parser->offset += 3; | |
+ } | |
+ else { | |
+ parser->encoding = YAML_UTF8_ENCODING; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Update the raw buffer. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_update_raw_buffer(yaml_parser_t *parser) | |
+{ | |
+ size_t size_read = 0; | |
+ | |
+ /* Return if the raw buffer is full. */ | |
+ | |
+ if (parser->raw_buffer.start == parser->raw_buffer.pointer | |
+ && parser->raw_buffer.last == parser->raw_buffer.end) | |
+ return 1; | |
+ | |
+ /* Return on EOF. */ | |
+ | |
+ if (parser->eof) return 1; | |
+ | |
+ /* Move the remaining bytes in the raw buffer to the beginning. */ | |
+ | |
+ if (parser->raw_buffer.start < parser->raw_buffer.pointer | |
+ && parser->raw_buffer.pointer < parser->raw_buffer.last) { | |
+ memmove(parser->raw_buffer.start, parser->raw_buffer.pointer, | |
+ parser->raw_buffer.last - parser->raw_buffer.pointer); | |
+ } | |
+ parser->raw_buffer.last -= | |
+ parser->raw_buffer.pointer - parser->raw_buffer.start; | |
+ parser->raw_buffer.pointer = parser->raw_buffer.start; | |
+ | |
+ /* Call the read handler to fill the buffer. */ | |
+ | |
+ if (!parser->read_handler(parser->read_handler_data, parser->raw_buffer.last, | |
+ parser->raw_buffer.end - parser->raw_buffer.last, &size_read)) { | |
+ return yaml_parser_set_reader_error(parser, "input error", | |
+ parser->offset, -1); | |
+ } | |
+ parser->raw_buffer.last += size_read; | |
+ if (!size_read) { | |
+ parser->eof = 1; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Ensure that the buffer contains at least `length` characters. | |
+ * Return 1 on success, 0 on failure. | |
+ * | |
+ * The length is supposed to be significantly less that the buffer size. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_update_buffer(yaml_parser_t *parser, size_t length) | |
+{ | |
+ int first = 1; | |
+ | |
+ assert(parser->read_handler); /* Read handler must be set. */ | |
+ | |
+ /* If the EOF flag is set and the raw buffer is empty, do nothing. */ | |
+ | |
+ if (parser->eof && parser->raw_buffer.pointer == parser->raw_buffer.last) | |
+ return 1; | |
+ | |
+ /* Return if the buffer contains enough characters. */ | |
+ | |
+ if (parser->unread >= length) | |
+ return 1; | |
+ | |
+ /* Determine the input encoding if it is not known yet. */ | |
+ | |
+ if (!parser->encoding) { | |
+ if (!yaml_parser_determine_encoding(parser)) | |
+ return 0; | |
+ } | |
+ | |
+ /* Move the unread characters to the beginning of the buffer. */ | |
+ | |
+ if (parser->buffer.start < parser->buffer.pointer | |
+ && parser->buffer.pointer < parser->buffer.last) { | |
+ size_t size = parser->buffer.last - parser->buffer.pointer; | |
+ memmove(parser->buffer.start, parser->buffer.pointer, size); | |
+ parser->buffer.pointer = parser->buffer.start; | |
+ parser->buffer.last = parser->buffer.start + size; | |
+ } | |
+ else if (parser->buffer.pointer == parser->buffer.last) { | |
+ parser->buffer.pointer = parser->buffer.start; | |
+ parser->buffer.last = parser->buffer.start; | |
+ } | |
+ | |
+ /* Fill the buffer until it has enough characters. */ | |
+ | |
+ while (parser->unread < length) | |
+ { | |
+ /* Fill the raw buffer if necessary. */ | |
+ | |
+ if (!first || parser->raw_buffer.pointer == parser->raw_buffer.last) { | |
+ if (!yaml_parser_update_raw_buffer(parser)) return 0; | |
+ } | |
+ first = 0; | |
+ | |
+ /* Decode the raw buffer. */ | |
+ | |
+ while (parser->raw_buffer.pointer != parser->raw_buffer.last) | |
+ { | |
+ unsigned int value = 0, value2 = 0; | |
+ int incomplete = 0; | |
+ unsigned char octet; | |
+ unsigned int width = 0; | |
+ int low, high; | |
+ size_t k; | |
+ size_t raw_unread = parser->raw_buffer.last - parser->raw_buffer.pointer; | |
+ | |
+ /* Decode the next character. */ | |
+ | |
+ switch (parser->encoding) | |
+ { | |
+ case YAML_UTF8_ENCODING: | |
+ | |
+ /* | |
+ * Decode a UTF-8 character. Check RFC 3629 | |
+ * (http://www.ietf.org/rfc/rfc3629.txt) for more details. | |
+ * | |
+ * The following table (taken from the RFC) is used for | |
+ * decoding. | |
+ * | |
+ * Char. number range | UTF-8 octet sequence | |
+ * (hexadecimal) | (binary) | |
+ * --------------------+------------------------------------ | |
+ * 0000 0000-0000 007F | 0xxxxxxx | |
+ * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx | |
+ * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx | |
+ * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | |
+ * | |
+ * Additionally, the characters in the range 0xD800-0xDFFF | |
+ * are prohibited as they are reserved for use with UTF-16 | |
+ * surrogate pairs. | |
+ */ | |
+ | |
+ /* Determine the length of the UTF-8 sequence. */ | |
+ | |
+ octet = parser->raw_buffer.pointer[0]; | |
+ width = (octet & 0x80) == 0x00 ? 1 : | |
+ (octet & 0xE0) == 0xC0 ? 2 : | |
+ (octet & 0xF0) == 0xE0 ? 3 : | |
+ (octet & 0xF8) == 0xF0 ? 4 : 0; | |
+ | |
+ /* Check if the leading octet is valid. */ | |
+ | |
+ if (!width) | |
+ return yaml_parser_set_reader_error(parser, | |
+ "invalid leading UTF-8 octet", | |
+ parser->offset, octet); | |
+ | |
+ /* Check if the raw buffer contains an incomplete character. */ | |
+ | |
+ if (width > raw_unread) { | |
+ if (parser->eof) { | |
+ return yaml_parser_set_reader_error(parser, | |
+ "incomplete UTF-8 octet sequence", | |
+ parser->offset, -1); | |
+ } | |
+ incomplete = 1; | |
+ break; | |
+ } | |
+ | |
+ /* Decode the leading octet. */ | |
+ | |
+ value = (octet & 0x80) == 0x00 ? octet & 0x7F : | |
+ (octet & 0xE0) == 0xC0 ? octet & 0x1F : | |
+ (octet & 0xF0) == 0xE0 ? octet & 0x0F : | |
+ (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; | |
+ | |
+ /* Check and decode the trailing octets. */ | |
+ | |
+ for (k = 1; k < width; k ++) | |
+ { | |
+ octet = parser->raw_buffer.pointer[k]; | |
+ | |
+ /* Check if the octet is valid. */ | |
+ | |
+ if ((octet & 0xC0) != 0x80) | |
+ return yaml_parser_set_reader_error(parser, | |
+ "invalid trailing UTF-8 octet", | |
+ parser->offset+k, octet); | |
+ | |
+ /* Decode the octet. */ | |
+ | |
+ value = (value << 6) + (octet & 0x3F); | |
+ } | |
+ | |
+ /* Check the length of the sequence against the value. */ | |
+ | |
+ if (!((width == 1) || | |
+ (width == 2 && value >= 0x80) || | |
+ (width == 3 && value >= 0x800) || | |
+ (width == 4 && value >= 0x10000))) | |
+ return yaml_parser_set_reader_error(parser, | |
+ "invalid length of a UTF-8 sequence", | |
+ parser->offset, -1); | |
+ | |
+ /* Check the range of the value. */ | |
+ | |
+ if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) | |
+ return yaml_parser_set_reader_error(parser, | |
+ "invalid Unicode character", | |
+ parser->offset, value); | |
+ | |
+ break; | |
+ | |
+ case YAML_UTF16LE_ENCODING: | |
+ case YAML_UTF16BE_ENCODING: | |
+ | |
+ low = (parser->encoding == YAML_UTF16LE_ENCODING ? 0 : 1); | |
+ high = (parser->encoding == YAML_UTF16LE_ENCODING ? 1 : 0); | |
+ | |
+ /* | |
+ * The UTF-16 encoding is not as simple as one might | |
+ * naively think. Check RFC 2781 | |
+ * (http://www.ietf.org/rfc/rfc2781.txt). | |
+ * | |
+ * Normally, two subsequent bytes describe a Unicode | |
+ * character. However a special technique (called a | |
+ * surrogate pair) is used for specifying character | |
+ * values larger than 0xFFFF. | |
+ * | |
+ * A surrogate pair consists of two pseudo-characters: | |
+ * high surrogate area (0xD800-0xDBFF) | |
+ * low surrogate area (0xDC00-0xDFFF) | |
+ * | |
+ * The following formulas are used for decoding | |
+ * and encoding characters using surrogate pairs: | |
+ * | |
+ * U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) | |
+ * U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) | |
+ * W1 = 110110yyyyyyyyyy | |
+ * W2 = 110111xxxxxxxxxx | |
+ * | |
+ * where U is the character value, W1 is the high surrogate | |
+ * area, W2 is the low surrogate area. | |
+ */ | |
+ | |
+ /* Check for incomplete UTF-16 character. */ | |
+ | |
+ if (raw_unread < 2) { | |
+ if (parser->eof) { | |
+ return yaml_parser_set_reader_error(parser, | |
+ "incomplete UTF-16 character", | |
+ parser->offset, -1); | |
+ } | |
+ incomplete = 1; | |
+ break; | |
+ } | |
+ | |
+ /* Get the character. */ | |
+ | |
+ value = parser->raw_buffer.pointer[low] | |
+ + (parser->raw_buffer.pointer[high] << 8); | |
+ | |
+ /* Check for unexpected low surrogate area. */ | |
+ | |
+ if ((value & 0xFC00) == 0xDC00) | |
+ return yaml_parser_set_reader_error(parser, | |
+ "unexpected low surrogate area", | |
+ parser->offset, value); | |
+ | |
+ /* Check for a high surrogate area. */ | |
+ | |
+ if ((value & 0xFC00) == 0xD800) { | |
+ | |
+ width = 4; | |
+ | |
+ /* Check for incomplete surrogate pair. */ | |
+ | |
+ if (raw_unread < 4) { | |
+ if (parser->eof) { | |
+ return yaml_parser_set_reader_error(parser, | |
+ "incomplete UTF-16 surrogate pair", | |
+ parser->offset, -1); | |
+ } | |
+ incomplete = 1; | |
+ break; | |
+ } | |
+ | |
+ /* Get the next character. */ | |
+ | |
+ value2 = parser->raw_buffer.pointer[low+2] | |
+ + (parser->raw_buffer.pointer[high+2] << 8); | |
+ | |
+ /* Check for a low surrogate area. */ | |
+ | |
+ if ((value2 & 0xFC00) != 0xDC00) | |
+ return yaml_parser_set_reader_error(parser, | |
+ "expected low surrogate area", | |
+ parser->offset+2, value2); | |
+ | |
+ /* Generate the value of the surrogate pair. */ | |
+ | |
+ value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF); | |
+ } | |
+ | |
+ else { | |
+ width = 2; | |
+ } | |
+ | |
+ break; | |
+ | |
+ default: | |
+ assert(1); /* Impossible. */ | |
+ } | |
+ | |
+ /* Check if the raw buffer contains enough bytes to form a character. */ | |
+ | |
+ if (incomplete) break; | |
+ | |
+ /* | |
+ * Check if the character is in the allowed range: | |
+ * #x9 | #xA | #xD | [#x20-#x7E] (8 bit) | |
+ * | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) | |
+ * | [#x10000-#x10FFFF] (32 bit) | |
+ */ | |
+ | |
+ if (! (value == 0x09 || value == 0x0A || value == 0x0D | |
+ || (value >= 0x20 && value <= 0x7E) | |
+ || (value == 0x85) || (value >= 0xA0 && value <= 0xD7FF) | |
+ || (value >= 0xE000 && value <= 0xFFFD) | |
+ || (value >= 0x10000 && value <= 0x10FFFF))) | |
+ return yaml_parser_set_reader_error(parser, | |
+ "control characters are not allowed", | |
+ parser->offset, value); | |
+ | |
+ /* Move the raw pointers. */ | |
+ | |
+ parser->raw_buffer.pointer += width; | |
+ parser->offset += width; | |
+ | |
+ /* Finally put the character into the buffer. */ | |
+ | |
+ /* 0000 0000-0000 007F -> 0xxxxxxx */ | |
+ if (value <= 0x7F) { | |
+ *(parser->buffer.last++) = value; | |
+ } | |
+ /* 0000 0080-0000 07FF -> 110xxxxx 10xxxxxx */ | |
+ else if (value <= 0x7FF) { | |
+ *(parser->buffer.last++) = 0xC0 + (value >> 6); | |
+ *(parser->buffer.last++) = 0x80 + (value & 0x3F); | |
+ } | |
+ /* 0000 0800-0000 FFFF -> 1110xxxx 10xxxxxx 10xxxxxx */ | |
+ else if (value <= 0xFFFF) { | |
+ *(parser->buffer.last++) = 0xE0 + (value >> 12); | |
+ *(parser->buffer.last++) = 0x80 + ((value >> 6) & 0x3F); | |
+ *(parser->buffer.last++) = 0x80 + (value & 0x3F); | |
+ } | |
+ /* 0001 0000-0010 FFFF -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ | |
+ else { | |
+ *(parser->buffer.last++) = 0xF0 + (value >> 18); | |
+ *(parser->buffer.last++) = 0x80 + ((value >> 12) & 0x3F); | |
+ *(parser->buffer.last++) = 0x80 + ((value >> 6) & 0x3F); | |
+ *(parser->buffer.last++) = 0x80 + (value & 0x3F); | |
+ } | |
+ | |
+ parser->unread ++; | |
+ } | |
+ | |
+ /* On EOF, put NUL into the buffer and return. */ | |
+ | |
+ if (parser->eof) { | |
+ *(parser->buffer.last++) = '\0'; | |
+ parser->unread ++; | |
+ return 1; | |
+ } | |
+ | |
+ } | |
+ | |
+ return 1; | |
+} | |
diff --git a/ext/psych/yaml/scanner.c b/ext/psych/yaml/scanner.c | |
new file mode 100644 | |
index 0000000..f8dfdb7 | |
--- /dev/null | |
+++ b/ext/psych/yaml/scanner.c | |
@@ -0,0 +1,3569 @@ | |
+ | |
+/* | |
+ * Introduction | |
+ * ************ | |
+ * | |
+ * The following notes assume that you are familiar with the YAML specification | |
+ * (http://yaml.org/spec/cvs/current.html). We mostly follow it, although in | |
+ * some cases we are less restrictive that it requires. | |
+ * | |
+ * The process of transforming a YAML stream into a sequence of events is | |
+ * divided on two steps: Scanning and Parsing. | |
+ * | |
+ * The Scanner transforms the input stream into a sequence of tokens, while the | |
+ * parser transform the sequence of tokens produced by the Scanner into a | |
+ * sequence of parsing events. | |
+ * | |
+ * The Scanner is rather clever and complicated. The Parser, on the contrary, | |
+ * is a straightforward implementation of a recursive-descendant parser (or, | |
+ * LL(1) parser, as it is usually called). | |
+ * | |
+ * Actually there are two issues of Scanning that might be called "clever", the | |
+ * rest is quite straightforward. The issues are "block collection start" and | |
+ * "simple keys". Both issues are explained below in details. | |
+ * | |
+ * Here the Scanning step is explained and implemented. We start with the list | |
+ * of all the tokens produced by the Scanner together with short descriptions. | |
+ * | |
+ * Now, tokens: | |
+ * | |
+ * STREAM-START(encoding) # The stream start. | |
+ * STREAM-END # The stream end. | |
+ * VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. | |
+ * TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. | |
+ * DOCUMENT-START # '---' | |
+ * DOCUMENT-END # '...' | |
+ * BLOCK-SEQUENCE-START # Indentation increase denoting a block | |
+ * BLOCK-MAPPING-START # sequence or a block mapping. | |
+ * BLOCK-END # Indentation decrease. | |
+ * FLOW-SEQUENCE-START # '[' | |
+ * FLOW-SEQUENCE-END # ']' | |
+ * BLOCK-SEQUENCE-START # '{' | |
+ * BLOCK-SEQUENCE-END # '}' | |
+ * BLOCK-ENTRY # '-' | |
+ * FLOW-ENTRY # ',' | |
+ * KEY # '?' or nothing (simple keys). | |
+ * VALUE # ':' | |
+ * ALIAS(anchor) # '*anchor' | |
+ * ANCHOR(anchor) # '&anchor' | |
+ * TAG(handle,suffix) # '!handle!suffix' | |
+ * SCALAR(value,style) # A scalar. | |
+ * | |
+ * The following two tokens are "virtual" tokens denoting the beginning and the | |
+ * end of the stream: | |
+ * | |
+ * STREAM-START(encoding) | |
+ * STREAM-END | |
+ * | |
+ * We pass the information about the input stream encoding with the | |
+ * STREAM-START token. | |
+ * | |
+ * The next two tokens are responsible for tags: | |
+ * | |
+ * VERSION-DIRECTIVE(major,minor) | |
+ * TAG-DIRECTIVE(handle,prefix) | |
+ * | |
+ * Example: | |
+ * | |
+ * %YAML 1.1 | |
+ * %TAG ! !foo | |
+ * %TAG !yaml! tag:yaml.org,2002: | |
+ * --- | |
+ * | |
+ * The correspoding sequence of tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * VERSION-DIRECTIVE(1,1) | |
+ * TAG-DIRECTIVE("!","!foo") | |
+ * TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") | |
+ * DOCUMENT-START | |
+ * STREAM-END | |
+ * | |
+ * Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole | |
+ * line. | |
+ * | |
+ * The document start and end indicators are represented by: | |
+ * | |
+ * DOCUMENT-START | |
+ * DOCUMENT-END | |
+ * | |
+ * Note that if a YAML stream contains an implicit document (without '---' | |
+ * and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be | |
+ * produced. | |
+ * | |
+ * In the following examples, we present whole documents together with the | |
+ * produced tokens. | |
+ * | |
+ * 1. An implicit document: | |
+ * | |
+ * 'a scalar' | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * SCALAR("a scalar",single-quoted) | |
+ * STREAM-END | |
+ * | |
+ * 2. An explicit document: | |
+ * | |
+ * --- | |
+ * 'a scalar' | |
+ * ... | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * DOCUMENT-START | |
+ * SCALAR("a scalar",single-quoted) | |
+ * DOCUMENT-END | |
+ * STREAM-END | |
+ * | |
+ * 3. Several documents in a stream: | |
+ * | |
+ * 'a scalar' | |
+ * --- | |
+ * 'another scalar' | |
+ * --- | |
+ * 'yet another scalar' | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * SCALAR("a scalar",single-quoted) | |
+ * DOCUMENT-START | |
+ * SCALAR("another scalar",single-quoted) | |
+ * DOCUMENT-START | |
+ * SCALAR("yet another scalar",single-quoted) | |
+ * STREAM-END | |
+ * | |
+ * We have already introduced the SCALAR token above. The following tokens are | |
+ * used to describe aliases, anchors, tag, and scalars: | |
+ * | |
+ * ALIAS(anchor) | |
+ * ANCHOR(anchor) | |
+ * TAG(handle,suffix) | |
+ * SCALAR(value,style) | |
+ * | |
+ * The following series of examples illustrate the usage of these tokens: | |
+ * | |
+ * 1. A recursive sequence: | |
+ * | |
+ * &A [ *A ] | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * ANCHOR("A") | |
+ * FLOW-SEQUENCE-START | |
+ * ALIAS("A") | |
+ * FLOW-SEQUENCE-END | |
+ * STREAM-END | |
+ * | |
+ * 2. A tagged scalar: | |
+ * | |
+ * !!float "3.14" # A good approximation. | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * TAG("!!","float") | |
+ * SCALAR("3.14",double-quoted) | |
+ * STREAM-END | |
+ * | |
+ * 3. Various scalar styles: | |
+ * | |
+ * --- # Implicit empty plain scalars do not produce tokens. | |
+ * --- a plain scalar | |
+ * --- 'a single-quoted scalar' | |
+ * --- "a double-quoted scalar" | |
+ * --- |- | |
+ * a literal scalar | |
+ * --- >- | |
+ * a folded | |
+ * scalar | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * DOCUMENT-START | |
+ * DOCUMENT-START | |
+ * SCALAR("a plain scalar",plain) | |
+ * DOCUMENT-START | |
+ * SCALAR("a single-quoted scalar",single-quoted) | |
+ * DOCUMENT-START | |
+ * SCALAR("a double-quoted scalar",double-quoted) | |
+ * DOCUMENT-START | |
+ * SCALAR("a literal scalar",literal) | |
+ * DOCUMENT-START | |
+ * SCALAR("a folded scalar",folded) | |
+ * STREAM-END | |
+ * | |
+ * Now it's time to review collection-related tokens. We will start with | |
+ * flow collections: | |
+ * | |
+ * FLOW-SEQUENCE-START | |
+ * FLOW-SEQUENCE-END | |
+ * FLOW-MAPPING-START | |
+ * FLOW-MAPPING-END | |
+ * FLOW-ENTRY | |
+ * KEY | |
+ * VALUE | |
+ * | |
+ * The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and | |
+ * FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' | |
+ * correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the | |
+ * indicators '?' and ':', which are used for denoting mapping keys and values, | |
+ * are represented by the KEY and VALUE tokens. | |
+ * | |
+ * The following examples show flow collections: | |
+ * | |
+ * 1. A flow sequence: | |
+ * | |
+ * [item 1, item 2, item 3] | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * FLOW-SEQUENCE-START | |
+ * SCALAR("item 1",plain) | |
+ * FLOW-ENTRY | |
+ * SCALAR("item 2",plain) | |
+ * FLOW-ENTRY | |
+ * SCALAR("item 3",plain) | |
+ * FLOW-SEQUENCE-END | |
+ * STREAM-END | |
+ * | |
+ * 2. A flow mapping: | |
+ * | |
+ * { | |
+ * a simple key: a value, # Note that the KEY token is produced. | |
+ * ? a complex key: another value, | |
+ * } | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * FLOW-MAPPING-START | |
+ * KEY | |
+ * SCALAR("a simple key",plain) | |
+ * VALUE | |
+ * SCALAR("a value",plain) | |
+ * FLOW-ENTRY | |
+ * KEY | |
+ * SCALAR("a complex key",plain) | |
+ * VALUE | |
+ * SCALAR("another value",plain) | |
+ * FLOW-ENTRY | |
+ * FLOW-MAPPING-END | |
+ * STREAM-END | |
+ * | |
+ * A simple key is a key which is not denoted by the '?' indicator. Note that | |
+ * the Scanner still produce the KEY token whenever it encounters a simple key. | |
+ * | |
+ * For scanning block collections, the following tokens are used (note that we | |
+ * repeat KEY and VALUE here): | |
+ * | |
+ * BLOCK-SEQUENCE-START | |
+ * BLOCK-MAPPING-START | |
+ * BLOCK-END | |
+ * BLOCK-ENTRY | |
+ * KEY | |
+ * VALUE | |
+ * | |
+ * The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation | |
+ * increase that precedes a block collection (cf. the INDENT token in Python). | |
+ * The token BLOCK-END denote indentation decrease that ends a block collection | |
+ * (cf. the DEDENT token in Python). However YAML has some syntax pecularities | |
+ * that makes detections of these tokens more complex. | |
+ * | |
+ * The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators | |
+ * '-', '?', and ':' correspondingly. | |
+ * | |
+ * The following examples show how the tokens BLOCK-SEQUENCE-START, | |
+ * BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: | |
+ * | |
+ * 1. Block sequences: | |
+ * | |
+ * - item 1 | |
+ * - item 2 | |
+ * - | |
+ * - item 3.1 | |
+ * - item 3.2 | |
+ * - | |
+ * key 1: value 1 | |
+ * key 2: value 2 | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * BLOCK-SEQUENCE-START | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 1",plain) | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 2",plain) | |
+ * BLOCK-ENTRY | |
+ * BLOCK-SEQUENCE-START | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 3.1",plain) | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 3.2",plain) | |
+ * BLOCK-END | |
+ * BLOCK-ENTRY | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("key 1",plain) | |
+ * VALUE | |
+ * SCALAR("value 1",plain) | |
+ * KEY | |
+ * SCALAR("key 2",plain) | |
+ * VALUE | |
+ * SCALAR("value 2",plain) | |
+ * BLOCK-END | |
+ * BLOCK-END | |
+ * STREAM-END | |
+ * | |
+ * 2. Block mappings: | |
+ * | |
+ * a simple key: a value # The KEY token is produced here. | |
+ * ? a complex key | |
+ * : another value | |
+ * a mapping: | |
+ * key 1: value 1 | |
+ * key 2: value 2 | |
+ * a sequence: | |
+ * - item 1 | |
+ * - item 2 | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("a simple key",plain) | |
+ * VALUE | |
+ * SCALAR("a value",plain) | |
+ * KEY | |
+ * SCALAR("a complex key",plain) | |
+ * VALUE | |
+ * SCALAR("another value",plain) | |
+ * KEY | |
+ * SCALAR("a mapping",plain) | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("key 1",plain) | |
+ * VALUE | |
+ * SCALAR("value 1",plain) | |
+ * KEY | |
+ * SCALAR("key 2",plain) | |
+ * VALUE | |
+ * SCALAR("value 2",plain) | |
+ * BLOCK-END | |
+ * KEY | |
+ * SCALAR("a sequence",plain) | |
+ * VALUE | |
+ * BLOCK-SEQUENCE-START | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 1",plain) | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 2",plain) | |
+ * BLOCK-END | |
+ * BLOCK-END | |
+ * STREAM-END | |
+ * | |
+ * YAML does not always require to start a new block collection from a new | |
+ * line. If the current line contains only '-', '?', and ':' indicators, a new | |
+ * block collection may start at the current line. The following examples | |
+ * illustrate this case: | |
+ * | |
+ * 1. Collections in a sequence: | |
+ * | |
+ * - - item 1 | |
+ * - item 2 | |
+ * - key 1: value 1 | |
+ * key 2: value 2 | |
+ * - ? complex key | |
+ * : complex value | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * BLOCK-SEQUENCE-START | |
+ * BLOCK-ENTRY | |
+ * BLOCK-SEQUENCE-START | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 1",plain) | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 2",plain) | |
+ * BLOCK-END | |
+ * BLOCK-ENTRY | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("key 1",plain) | |
+ * VALUE | |
+ * SCALAR("value 1",plain) | |
+ * KEY | |
+ * SCALAR("key 2",plain) | |
+ * VALUE | |
+ * SCALAR("value 2",plain) | |
+ * BLOCK-END | |
+ * BLOCK-ENTRY | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("complex key") | |
+ * VALUE | |
+ * SCALAR("complex value") | |
+ * BLOCK-END | |
+ * BLOCK-END | |
+ * STREAM-END | |
+ * | |
+ * 2. Collections in a mapping: | |
+ * | |
+ * ? a sequence | |
+ * : - item 1 | |
+ * - item 2 | |
+ * ? a mapping | |
+ * : key 1: value 1 | |
+ * key 2: value 2 | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("a sequence",plain) | |
+ * VALUE | |
+ * BLOCK-SEQUENCE-START | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 1",plain) | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 2",plain) | |
+ * BLOCK-END | |
+ * KEY | |
+ * SCALAR("a mapping",plain) | |
+ * VALUE | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("key 1",plain) | |
+ * VALUE | |
+ * SCALAR("value 1",plain) | |
+ * KEY | |
+ * SCALAR("key 2",plain) | |
+ * VALUE | |
+ * SCALAR("value 2",plain) | |
+ * BLOCK-END | |
+ * BLOCK-END | |
+ * STREAM-END | |
+ * | |
+ * YAML also permits non-indented sequences if they are included into a block | |
+ * mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: | |
+ * | |
+ * key: | |
+ * - item 1 # BLOCK-SEQUENCE-START is NOT produced here. | |
+ * - item 2 | |
+ * | |
+ * Tokens: | |
+ * | |
+ * STREAM-START(utf-8) | |
+ * BLOCK-MAPPING-START | |
+ * KEY | |
+ * SCALAR("key",plain) | |
+ * VALUE | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 1",plain) | |
+ * BLOCK-ENTRY | |
+ * SCALAR("item 2",plain) | |
+ * BLOCK-END | |
+ */ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * Ensure that the buffer contains the required number of characters. | |
+ * Return 1 on success, 0 on failure (reader error or memory error). | |
+ */ | |
+ | |
+#define CACHE(parser,length) \ | |
+ (parser->unread >= (length) \ | |
+ ? 1 \ | |
+ : yaml_parser_update_buffer(parser, (length))) | |
+ | |
+/* | |
+ * Advance the buffer pointer. | |
+ */ | |
+ | |
+#define SKIP(parser) \ | |
+ (parser->mark.index ++, \ | |
+ parser->mark.column ++, \ | |
+ parser->unread --, \ | |
+ parser->buffer.pointer += WIDTH(parser->buffer)) | |
+ | |
+#define SKIP_LINE(parser) \ | |
+ (IS_CRLF(parser->buffer) ? \ | |
+ (parser->mark.index += 2, \ | |
+ parser->mark.column = 0, \ | |
+ parser->mark.line ++, \ | |
+ parser->unread -= 2, \ | |
+ parser->buffer.pointer += 2) : \ | |
+ IS_BREAK(parser->buffer) ? \ | |
+ (parser->mark.index ++, \ | |
+ parser->mark.column = 0, \ | |
+ parser->mark.line ++, \ | |
+ parser->unread --, \ | |
+ parser->buffer.pointer += WIDTH(parser->buffer)) : 0) | |
+ | |
+/* | |
+ * Copy a character to a string buffer and advance pointers. | |
+ */ | |
+ | |
+#define READ(parser,string) \ | |
+ (STRING_EXTEND(parser,string) ? \ | |
+ (COPY(string,parser->buffer), \ | |
+ parser->mark.index ++, \ | |
+ parser->mark.column ++, \ | |
+ parser->unread --, \ | |
+ 1) : 0) | |
+ | |
+/* | |
+ * Copy a line break character to a string buffer and advance pointers. | |
+ */ | |
+ | |
+#define READ_LINE(parser,string) \ | |
+ (STRING_EXTEND(parser,string) ? \ | |
+ (((CHECK_AT(parser->buffer,'\r',0) \ | |
+ && CHECK_AT(parser->buffer,'\n',1)) ? /* CR LF -> LF */ \ | |
+ (*((string).pointer++) = (yaml_char_t) '\n', \ | |
+ parser->buffer.pointer += 2, \ | |
+ parser->mark.index += 2, \ | |
+ parser->mark.column = 0, \ | |
+ parser->mark.line ++, \ | |
+ parser->unread -= 2) : \ | |
+ (CHECK_AT(parser->buffer,'\r',0) \ | |
+ || CHECK_AT(parser->buffer,'\n',0)) ? /* CR|LF -> LF */ \ | |
+ (*((string).pointer++) = (yaml_char_t) '\n', \ | |
+ parser->buffer.pointer ++, \ | |
+ parser->mark.index ++, \ | |
+ parser->mark.column = 0, \ | |
+ parser->mark.line ++, \ | |
+ parser->unread --) : \ | |
+ (CHECK_AT(parser->buffer,'\xC2',0) \ | |
+ && CHECK_AT(parser->buffer,'\x85',1)) ? /* NEL -> LF */ \ | |
+ (*((string).pointer++) = (yaml_char_t) '\n', \ | |
+ parser->buffer.pointer += 2, \ | |
+ parser->mark.index ++, \ | |
+ parser->mark.column = 0, \ | |
+ parser->mark.line ++, \ | |
+ parser->unread --) : \ | |
+ (CHECK_AT(parser->buffer,'\xE2',0) && \ | |
+ CHECK_AT(parser->buffer,'\x80',1) && \ | |
+ (CHECK_AT(parser->buffer,'\xA8',2) || \ | |
+ CHECK_AT(parser->buffer,'\xA9',2))) ? /* LS|PS -> LS|PS */ \ | |
+ (*((string).pointer++) = *(parser->buffer.pointer++), \ | |
+ *((string).pointer++) = *(parser->buffer.pointer++), \ | |
+ *((string).pointer++) = *(parser->buffer.pointer++), \ | |
+ parser->mark.index ++, \ | |
+ parser->mark.column = 0, \ | |
+ parser->mark.line ++, \ | |
+ parser->unread --) : 0), \ | |
+ 1) : 0) | |
+ | |
+/* | |
+ * Public API declarations. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token); | |
+ | |
+/* | |
+ * Error handling. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_scanner_error(yaml_parser_t *parser, const char *context, | |
+ yaml_mark_t context_mark, const char *problem); | |
+ | |
+/* | |
+ * High-level token API. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_fetch_more_tokens(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_next_token(yaml_parser_t *parser); | |
+ | |
+/* | |
+ * Potential simple keys. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_stale_simple_keys(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_save_simple_key(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_remove_simple_key(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_increase_flow_level(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_decrease_flow_level(yaml_parser_t *parser); | |
+ | |
+/* | |
+ * Indentation treatment. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_roll_indent(yaml_parser_t *parser, int column, | |
+ int number, yaml_token_type_t type, yaml_mark_t mark); | |
+ | |
+static int | |
+yaml_parser_unroll_indent(yaml_parser_t *parser, int column); | |
+ | |
+/* | |
+ * Token fetchers. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_stream_start(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_stream_end(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_directive(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_document_indicator(yaml_parser_t *parser, | |
+ yaml_token_type_t type); | |
+ | |
+static int | |
+yaml_parser_fetch_flow_collection_start(yaml_parser_t *parser, | |
+ yaml_token_type_t type); | |
+ | |
+static int | |
+yaml_parser_fetch_flow_collection_end(yaml_parser_t *parser, | |
+ yaml_token_type_t type); | |
+ | |
+static int | |
+yaml_parser_fetch_flow_entry(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_block_entry(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_key(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_value(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_anchor(yaml_parser_t *parser, yaml_token_type_t type); | |
+ | |
+static int | |
+yaml_parser_fetch_tag(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_fetch_block_scalar(yaml_parser_t *parser, int literal); | |
+ | |
+static int | |
+yaml_parser_fetch_flow_scalar(yaml_parser_t *parser, int single); | |
+ | |
+static int | |
+yaml_parser_fetch_plain_scalar(yaml_parser_t *parser); | |
+ | |
+/* | |
+ * Token scanners. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_to_next_token(yaml_parser_t *parser); | |
+ | |
+static int | |
+yaml_parser_scan_directive(yaml_parser_t *parser, yaml_token_t *token); | |
+ | |
+static int | |
+yaml_parser_scan_directive_name(yaml_parser_t *parser, | |
+ yaml_mark_t start_mark, yaml_char_t **name); | |
+ | |
+static int | |
+yaml_parser_scan_version_directive_value(yaml_parser_t *parser, | |
+ yaml_mark_t start_mark, int *major, int *minor); | |
+ | |
+static int | |
+yaml_parser_scan_version_directive_number(yaml_parser_t *parser, | |
+ yaml_mark_t start_mark, int *number); | |
+ | |
+static int | |
+yaml_parser_scan_tag_directive_value(yaml_parser_t *parser, | |
+ yaml_mark_t mark, yaml_char_t **handle, yaml_char_t **prefix); | |
+ | |
+static int | |
+yaml_parser_scan_anchor(yaml_parser_t *parser, yaml_token_t *token, | |
+ yaml_token_type_t type); | |
+ | |
+static int | |
+yaml_parser_scan_tag(yaml_parser_t *parser, yaml_token_t *token); | |
+ | |
+static int | |
+yaml_parser_scan_tag_handle(yaml_parser_t *parser, int directive, | |
+ yaml_mark_t start_mark, yaml_char_t **handle); | |
+ | |
+static int | |
+yaml_parser_scan_tag_uri(yaml_parser_t *parser, int directive, | |
+ yaml_char_t *head, yaml_mark_t start_mark, yaml_char_t **uri); | |
+ | |
+static int | |
+yaml_parser_scan_uri_escapes(yaml_parser_t *parser, int directive, | |
+ yaml_mark_t start_mark, yaml_string_t *string); | |
+ | |
+static int | |
+yaml_parser_scan_block_scalar(yaml_parser_t *parser, yaml_token_t *token, | |
+ int literal); | |
+ | |
+static int | |
+yaml_parser_scan_block_scalar_breaks(yaml_parser_t *parser, | |
+ int *indent, yaml_string_t *breaks, | |
+ yaml_mark_t start_mark, yaml_mark_t *end_mark); | |
+ | |
+static int | |
+yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token, | |
+ int single); | |
+ | |
+static int | |
+yaml_parser_scan_plain_scalar(yaml_parser_t *parser, yaml_token_t *token); | |
+ | |
+/* | |
+ * Get the next token. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token) | |
+{ | |
+ assert(parser); /* Non-NULL parser object is expected. */ | |
+ assert(token); /* Non-NULL token object is expected. */ | |
+ | |
+ /* Erase the token object. */ | |
+ | |
+ memset(token, 0, sizeof(yaml_token_t)); | |
+ | |
+ /* No tokens after STREAM-END or error. */ | |
+ | |
+ if (parser->stream_end_produced || parser->error) { | |
+ return 1; | |
+ } | |
+ | |
+ /* Ensure that the tokens queue contains enough tokens. */ | |
+ | |
+ if (!parser->token_available) { | |
+ if (!yaml_parser_fetch_more_tokens(parser)) | |
+ return 0; | |
+ } | |
+ | |
+ /* Fetch the next token from the queue. */ | |
+ | |
+ *token = DEQUEUE(parser, parser->tokens); | |
+ parser->token_available = 0; | |
+ parser->tokens_parsed ++; | |
+ | |
+ if (token->type == YAML_STREAM_END_TOKEN) { | |
+ parser->stream_end_produced = 1; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Set the scanner error and return 0. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_set_scanner_error(yaml_parser_t *parser, const char *context, | |
+ yaml_mark_t context_mark, const char *problem) | |
+{ | |
+ parser->error = YAML_SCANNER_ERROR; | |
+ parser->context = context; | |
+ parser->context_mark = context_mark; | |
+ parser->problem = problem; | |
+ parser->problem_mark = parser->mark; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Ensure that the tokens queue contains at least one token which can be | |
+ * returned to the Parser. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_fetch_more_tokens(yaml_parser_t *parser) | |
+{ | |
+ int need_more_tokens; | |
+ | |
+ /* While we need more tokens to fetch, do it. */ | |
+ | |
+ while (1) | |
+ { | |
+ /* | |
+ * Check if we really need to fetch more tokens. | |
+ */ | |
+ | |
+ need_more_tokens = 0; | |
+ | |
+ if (parser->tokens.head == parser->tokens.tail) | |
+ { | |
+ /* Queue is empty. */ | |
+ | |
+ need_more_tokens = 1; | |
+ } | |
+ else | |
+ { | |
+ yaml_simple_key_t *simple_key; | |
+ | |
+ /* Check if any potential simple key may occupy the head position. */ | |
+ | |
+ if (!yaml_parser_stale_simple_keys(parser)) | |
+ return 0; | |
+ | |
+ for (simple_key = parser->simple_keys.start; | |
+ simple_key != parser->simple_keys.top; simple_key++) { | |
+ if (simple_key->possible | |
+ && simple_key->token_number == parser->tokens_parsed) { | |
+ need_more_tokens = 1; | |
+ break; | |
+ } | |
+ } | |
+ } | |
+ | |
+ /* We are finished. */ | |
+ | |
+ if (!need_more_tokens) | |
+ break; | |
+ | |
+ /* Fetch the next token. */ | |
+ | |
+ if (!yaml_parser_fetch_next_token(parser)) | |
+ return 0; | |
+ } | |
+ | |
+ parser->token_available = 1; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * The dispatcher for token fetchers. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_next_token(yaml_parser_t *parser) | |
+{ | |
+ /* Ensure that the buffer is initialized. */ | |
+ | |
+ if (!CACHE(parser, 1)) | |
+ return 0; | |
+ | |
+ /* Check if we just started scanning. Fetch STREAM-START then. */ | |
+ | |
+ if (!parser->stream_start_produced) | |
+ return yaml_parser_fetch_stream_start(parser); | |
+ | |
+ /* Eat whitespaces and comments until we reach the next token. */ | |
+ | |
+ if (!yaml_parser_scan_to_next_token(parser)) | |
+ return 0; | |
+ | |
+ /* Remove obsolete potential simple keys. */ | |
+ | |
+ if (!yaml_parser_stale_simple_keys(parser)) | |
+ return 0; | |
+ | |
+ /* Check the indentation level against the current column. */ | |
+ | |
+ if (!yaml_parser_unroll_indent(parser, parser->mark.column)) | |
+ return 0; | |
+ | |
+ /* | |
+ * Ensure that the buffer contains at least 4 characters. 4 is the length | |
+ * of the longest indicators ('--- ' and '... '). | |
+ */ | |
+ | |
+ if (!CACHE(parser, 4)) | |
+ return 0; | |
+ | |
+ /* Is it the end of the stream? */ | |
+ | |
+ if (IS_Z(parser->buffer)) | |
+ return yaml_parser_fetch_stream_end(parser); | |
+ | |
+ /* Is it a directive? */ | |
+ | |
+ if (parser->mark.column == 0 && CHECK(parser->buffer, '%')) | |
+ return yaml_parser_fetch_directive(parser); | |
+ | |
+ /* Is it the document start indicator? */ | |
+ | |
+ if (parser->mark.column == 0 | |
+ && CHECK_AT(parser->buffer, '-', 0) | |
+ && CHECK_AT(parser->buffer, '-', 1) | |
+ && CHECK_AT(parser->buffer, '-', 2) | |
+ && IS_BLANKZ_AT(parser->buffer, 3)) | |
+ return yaml_parser_fetch_document_indicator(parser, | |
+ YAML_DOCUMENT_START_TOKEN); | |
+ | |
+ /* Is it the document end indicator? */ | |
+ | |
+ if (parser->mark.column == 0 | |
+ && CHECK_AT(parser->buffer, '.', 0) | |
+ && CHECK_AT(parser->buffer, '.', 1) | |
+ && CHECK_AT(parser->buffer, '.', 2) | |
+ && IS_BLANKZ_AT(parser->buffer, 3)) | |
+ return yaml_parser_fetch_document_indicator(parser, | |
+ YAML_DOCUMENT_END_TOKEN); | |
+ | |
+ /* Is it the flow sequence start indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, '[')) | |
+ return yaml_parser_fetch_flow_collection_start(parser, | |
+ YAML_FLOW_SEQUENCE_START_TOKEN); | |
+ | |
+ /* Is it the flow mapping start indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, '{')) | |
+ return yaml_parser_fetch_flow_collection_start(parser, | |
+ YAML_FLOW_MAPPING_START_TOKEN); | |
+ | |
+ /* Is it the flow sequence end indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, ']')) | |
+ return yaml_parser_fetch_flow_collection_end(parser, | |
+ YAML_FLOW_SEQUENCE_END_TOKEN); | |
+ | |
+ /* Is it the flow mapping end indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, '}')) | |
+ return yaml_parser_fetch_flow_collection_end(parser, | |
+ YAML_FLOW_MAPPING_END_TOKEN); | |
+ | |
+ /* Is it the flow entry indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, ',')) | |
+ return yaml_parser_fetch_flow_entry(parser); | |
+ | |
+ /* Is it the block entry indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, '-') && IS_BLANKZ_AT(parser->buffer, 1)) | |
+ return yaml_parser_fetch_block_entry(parser); | |
+ | |
+ /* Is it the key indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, '?') | |
+ && (parser->flow_level || IS_BLANKZ_AT(parser->buffer, 1))) | |
+ return yaml_parser_fetch_key(parser); | |
+ | |
+ /* Is it the value indicator? */ | |
+ | |
+ if (CHECK(parser->buffer, ':') | |
+ && (parser->flow_level || IS_BLANKZ_AT(parser->buffer, 1))) | |
+ return yaml_parser_fetch_value(parser); | |
+ | |
+ /* Is it an alias? */ | |
+ | |
+ if (CHECK(parser->buffer, '*')) | |
+ return yaml_parser_fetch_anchor(parser, YAML_ALIAS_TOKEN); | |
+ | |
+ /* Is it an anchor? */ | |
+ | |
+ if (CHECK(parser->buffer, '&')) | |
+ return yaml_parser_fetch_anchor(parser, YAML_ANCHOR_TOKEN); | |
+ | |
+ /* Is it a tag? */ | |
+ | |
+ if (CHECK(parser->buffer, '!')) | |
+ return yaml_parser_fetch_tag(parser); | |
+ | |
+ /* Is it a literal scalar? */ | |
+ | |
+ if (CHECK(parser->buffer, '|') && !parser->flow_level) | |
+ return yaml_parser_fetch_block_scalar(parser, 1); | |
+ | |
+ /* Is it a folded scalar? */ | |
+ | |
+ if (CHECK(parser->buffer, '>') && !parser->flow_level) | |
+ return yaml_parser_fetch_block_scalar(parser, 0); | |
+ | |
+ /* Is it a single-quoted scalar? */ | |
+ | |
+ if (CHECK(parser->buffer, '\'')) | |
+ return yaml_parser_fetch_flow_scalar(parser, 1); | |
+ | |
+ /* Is it a double-quoted scalar? */ | |
+ | |
+ if (CHECK(parser->buffer, '"')) | |
+ return yaml_parser_fetch_flow_scalar(parser, 0); | |
+ | |
+ /* | |
+ * Is it a plain scalar? | |
+ * | |
+ * A plain scalar may start with any non-blank characters except | |
+ * | |
+ * '-', '?', ':', ',', '[', ']', '{', '}', | |
+ * '#', '&', '*', '!', '|', '>', '\'', '\"', | |
+ * '%', '@', '`'. | |
+ * | |
+ * In the block context (and, for the '-' indicator, in the flow context | |
+ * too), it may also start with the characters | |
+ * | |
+ * '-', '?', ':' | |
+ * | |
+ * if it is followed by a non-space character. | |
+ * | |
+ * The last rule is more restrictive than the specification requires. | |
+ */ | |
+ | |
+ if (!(IS_BLANKZ(parser->buffer) || CHECK(parser->buffer, '-') | |
+ || CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':') | |
+ || CHECK(parser->buffer, ',') || CHECK(parser->buffer, '[') | |
+ || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '{') | |
+ || CHECK(parser->buffer, '}') || CHECK(parser->buffer, '#') | |
+ || CHECK(parser->buffer, '&') || CHECK(parser->buffer, '*') | |
+ || CHECK(parser->buffer, '!') || CHECK(parser->buffer, '|') | |
+ || CHECK(parser->buffer, '>') || CHECK(parser->buffer, '\'') | |
+ || CHECK(parser->buffer, '"') || CHECK(parser->buffer, '%') | |
+ || CHECK(parser->buffer, '@') || CHECK(parser->buffer, '`')) || | |
+ (CHECK(parser->buffer, '-') && !IS_BLANK_AT(parser->buffer, 1)) || | |
+ (!parser->flow_level && | |
+ (CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':')) | |
+ && !IS_BLANKZ_AT(parser->buffer, 1))) | |
+ return yaml_parser_fetch_plain_scalar(parser); | |
+ | |
+ /* | |
+ * If we don't determine the token type so far, it is an error. | |
+ */ | |
+ | |
+ return yaml_parser_set_scanner_error(parser, | |
+ "while scanning for the next token", parser->mark, | |
+ "found character that cannot start any token"); | |
+} | |
+ | |
+/* | |
+ * Check the list of potential simple keys and remove the positions that | |
+ * cannot contain simple keys anymore. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_stale_simple_keys(yaml_parser_t *parser) | |
+{ | |
+ yaml_simple_key_t *simple_key; | |
+ | |
+ /* Check for a potential simple key for each flow level. */ | |
+ | |
+ for (simple_key = parser->simple_keys.start; | |
+ simple_key != parser->simple_keys.top; simple_key ++) | |
+ { | |
+ /* | |
+ * The specification requires that a simple key | |
+ * | |
+ * - is limited to a single line, | |
+ * - is shorter than 1024 characters. | |
+ */ | |
+ | |
+ if (simple_key->possible | |
+ && (simple_key->mark.line < parser->mark.line | |
+ || simple_key->mark.index+1024 < parser->mark.index)) { | |
+ | |
+ /* Check if the potential simple key to be removed is required. */ | |
+ | |
+ if (simple_key->required) { | |
+ return yaml_parser_set_scanner_error(parser, | |
+ "while scanning a simple key", simple_key->mark, | |
+ "could not find expected ':'"); | |
+ } | |
+ | |
+ simple_key->possible = 0; | |
+ } | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Check if a simple key may start at the current position and add it if | |
+ * needed. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_save_simple_key(yaml_parser_t *parser) | |
+{ | |
+ /* | |
+ * A simple key is required at the current position if the scanner is in | |
+ * the block context and the current column coincides with the indentation | |
+ * level. | |
+ */ | |
+ | |
+ int required = (!parser->flow_level | |
+ && parser->indent == (int)parser->mark.column); | |
+ | |
+ /* | |
+ * A simple key is required only when it is the first token in the current | |
+ * line. Therefore it is always allowed. But we add a check anyway. | |
+ */ | |
+ | |
+ assert(parser->simple_key_allowed || !required); /* Impossible. */ | |
+ | |
+ /* | |
+ * If the current position may start a simple key, save it. | |
+ */ | |
+ | |
+ if (parser->simple_key_allowed) | |
+ { | |
+ yaml_simple_key_t simple_key; | |
+ simple_key.possible = 1; | |
+ simple_key.required = required; | |
+ simple_key.token_number = | |
+ parser->tokens_parsed + (parser->tokens.tail - parser->tokens.head); | |
+ simple_key.mark = parser->mark; | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) return 0; | |
+ | |
+ *(parser->simple_keys.top-1) = simple_key; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Remove a potential simple key at the current flow level. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_remove_simple_key(yaml_parser_t *parser) | |
+{ | |
+ yaml_simple_key_t *simple_key = parser->simple_keys.top-1; | |
+ | |
+ if (simple_key->possible) | |
+ { | |
+ /* If the key is required, it is an error. */ | |
+ | |
+ if (simple_key->required) { | |
+ return yaml_parser_set_scanner_error(parser, | |
+ "while scanning a simple key", simple_key->mark, | |
+ "could not find expected ':'"); | |
+ } | |
+ } | |
+ | |
+ /* Remove the key from the stack. */ | |
+ | |
+ simple_key->possible = 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Increase the flow level and resize the simple key list if needed. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_increase_flow_level(yaml_parser_t *parser) | |
+{ | |
+ yaml_simple_key_t empty_simple_key = { 0, 0, 0, { 0, 0, 0 } }; | |
+ | |
+ /* Reset the simple key on the next level. */ | |
+ | |
+ if (!PUSH(parser, parser->simple_keys, empty_simple_key)) | |
+ return 0; | |
+ | |
+ /* Increase the flow level. */ | |
+ | |
+ parser->flow_level++; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Decrease the flow level. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_decrease_flow_level(yaml_parser_t *parser) | |
+{ | |
+ yaml_simple_key_t dummy_key; /* Used to eliminate a compiler warning. */ | |
+ | |
+ if (parser->flow_level) { | |
+ parser->flow_level --; | |
+ dummy_key = POP(parser, parser->simple_keys); | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Push the current indentation level to the stack and set the new level | |
+ * the current column is greater than the indentation level. In this case, | |
+ * append or insert the specified token into the token queue. | |
+ * | |
+ */ | |
+ | |
+static int | |
+yaml_parser_roll_indent(yaml_parser_t *parser, int column, | |
+ int number, yaml_token_type_t type, yaml_mark_t mark) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* In the flow context, do nothing. */ | |
+ | |
+ if (parser->flow_level) | |
+ return 1; | |
+ | |
+ if (parser->indent < column) | |
+ { | |
+ /* | |
+ * Push the current indentation level to the stack and set the new | |
+ * indentation level. | |
+ */ | |
+ | |
+ if (!PUSH(parser, parser->indents, parser->indent)) | |
+ return 0; | |
+ | |
+ parser->indent = column; | |
+ | |
+ /* Create a token and insert it into the queue. */ | |
+ | |
+ TOKEN_INIT(token, type, mark, mark); | |
+ | |
+ if (number == -1) { | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ } | |
+ else { | |
+ if (!QUEUE_INSERT(parser, | |
+ parser->tokens, number - parser->tokens_parsed, token)) | |
+ return 0; | |
+ } | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Pop indentation levels from the indents stack until the current level | |
+ * becomes less or equal to the column. For each indentation level, append | |
+ * the BLOCK-END token. | |
+ */ | |
+ | |
+ | |
+static int | |
+yaml_parser_unroll_indent(yaml_parser_t *parser, int column) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* In the flow context, do nothing. */ | |
+ | |
+ if (parser->flow_level) | |
+ return 1; | |
+ | |
+ /* Loop through the indentation levels in the stack. */ | |
+ | |
+ while (parser->indent > column) | |
+ { | |
+ /* Create a token and append it to the queue. */ | |
+ | |
+ TOKEN_INIT(token, YAML_BLOCK_END_TOKEN, parser->mark, parser->mark); | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ /* Pop the indentation level. */ | |
+ | |
+ parser->indent = POP(parser, parser->indents); | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Initialize the scanner and produce the STREAM-START token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_stream_start(yaml_parser_t *parser) | |
+{ | |
+ yaml_simple_key_t simple_key = { 0, 0, 0, { 0, 0, 0 } }; | |
+ yaml_token_t token; | |
+ | |
+ /* Set the initial indentation. */ | |
+ | |
+ parser->indent = -1; | |
+ | |
+ /* Initialize the simple key stack. */ | |
+ | |
+ if (!PUSH(parser, parser->simple_keys, simple_key)) | |
+ return 0; | |
+ | |
+ /* A simple key is allowed at the beginning of the stream. */ | |
+ | |
+ parser->simple_key_allowed = 1; | |
+ | |
+ /* We have started. */ | |
+ | |
+ parser->stream_start_produced = 1; | |
+ | |
+ /* Create the STREAM-START token and append it to the queue. */ | |
+ | |
+ STREAM_START_TOKEN_INIT(token, parser->encoding, | |
+ parser->mark, parser->mark); | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the STREAM-END token and shut down the scanner. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_stream_end(yaml_parser_t *parser) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* Force new line. */ | |
+ | |
+ if (parser->mark.column != 0) { | |
+ parser->mark.column = 0; | |
+ parser->mark.line ++; | |
+ } | |
+ | |
+ /* Reset the indentation level. */ | |
+ | |
+ if (!yaml_parser_unroll_indent(parser, -1)) | |
+ return 0; | |
+ | |
+ /* Reset simple keys. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Create the STREAM-END token and append it to the queue. */ | |
+ | |
+ STREAM_END_TOKEN_INIT(token, parser->mark, parser->mark); | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_directive(yaml_parser_t *parser) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* Reset the indentation level. */ | |
+ | |
+ if (!yaml_parser_unroll_indent(parser, -1)) | |
+ return 0; | |
+ | |
+ /* Reset simple keys. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. */ | |
+ | |
+ if (!yaml_parser_scan_directive(parser, &token)) | |
+ return 0; | |
+ | |
+ /* Append the token to the queue. */ | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) { | |
+ yaml_token_delete(&token); | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the DOCUMENT-START or DOCUMENT-END token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_document_indicator(yaml_parser_t *parser, | |
+ yaml_token_type_t type) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_token_t token; | |
+ | |
+ /* Reset the indentation level. */ | |
+ | |
+ if (!yaml_parser_unroll_indent(parser, -1)) | |
+ return 0; | |
+ | |
+ /* Reset simple keys. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Consume the token. */ | |
+ | |
+ start_mark = parser->mark; | |
+ | |
+ SKIP(parser); | |
+ SKIP(parser); | |
+ SKIP(parser); | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create the DOCUMENT-START or DOCUMENT-END token. */ | |
+ | |
+ TOKEN_INIT(token, type, start_mark, end_mark); | |
+ | |
+ /* Append the token to the queue. */ | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_flow_collection_start(yaml_parser_t *parser, | |
+ yaml_token_type_t type) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_token_t token; | |
+ | |
+ /* The indicators '[' and '{' may start a simple key. */ | |
+ | |
+ if (!yaml_parser_save_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* Increase the flow level. */ | |
+ | |
+ if (!yaml_parser_increase_flow_level(parser)) | |
+ return 0; | |
+ | |
+ /* A simple key may follow the indicators '[' and '{'. */ | |
+ | |
+ parser->simple_key_allowed = 1; | |
+ | |
+ /* Consume the token. */ | |
+ | |
+ start_mark = parser->mark; | |
+ SKIP(parser); | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. */ | |
+ | |
+ TOKEN_INIT(token, type, start_mark, end_mark); | |
+ | |
+ /* Append the token to the queue. */ | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_flow_collection_end(yaml_parser_t *parser, | |
+ yaml_token_type_t type) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_token_t token; | |
+ | |
+ /* Reset any potential simple key on the current flow level. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* Decrease the flow level. */ | |
+ | |
+ if (!yaml_parser_decrease_flow_level(parser)) | |
+ return 0; | |
+ | |
+ /* No simple keys after the indicators ']' and '}'. */ | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Consume the token. */ | |
+ | |
+ start_mark = parser->mark; | |
+ SKIP(parser); | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. */ | |
+ | |
+ TOKEN_INIT(token, type, start_mark, end_mark); | |
+ | |
+ /* Append the token to the queue. */ | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the FLOW-ENTRY token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_flow_entry(yaml_parser_t *parser) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_token_t token; | |
+ | |
+ /* Reset any potential simple keys on the current flow level. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* Simple keys are allowed after ','. */ | |
+ | |
+ parser->simple_key_allowed = 1; | |
+ | |
+ /* Consume the token. */ | |
+ | |
+ start_mark = parser->mark; | |
+ SKIP(parser); | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create the FLOW-ENTRY token and append it to the queue. */ | |
+ | |
+ TOKEN_INIT(token, YAML_FLOW_ENTRY_TOKEN, start_mark, end_mark); | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the BLOCK-ENTRY token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_block_entry(yaml_parser_t *parser) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_token_t token; | |
+ | |
+ /* Check if the scanner is in the block context. */ | |
+ | |
+ if (!parser->flow_level) | |
+ { | |
+ /* Check if we are allowed to start a new entry. */ | |
+ | |
+ if (!parser->simple_key_allowed) { | |
+ return yaml_parser_set_scanner_error(parser, NULL, parser->mark, | |
+ "block sequence entries are not allowed in this context"); | |
+ } | |
+ | |
+ /* Add the BLOCK-SEQUENCE-START token if needed. */ | |
+ | |
+ if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, | |
+ YAML_BLOCK_SEQUENCE_START_TOKEN, parser->mark)) | |
+ return 0; | |
+ } | |
+ else | |
+ { | |
+ /* | |
+ * It is an error for the '-' indicator to occur in the flow context, | |
+ * but we let the Parser detect and report about it because the Parser | |
+ * is able to point to the context. | |
+ */ | |
+ } | |
+ | |
+ /* Reset any potential simple keys on the current flow level. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* Simple keys are allowed after '-'. */ | |
+ | |
+ parser->simple_key_allowed = 1; | |
+ | |
+ /* Consume the token. */ | |
+ | |
+ start_mark = parser->mark; | |
+ SKIP(parser); | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create the BLOCK-ENTRY token and append it to the queue. */ | |
+ | |
+ TOKEN_INIT(token, YAML_BLOCK_ENTRY_TOKEN, start_mark, end_mark); | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the KEY token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_key(yaml_parser_t *parser) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_token_t token; | |
+ | |
+ /* In the block context, additional checks are required. */ | |
+ | |
+ if (!parser->flow_level) | |
+ { | |
+ /* Check if we are allowed to start a new key (not nessesary simple). */ | |
+ | |
+ if (!parser->simple_key_allowed) { | |
+ return yaml_parser_set_scanner_error(parser, NULL, parser->mark, | |
+ "mapping keys are not allowed in this context"); | |
+ } | |
+ | |
+ /* Add the BLOCK-MAPPING-START token if needed. */ | |
+ | |
+ if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, | |
+ YAML_BLOCK_MAPPING_START_TOKEN, parser->mark)) | |
+ return 0; | |
+ } | |
+ | |
+ /* Reset any potential simple keys on the current flow level. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* Simple keys are allowed after '?' in the block context. */ | |
+ | |
+ parser->simple_key_allowed = (!parser->flow_level); | |
+ | |
+ /* Consume the token. */ | |
+ | |
+ start_mark = parser->mark; | |
+ SKIP(parser); | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create the KEY token and append it to the queue. */ | |
+ | |
+ TOKEN_INIT(token, YAML_KEY_TOKEN, start_mark, end_mark); | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the VALUE token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_value(yaml_parser_t *parser) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_token_t token; | |
+ yaml_simple_key_t *simple_key = parser->simple_keys.top-1; | |
+ | |
+ /* Have we found a simple key? */ | |
+ | |
+ if (simple_key->possible) | |
+ { | |
+ | |
+ /* Create the KEY token and insert it into the queue. */ | |
+ | |
+ TOKEN_INIT(token, YAML_KEY_TOKEN, simple_key->mark, simple_key->mark); | |
+ | |
+ if (!QUEUE_INSERT(parser, parser->tokens, | |
+ simple_key->token_number - parser->tokens_parsed, token)) | |
+ return 0; | |
+ | |
+ /* In the block context, we may need to add the BLOCK-MAPPING-START token. */ | |
+ | |
+ if (!yaml_parser_roll_indent(parser, simple_key->mark.column, | |
+ simple_key->token_number, | |
+ YAML_BLOCK_MAPPING_START_TOKEN, simple_key->mark)) | |
+ return 0; | |
+ | |
+ /* Remove the simple key. */ | |
+ | |
+ simple_key->possible = 0; | |
+ | |
+ /* A simple key cannot follow another simple key. */ | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ } | |
+ else | |
+ { | |
+ /* The ':' indicator follows a complex key. */ | |
+ | |
+ /* In the block context, extra checks are required. */ | |
+ | |
+ if (!parser->flow_level) | |
+ { | |
+ /* Check if we are allowed to start a complex value. */ | |
+ | |
+ if (!parser->simple_key_allowed) { | |
+ return yaml_parser_set_scanner_error(parser, NULL, parser->mark, | |
+ "mapping values are not allowed in this context"); | |
+ } | |
+ | |
+ /* Add the BLOCK-MAPPING-START token if needed. */ | |
+ | |
+ if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, | |
+ YAML_BLOCK_MAPPING_START_TOKEN, parser->mark)) | |
+ return 0; | |
+ } | |
+ | |
+ /* Simple keys after ':' are allowed in the block context. */ | |
+ | |
+ parser->simple_key_allowed = (!parser->flow_level); | |
+ } | |
+ | |
+ /* Consume the token. */ | |
+ | |
+ start_mark = parser->mark; | |
+ SKIP(parser); | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create the VALUE token and append it to the queue. */ | |
+ | |
+ TOKEN_INIT(token, YAML_VALUE_TOKEN, start_mark, end_mark); | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the ALIAS or ANCHOR token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_anchor(yaml_parser_t *parser, yaml_token_type_t type) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* An anchor or an alias could be a simple key. */ | |
+ | |
+ if (!yaml_parser_save_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* A simple key cannot follow an anchor or an alias. */ | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Create the ALIAS or ANCHOR token and append it to the queue. */ | |
+ | |
+ if (!yaml_parser_scan_anchor(parser, &token, type)) | |
+ return 0; | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) { | |
+ yaml_token_delete(&token); | |
+ return 0; | |
+ } | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the TAG token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_tag(yaml_parser_t *parser) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* A tag could be a simple key. */ | |
+ | |
+ if (!yaml_parser_save_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* A simple key cannot follow a tag. */ | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Create the TAG token and append it to the queue. */ | |
+ | |
+ if (!yaml_parser_scan_tag(parser, &token)) | |
+ return 0; | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) { | |
+ yaml_token_delete(&token); | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_block_scalar(yaml_parser_t *parser, int literal) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* Remove any potential simple keys. */ | |
+ | |
+ if (!yaml_parser_remove_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* A simple key may follow a block scalar. */ | |
+ | |
+ parser->simple_key_allowed = 1; | |
+ | |
+ /* Create the SCALAR token and append it to the queue. */ | |
+ | |
+ if (!yaml_parser_scan_block_scalar(parser, &token, literal)) | |
+ return 0; | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) { | |
+ yaml_token_delete(&token); | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_flow_scalar(yaml_parser_t *parser, int single) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* A plain scalar could be a simple key. */ | |
+ | |
+ if (!yaml_parser_save_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* A simple key cannot follow a flow scalar. */ | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Create the SCALAR token and append it to the queue. */ | |
+ | |
+ if (!yaml_parser_scan_flow_scalar(parser, &token, single)) | |
+ return 0; | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) { | |
+ yaml_token_delete(&token); | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Produce the SCALAR(...,plain) token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_fetch_plain_scalar(yaml_parser_t *parser) | |
+{ | |
+ yaml_token_t token; | |
+ | |
+ /* A plain scalar could be a simple key. */ | |
+ | |
+ if (!yaml_parser_save_simple_key(parser)) | |
+ return 0; | |
+ | |
+ /* A simple key cannot follow a flow scalar. */ | |
+ | |
+ parser->simple_key_allowed = 0; | |
+ | |
+ /* Create the SCALAR token and append it to the queue. */ | |
+ | |
+ if (!yaml_parser_scan_plain_scalar(parser, &token)) | |
+ return 0; | |
+ | |
+ if (!ENQUEUE(parser, parser->tokens, token)) { | |
+ yaml_token_delete(&token); | |
+ return 0; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Eat whitespaces and comments until the next token is found. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_to_next_token(yaml_parser_t *parser) | |
+{ | |
+ /* Until the next token is not found. */ | |
+ | |
+ while (1) | |
+ { | |
+ /* Allow the BOM mark to start a line. */ | |
+ | |
+ if (!CACHE(parser, 1)) return 0; | |
+ | |
+ if (parser->mark.column == 0 && IS_BOM(parser->buffer)) | |
+ SKIP(parser); | |
+ | |
+ /* | |
+ * Eat whitespaces. | |
+ * | |
+ * Tabs are allowed: | |
+ * | |
+ * - in the flow context; | |
+ * - in the block context, but not at the beginning of the line or | |
+ * after '-', '?', or ':' (complex value). | |
+ */ | |
+ | |
+ if (!CACHE(parser, 1)) return 0; | |
+ | |
+ while (CHECK(parser->buffer,' ') || | |
+ ((parser->flow_level || !parser->simple_key_allowed) && | |
+ CHECK(parser->buffer, '\t'))) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) return 0; | |
+ } | |
+ | |
+ /* Eat a comment until a line break. */ | |
+ | |
+ if (CHECK(parser->buffer, '#')) { | |
+ while (!IS_BREAKZ(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) return 0; | |
+ } | |
+ } | |
+ | |
+ /* If it is a line break, eat it. */ | |
+ | |
+ if (IS_BREAK(parser->buffer)) | |
+ { | |
+ if (!CACHE(parser, 2)) return 0; | |
+ SKIP_LINE(parser); | |
+ | |
+ /* In the block context, a new line may start a simple key. */ | |
+ | |
+ if (!parser->flow_level) { | |
+ parser->simple_key_allowed = 1; | |
+ } | |
+ } | |
+ else | |
+ { | |
+ /* We have found a token. */ | |
+ | |
+ break; | |
+ } | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. | |
+ * | |
+ * Scope: | |
+ * %YAML 1.1 # a comment \n | |
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
+ * %TAG !yaml! tag:yaml.org,2002: \n | |
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
+ */ | |
+ | |
+int | |
+yaml_parser_scan_directive(yaml_parser_t *parser, yaml_token_t *token) | |
+{ | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_char_t *name = NULL; | |
+ int major, minor; | |
+ yaml_char_t *handle = NULL, *prefix = NULL; | |
+ | |
+ /* Eat '%'. */ | |
+ | |
+ start_mark = parser->mark; | |
+ | |
+ SKIP(parser); | |
+ | |
+ /* Scan the directive name. */ | |
+ | |
+ if (!yaml_parser_scan_directive_name(parser, start_mark, &name)) | |
+ goto error; | |
+ | |
+ /* Is it a YAML directive? */ | |
+ | |
+ if (strcmp((char *)name, "YAML") == 0) | |
+ { | |
+ /* Scan the VERSION directive value. */ | |
+ | |
+ if (!yaml_parser_scan_version_directive_value(parser, start_mark, | |
+ &major, &minor)) | |
+ goto error; | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create a VERSION-DIRECTIVE token. */ | |
+ | |
+ VERSION_DIRECTIVE_TOKEN_INIT(*token, major, minor, | |
+ start_mark, end_mark); | |
+ } | |
+ | |
+ /* Is it a TAG directive? */ | |
+ | |
+ else if (strcmp((char *)name, "TAG") == 0) | |
+ { | |
+ /* Scan the TAG directive value. */ | |
+ | |
+ if (!yaml_parser_scan_tag_directive_value(parser, start_mark, | |
+ &handle, &prefix)) | |
+ goto error; | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create a TAG-DIRECTIVE token. */ | |
+ | |
+ TAG_DIRECTIVE_TOKEN_INIT(*token, handle, prefix, | |
+ start_mark, end_mark); | |
+ } | |
+ | |
+ /* Unknown directive. */ | |
+ | |
+ else | |
+ { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a directive", | |
+ start_mark, "found uknown directive name"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Eat the rest of the line including any comments. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_BLANK(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ if (CHECK(parser->buffer, '#')) { | |
+ while (!IS_BREAKZ(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ } | |
+ | |
+ /* Check if we are at the end of the line. */ | |
+ | |
+ if (!IS_BREAKZ(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a directive", | |
+ start_mark, "did not find expected comment or line break"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Eat a line break. */ | |
+ | |
+ if (IS_BREAK(parser->buffer)) { | |
+ if (!CACHE(parser, 2)) goto error; | |
+ SKIP_LINE(parser); | |
+ } | |
+ | |
+ yaml_free(name); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(prefix); | |
+ yaml_free(handle); | |
+ yaml_free(name); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Scan the directive name. | |
+ * | |
+ * Scope: | |
+ * %YAML 1.1 # a comment \n | |
+ * ^^^^ | |
+ * %TAG !yaml! tag:yaml.org,2002: \n | |
+ * ^^^ | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_directive_name(yaml_parser_t *parser, | |
+ yaml_mark_t start_mark, yaml_char_t **name) | |
+{ | |
+ yaml_string_t string = NULL_STRING; | |
+ | |
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; | |
+ | |
+ /* Consume the directive name. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_ALPHA(parser->buffer)) | |
+ { | |
+ if (!READ(parser, string)) goto error; | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Check if the name is empty. */ | |
+ | |
+ if (string.start == string.pointer) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a directive", | |
+ start_mark, "could not find expected directive name"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Check for an blank character after the name. */ | |
+ | |
+ if (!IS_BLANKZ(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a directive", | |
+ start_mark, "found unexpected non-alphabetical character"); | |
+ goto error; | |
+ } | |
+ | |
+ *name = string.start; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STRING_DEL(parser, string); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Scan the value of VERSION-DIRECTIVE. | |
+ * | |
+ * Scope: | |
+ * %YAML 1.1 # a comment \n | |
+ * ^^^^^^ | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_version_directive_value(yaml_parser_t *parser, | |
+ yaml_mark_t start_mark, int *major, int *minor) | |
+{ | |
+ /* Eat whitespaces. */ | |
+ | |
+ if (!CACHE(parser, 1)) return 0; | |
+ | |
+ while (IS_BLANK(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) return 0; | |
+ } | |
+ | |
+ /* Consume the major version number. */ | |
+ | |
+ if (!yaml_parser_scan_version_directive_number(parser, start_mark, major)) | |
+ return 0; | |
+ | |
+ /* Eat '.'. */ | |
+ | |
+ if (!CHECK(parser->buffer, '.')) { | |
+ return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", | |
+ start_mark, "did not find expected digit or '.' character"); | |
+ } | |
+ | |
+ SKIP(parser); | |
+ | |
+ /* Consume the minor version number. */ | |
+ | |
+ if (!yaml_parser_scan_version_directive_number(parser, start_mark, minor)) | |
+ return 0; | |
+ | |
+ return 1; | |
+} | |
+ | |
+#define MAX_NUMBER_LENGTH 9 | |
+ | |
+/* | |
+ * Scan the version number of VERSION-DIRECTIVE. | |
+ * | |
+ * Scope: | |
+ * %YAML 1.1 # a comment \n | |
+ * ^ | |
+ * %YAML 1.1 # a comment \n | |
+ * ^ | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_version_directive_number(yaml_parser_t *parser, | |
+ yaml_mark_t start_mark, int *number) | |
+{ | |
+ int value = 0; | |
+ size_t length = 0; | |
+ | |
+ /* Repeat while the next character is digit. */ | |
+ | |
+ if (!CACHE(parser, 1)) return 0; | |
+ | |
+ while (IS_DIGIT(parser->buffer)) | |
+ { | |
+ /* Check if the number is too long. */ | |
+ | |
+ if (++length > MAX_NUMBER_LENGTH) { | |
+ return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", | |
+ start_mark, "found extremely long version number"); | |
+ } | |
+ | |
+ value = value*10 + AS_DIGIT(parser->buffer); | |
+ | |
+ SKIP(parser); | |
+ | |
+ if (!CACHE(parser, 1)) return 0; | |
+ } | |
+ | |
+ /* Check if the number was present. */ | |
+ | |
+ if (!length) { | |
+ return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", | |
+ start_mark, "did not find expected version number"); | |
+ } | |
+ | |
+ *number = value; | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Scan the value of a TAG-DIRECTIVE token. | |
+ * | |
+ * Scope: | |
+ * %TAG !yaml! tag:yaml.org,2002: \n | |
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_tag_directive_value(yaml_parser_t *parser, | |
+ yaml_mark_t start_mark, yaml_char_t **handle, yaml_char_t **prefix) | |
+{ | |
+ yaml_char_t *handle_value = NULL; | |
+ yaml_char_t *prefix_value = NULL; | |
+ | |
+ /* Eat whitespaces. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_BLANK(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Scan a handle. */ | |
+ | |
+ if (!yaml_parser_scan_tag_handle(parser, 1, start_mark, &handle_value)) | |
+ goto error; | |
+ | |
+ /* Expect a whitespace. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ if (!IS_BLANK(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", | |
+ start_mark, "did not find expected whitespace"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Eat whitespaces. */ | |
+ | |
+ while (IS_BLANK(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Scan a prefix. */ | |
+ | |
+ if (!yaml_parser_scan_tag_uri(parser, 1, NULL, start_mark, &prefix_value)) | |
+ goto error; | |
+ | |
+ /* Expect a whitespace or line break. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ if (!IS_BLANKZ(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", | |
+ start_mark, "did not find expected whitespace or line break"); | |
+ goto error; | |
+ } | |
+ | |
+ *handle = handle_value; | |
+ *prefix = prefix_value; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(handle_value); | |
+ yaml_free(prefix_value); | |
+ return 0; | |
+} | |
+ | |
+static int | |
+yaml_parser_scan_anchor(yaml_parser_t *parser, yaml_token_t *token, | |
+ yaml_token_type_t type) | |
+{ | |
+ int length = 0; | |
+ yaml_mark_t start_mark, end_mark; | |
+ yaml_string_t string = NULL_STRING; | |
+ | |
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; | |
+ | |
+ /* Eat the indicator character. */ | |
+ | |
+ start_mark = parser->mark; | |
+ | |
+ SKIP(parser); | |
+ | |
+ /* Consume the value. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_ALPHA(parser->buffer)) { | |
+ if (!READ(parser, string)) goto error; | |
+ if (!CACHE(parser, 1)) goto error; | |
+ length ++; | |
+ } | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ /* | |
+ * Check if length of the anchor is greater than 0 and it is followed by | |
+ * a whitespace character or one of the indicators: | |
+ * | |
+ * '?', ':', ',', ']', '}', '%', '@', '`'. | |
+ */ | |
+ | |
+ if (!length || !(IS_BLANKZ(parser->buffer) || CHECK(parser->buffer, '?') | |
+ || CHECK(parser->buffer, ':') || CHECK(parser->buffer, ',') | |
+ || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '}') | |
+ || CHECK(parser->buffer, '%') || CHECK(parser->buffer, '@') | |
+ || CHECK(parser->buffer, '`'))) { | |
+ yaml_parser_set_scanner_error(parser, type == YAML_ANCHOR_TOKEN ? | |
+ "while scanning an anchor" : "while scanning an alias", start_mark, | |
+ "did not find expected alphabetic or numeric character"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Create a token. */ | |
+ | |
+ if (type == YAML_ANCHOR_TOKEN) { | |
+ ANCHOR_TOKEN_INIT(*token, string.start, start_mark, end_mark); | |
+ } | |
+ else { | |
+ ALIAS_TOKEN_INIT(*token, string.start, start_mark, end_mark); | |
+ } | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STRING_DEL(parser, string); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Scan a TAG token. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_tag(yaml_parser_t *parser, yaml_token_t *token) | |
+{ | |
+ yaml_char_t *handle = NULL; | |
+ yaml_char_t *suffix = NULL; | |
+ yaml_mark_t start_mark, end_mark; | |
+ | |
+ start_mark = parser->mark; | |
+ | |
+ /* Check if the tag is in the canonical form. */ | |
+ | |
+ if (!CACHE(parser, 2)) goto error; | |
+ | |
+ if (CHECK_AT(parser->buffer, '<', 1)) | |
+ { | |
+ /* Set the handle to '' */ | |
+ | |
+ handle = yaml_malloc(1); | |
+ if (!handle) goto error; | |
+ handle[0] = '\0'; | |
+ | |
+ /* Eat '!<' */ | |
+ | |
+ SKIP(parser); | |
+ SKIP(parser); | |
+ | |
+ /* Consume the tag value. */ | |
+ | |
+ if (!yaml_parser_scan_tag_uri(parser, 0, NULL, start_mark, &suffix)) | |
+ goto error; | |
+ | |
+ /* Check for '>' and eat it. */ | |
+ | |
+ if (!CHECK(parser->buffer, '>')) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a tag", | |
+ start_mark, "did not find the expected '>'"); | |
+ goto error; | |
+ } | |
+ | |
+ SKIP(parser); | |
+ } | |
+ else | |
+ { | |
+ /* The tag has either the '!suffix' or the '!handle!suffix' form. */ | |
+ | |
+ /* First, try to scan a handle. */ | |
+ | |
+ if (!yaml_parser_scan_tag_handle(parser, 0, start_mark, &handle)) | |
+ goto error; | |
+ | |
+ /* Check if it is, indeed, handle. */ | |
+ | |
+ if (handle[0] == '!' && handle[1] != '\0' && handle[strlen((char *)handle)-1] == '!') | |
+ { | |
+ /* Scan the suffix now. */ | |
+ | |
+ if (!yaml_parser_scan_tag_uri(parser, 0, NULL, start_mark, &suffix)) | |
+ goto error; | |
+ } | |
+ else | |
+ { | |
+ /* It wasn't a handle after all. Scan the rest of the tag. */ | |
+ | |
+ if (!yaml_parser_scan_tag_uri(parser, 0, handle, start_mark, &suffix)) | |
+ goto error; | |
+ | |
+ /* Set the handle to '!'. */ | |
+ | |
+ yaml_free(handle); | |
+ handle = yaml_malloc(2); | |
+ if (!handle) goto error; | |
+ handle[0] = '!'; | |
+ handle[1] = '\0'; | |
+ | |
+ /* | |
+ * A special case: the '!' tag. Set the handle to '' and the | |
+ * suffix to '!'. | |
+ */ | |
+ | |
+ if (suffix[0] == '\0') { | |
+ yaml_char_t *tmp = handle; | |
+ handle = suffix; | |
+ suffix = tmp; | |
+ } | |
+ } | |
+ } | |
+ | |
+ /* Check the character which ends the tag. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ if (!IS_BLANKZ(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a tag", | |
+ start_mark, "did not find expected whitespace or line break"); | |
+ goto error; | |
+ } | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create a token. */ | |
+ | |
+ TAG_TOKEN_INIT(*token, handle, suffix, start_mark, end_mark); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ yaml_free(handle); | |
+ yaml_free(suffix); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Scan a tag handle. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_tag_handle(yaml_parser_t *parser, int directive, | |
+ yaml_mark_t start_mark, yaml_char_t **handle) | |
+{ | |
+ yaml_string_t string = NULL_STRING; | |
+ | |
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; | |
+ | |
+ /* Check the initial '!' character. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ if (!CHECK(parser->buffer, '!')) { | |
+ yaml_parser_set_scanner_error(parser, directive ? | |
+ "while scanning a tag directive" : "while scanning a tag", | |
+ start_mark, "did not find expected '!'"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Copy the '!' character. */ | |
+ | |
+ if (!READ(parser, string)) goto error; | |
+ | |
+ /* Copy all subsequent alphabetical and numerical characters. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_ALPHA(parser->buffer)) | |
+ { | |
+ if (!READ(parser, string)) goto error; | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Check if the trailing character is '!' and copy it. */ | |
+ | |
+ if (CHECK(parser->buffer, '!')) | |
+ { | |
+ if (!READ(parser, string)) goto error; | |
+ } | |
+ else | |
+ { | |
+ /* | |
+ * It's either the '!' tag or not really a tag handle. If it's a %TAG | |
+ * directive, it's an error. If it's a tag token, it must be a part of | |
+ * URI. | |
+ */ | |
+ | |
+ if (directive && !(string.start[0] == '!' && string.start[1] == '\0')) { | |
+ yaml_parser_set_scanner_error(parser, "while parsing a tag directive", | |
+ start_mark, "did not find expected '!'"); | |
+ goto error; | |
+ } | |
+ } | |
+ | |
+ *handle = string.start; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STRING_DEL(parser, string); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Scan a tag. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_tag_uri(yaml_parser_t *parser, int directive, | |
+ yaml_char_t *head, yaml_mark_t start_mark, yaml_char_t **uri) | |
+{ | |
+ size_t length = head ? strlen((char *)head) : 0; | |
+ yaml_string_t string = NULL_STRING; | |
+ | |
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; | |
+ | |
+ /* Resize the string to include the head. */ | |
+ | |
+ while (string.end - string.start <= (int)length) { | |
+ if (!yaml_string_extend(&string.start, &string.pointer, &string.end)) { | |
+ parser->error = YAML_MEMORY_ERROR; | |
+ goto error; | |
+ } | |
+ } | |
+ | |
+ /* | |
+ * Copy the head if needed. | |
+ * | |
+ * Note that we don't copy the leading '!' character. | |
+ */ | |
+ | |
+ if (length > 1) { | |
+ memcpy(string.start, head+1, length-1); | |
+ string.pointer += length-1; | |
+ } | |
+ | |
+ /* Scan the tag. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ /* | |
+ * The set of characters that may appear in URI is as follows: | |
+ * | |
+ * '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', | |
+ * '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', | |
+ * '%'. | |
+ */ | |
+ | |
+ while (IS_ALPHA(parser->buffer) || CHECK(parser->buffer, ';') | |
+ || CHECK(parser->buffer, '/') || CHECK(parser->buffer, '?') | |
+ || CHECK(parser->buffer, ':') || CHECK(parser->buffer, '@') | |
+ || CHECK(parser->buffer, '&') || CHECK(parser->buffer, '=') | |
+ || CHECK(parser->buffer, '+') || CHECK(parser->buffer, '$') | |
+ || CHECK(parser->buffer, ',') || CHECK(parser->buffer, '.') | |
+ || CHECK(parser->buffer, '!') || CHECK(parser->buffer, '~') | |
+ || CHECK(parser->buffer, '*') || CHECK(parser->buffer, '\'') | |
+ || CHECK(parser->buffer, '(') || CHECK(parser->buffer, ')') | |
+ || CHECK(parser->buffer, '[') || CHECK(parser->buffer, ']') | |
+ || CHECK(parser->buffer, '%')) | |
+ { | |
+ /* Check if it is a URI-escape sequence. */ | |
+ | |
+ if (CHECK(parser->buffer, '%')) { | |
+ if (!yaml_parser_scan_uri_escapes(parser, | |
+ directive, start_mark, &string)) goto error; | |
+ } | |
+ else { | |
+ if (!READ(parser, string)) goto error; | |
+ } | |
+ | |
+ length ++; | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Check if the tag is non-empty. */ | |
+ | |
+ if (!length) { | |
+ if (!STRING_EXTEND(parser, string)) | |
+ goto error; | |
+ | |
+ yaml_parser_set_scanner_error(parser, directive ? | |
+ "while parsing a %TAG directive" : "while parsing a tag", | |
+ start_mark, "did not find expected tag URI"); | |
+ goto error; | |
+ } | |
+ | |
+ *uri = string.start; | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STRING_DEL(parser, string); | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Decode an URI-escape sequence corresponding to a single UTF-8 character. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_uri_escapes(yaml_parser_t *parser, int directive, | |
+ yaml_mark_t start_mark, yaml_string_t *string) | |
+{ | |
+ int width = 0; | |
+ | |
+ /* Decode the required number of characters. */ | |
+ | |
+ do { | |
+ | |
+ unsigned char octet = 0; | |
+ | |
+ /* Check for a URI-escaped octet. */ | |
+ | |
+ if (!CACHE(parser, 3)) return 0; | |
+ | |
+ if (!(CHECK(parser->buffer, '%') | |
+ && IS_HEX_AT(parser->buffer, 1) | |
+ && IS_HEX_AT(parser->buffer, 2))) { | |
+ return yaml_parser_set_scanner_error(parser, directive ? | |
+ "while parsing a %TAG directive" : "while parsing a tag", | |
+ start_mark, "did not find URI escaped octet"); | |
+ } | |
+ | |
+ /* Get the octet. */ | |
+ | |
+ octet = (AS_HEX_AT(parser->buffer, 1) << 4) + AS_HEX_AT(parser->buffer, 2); | |
+ | |
+ /* If it is the leading octet, determine the length of the UTF-8 sequence. */ | |
+ | |
+ if (!width) | |
+ { | |
+ width = (octet & 0x80) == 0x00 ? 1 : | |
+ (octet & 0xE0) == 0xC0 ? 2 : | |
+ (octet & 0xF0) == 0xE0 ? 3 : | |
+ (octet & 0xF8) == 0xF0 ? 4 : 0; | |
+ if (!width) { | |
+ return yaml_parser_set_scanner_error(parser, directive ? | |
+ "while parsing a %TAG directive" : "while parsing a tag", | |
+ start_mark, "found an incorrect leading UTF-8 octet"); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ /* Check if the trailing octet is correct. */ | |
+ | |
+ if ((octet & 0xC0) != 0x80) { | |
+ return yaml_parser_set_scanner_error(parser, directive ? | |
+ "while parsing a %TAG directive" : "while parsing a tag", | |
+ start_mark, "found an incorrect trailing UTF-8 octet"); | |
+ } | |
+ } | |
+ | |
+ /* Copy the octet and move the pointers. */ | |
+ | |
+ *(string->pointer++) = octet; | |
+ SKIP(parser); | |
+ SKIP(parser); | |
+ SKIP(parser); | |
+ | |
+ } while (--width); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Scan a block scalar. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_block_scalar(yaml_parser_t *parser, yaml_token_t *token, | |
+ int literal) | |
+{ | |
+ yaml_mark_t start_mark; | |
+ yaml_mark_t end_mark; | |
+ yaml_string_t string = NULL_STRING; | |
+ yaml_string_t leading_break = NULL_STRING; | |
+ yaml_string_t trailing_breaks = NULL_STRING; | |
+ int chomping = 0; | |
+ int increment = 0; | |
+ int indent = 0; | |
+ int leading_blank = 0; | |
+ int trailing_blank = 0; | |
+ | |
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; | |
+ | |
+ /* Eat the indicator '|' or '>'. */ | |
+ | |
+ start_mark = parser->mark; | |
+ | |
+ SKIP(parser); | |
+ | |
+ /* Scan the additional block scalar indicators. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ /* Check for a chomping indicator. */ | |
+ | |
+ if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-')) | |
+ { | |
+ /* Set the chomping method and eat the indicator. */ | |
+ | |
+ chomping = CHECK(parser->buffer, '+') ? +1 : -1; | |
+ | |
+ SKIP(parser); | |
+ | |
+ /* Check for an indentation indicator. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ if (IS_DIGIT(parser->buffer)) | |
+ { | |
+ /* Check that the indentation is greater than 0. */ | |
+ | |
+ if (CHECK(parser->buffer, '0')) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a block scalar", | |
+ start_mark, "found an indentation indicator equal to 0"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Get the indentation level and eat the indicator. */ | |
+ | |
+ increment = AS_DIGIT(parser->buffer); | |
+ | |
+ SKIP(parser); | |
+ } | |
+ } | |
+ | |
+ /* Do the same as above, but in the opposite order. */ | |
+ | |
+ else if (IS_DIGIT(parser->buffer)) | |
+ { | |
+ if (CHECK(parser->buffer, '0')) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a block scalar", | |
+ start_mark, "found an indentation indicator equal to 0"); | |
+ goto error; | |
+ } | |
+ | |
+ increment = AS_DIGIT(parser->buffer); | |
+ | |
+ SKIP(parser); | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-')) { | |
+ chomping = CHECK(parser->buffer, '+') ? +1 : -1; | |
+ | |
+ SKIP(parser); | |
+ } | |
+ } | |
+ | |
+ /* Eat whitespaces and comments to the end of the line. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_BLANK(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ if (CHECK(parser->buffer, '#')) { | |
+ while (!IS_BREAKZ(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ } | |
+ | |
+ /* Check if we are at the end of the line. */ | |
+ | |
+ if (!IS_BREAKZ(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a block scalar", | |
+ start_mark, "did not find expected comment or line break"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Eat a line break. */ | |
+ | |
+ if (IS_BREAK(parser->buffer)) { | |
+ if (!CACHE(parser, 2)) goto error; | |
+ SKIP_LINE(parser); | |
+ } | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ /* Set the indentation level if it was specified. */ | |
+ | |
+ if (increment) { | |
+ indent = parser->indent >= 0 ? parser->indent+increment : increment; | |
+ } | |
+ | |
+ /* Scan the leading line breaks and determine the indentation level if needed. */ | |
+ | |
+ if (!yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, | |
+ start_mark, &end_mark)) goto error; | |
+ | |
+ /* Scan the block scalar content. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while ((int)parser->mark.column == indent && !IS_Z(parser->buffer)) | |
+ { | |
+ /* | |
+ * We are at the beginning of a non-empty line. | |
+ */ | |
+ | |
+ /* Is it a trailing whitespace? */ | |
+ | |
+ trailing_blank = IS_BLANK(parser->buffer); | |
+ | |
+ /* Check if we need to fold the leading line break. */ | |
+ | |
+ if (!literal && (*leading_break.start == '\n') | |
+ && !leading_blank && !trailing_blank) | |
+ { | |
+ /* Do we need to join the lines by space? */ | |
+ | |
+ if (*trailing_breaks.start == '\0') { | |
+ if (!STRING_EXTEND(parser, string)) goto error; | |
+ *(string.pointer ++) = ' '; | |
+ } | |
+ | |
+ CLEAR(parser, leading_break); | |
+ } | |
+ else { | |
+ if (!JOIN(parser, string, leading_break)) goto error; | |
+ CLEAR(parser, leading_break); | |
+ } | |
+ | |
+ /* Append the remaining line breaks. */ | |
+ | |
+ if (!JOIN(parser, string, trailing_breaks)) goto error; | |
+ CLEAR(parser, trailing_breaks); | |
+ | |
+ /* Is it a leading whitespace? */ | |
+ | |
+ leading_blank = IS_BLANK(parser->buffer); | |
+ | |
+ /* Consume the current line. */ | |
+ | |
+ while (!IS_BREAKZ(parser->buffer)) { | |
+ if (!READ(parser, string)) goto error; | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Consume the line break. */ | |
+ | |
+ if (!CACHE(parser, 2)) goto error; | |
+ | |
+ if (!READ_LINE(parser, leading_break)) goto error; | |
+ | |
+ /* Eat the following indentation spaces and line breaks. */ | |
+ | |
+ if (!yaml_parser_scan_block_scalar_breaks(parser, | |
+ &indent, &trailing_breaks, start_mark, &end_mark)) goto error; | |
+ } | |
+ | |
+ /* Chomp the tail. */ | |
+ | |
+ if (chomping != -1) { | |
+ if (!JOIN(parser, string, leading_break)) goto error; | |
+ } | |
+ if (chomping == 1) { | |
+ if (!JOIN(parser, string, trailing_breaks)) goto error; | |
+ } | |
+ | |
+ /* Create a token. */ | |
+ | |
+ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, | |
+ literal ? YAML_LITERAL_SCALAR_STYLE : YAML_FOLDED_SCALAR_STYLE, | |
+ start_mark, end_mark); | |
+ | |
+ STRING_DEL(parser, leading_break); | |
+ STRING_DEL(parser, trailing_breaks); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STRING_DEL(parser, string); | |
+ STRING_DEL(parser, leading_break); | |
+ STRING_DEL(parser, trailing_breaks); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Scan indentation spaces and line breaks for a block scalar. Determine the | |
+ * indentation level if needed. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_block_scalar_breaks(yaml_parser_t *parser, | |
+ int *indent, yaml_string_t *breaks, | |
+ yaml_mark_t start_mark, yaml_mark_t *end_mark) | |
+{ | |
+ int max_indent = 0; | |
+ | |
+ *end_mark = parser->mark; | |
+ | |
+ /* Eat the indentation spaces and line breaks. */ | |
+ | |
+ while (1) | |
+ { | |
+ /* Eat the indentation spaces. */ | |
+ | |
+ if (!CACHE(parser, 1)) return 0; | |
+ | |
+ while ((!*indent || (int)parser->mark.column < *indent) | |
+ && IS_SPACE(parser->buffer)) { | |
+ SKIP(parser); | |
+ if (!CACHE(parser, 1)) return 0; | |
+ } | |
+ | |
+ if ((int)parser->mark.column > max_indent) | |
+ max_indent = (int)parser->mark.column; | |
+ | |
+ /* Check for a tab character messing the indentation. */ | |
+ | |
+ if ((!*indent || (int)parser->mark.column < *indent) | |
+ && IS_TAB(parser->buffer)) { | |
+ return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", | |
+ start_mark, "found a tab character where an indentation space is expected"); | |
+ } | |
+ | |
+ /* Have we found a non-empty line? */ | |
+ | |
+ if (!IS_BREAK(parser->buffer)) break; | |
+ | |
+ /* Consume the line break. */ | |
+ | |
+ if (!CACHE(parser, 2)) return 0; | |
+ if (!READ_LINE(parser, *breaks)) return 0; | |
+ *end_mark = parser->mark; | |
+ } | |
+ | |
+ /* Determine the indentation level if needed. */ | |
+ | |
+ if (!*indent) { | |
+ *indent = max_indent; | |
+ if (*indent < parser->indent + 1) | |
+ *indent = parser->indent + 1; | |
+ if (*indent < 1) | |
+ *indent = 1; | |
+ } | |
+ | |
+ return 1; | |
+} | |
+ | |
+/* | |
+ * Scan a quoted scalar. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token, | |
+ int single) | |
+{ | |
+ yaml_mark_t start_mark; | |
+ yaml_mark_t end_mark; | |
+ yaml_string_t string = NULL_STRING; | |
+ yaml_string_t leading_break = NULL_STRING; | |
+ yaml_string_t trailing_breaks = NULL_STRING; | |
+ yaml_string_t whitespaces = NULL_STRING; | |
+ int leading_blanks; | |
+ | |
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error; | |
+ | |
+ /* Eat the left quote. */ | |
+ | |
+ start_mark = parser->mark; | |
+ | |
+ SKIP(parser); | |
+ | |
+ /* Consume the content of the quoted scalar. */ | |
+ | |
+ while (1) | |
+ { | |
+ /* Check that there are no document indicators at the beginning of the line. */ | |
+ | |
+ if (!CACHE(parser, 4)) goto error; | |
+ | |
+ if (parser->mark.column == 0 && | |
+ ((CHECK_AT(parser->buffer, '-', 0) && | |
+ CHECK_AT(parser->buffer, '-', 1) && | |
+ CHECK_AT(parser->buffer, '-', 2)) || | |
+ (CHECK_AT(parser->buffer, '.', 0) && | |
+ CHECK_AT(parser->buffer, '.', 1) && | |
+ CHECK_AT(parser->buffer, '.', 2))) && | |
+ IS_BLANKZ_AT(parser->buffer, 3)) | |
+ { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", | |
+ start_mark, "found unexpected document indicator"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Check for EOF. */ | |
+ | |
+ if (IS_Z(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", | |
+ start_mark, "found unexpected end of stream"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Consume non-blank characters. */ | |
+ | |
+ if (!CACHE(parser, 2)) goto error; | |
+ | |
+ leading_blanks = 0; | |
+ | |
+ while (!IS_BLANKZ(parser->buffer)) | |
+ { | |
+ /* Check for an escaped single quote. */ | |
+ | |
+ if (single && CHECK_AT(parser->buffer, '\'', 0) | |
+ && CHECK_AT(parser->buffer, '\'', 1)) | |
+ { | |
+ if (!STRING_EXTEND(parser, string)) goto error; | |
+ *(string.pointer++) = '\''; | |
+ SKIP(parser); | |
+ SKIP(parser); | |
+ } | |
+ | |
+ /* Check for the right quote. */ | |
+ | |
+ else if (CHECK(parser->buffer, single ? '\'' : '"')) | |
+ { | |
+ break; | |
+ } | |
+ | |
+ /* Check for an escaped line break. */ | |
+ | |
+ else if (!single && CHECK(parser->buffer, '\\') | |
+ && IS_BREAK_AT(parser->buffer, 1)) | |
+ { | |
+ if (!CACHE(parser, 3)) goto error; | |
+ SKIP(parser); | |
+ SKIP_LINE(parser); | |
+ leading_blanks = 1; | |
+ break; | |
+ } | |
+ | |
+ /* Check for an escape sequence. */ | |
+ | |
+ else if (!single && CHECK(parser->buffer, '\\')) | |
+ { | |
+ size_t code_length = 0; | |
+ | |
+ if (!STRING_EXTEND(parser, string)) goto error; | |
+ | |
+ /* Check the escape character. */ | |
+ | |
+ switch (parser->buffer.pointer[1]) | |
+ { | |
+ case '0': | |
+ *(string.pointer++) = '\0'; | |
+ break; | |
+ | |
+ case 'a': | |
+ *(string.pointer++) = '\x07'; | |
+ break; | |
+ | |
+ case 'b': | |
+ *(string.pointer++) = '\x08'; | |
+ break; | |
+ | |
+ case 't': | |
+ case '\t': | |
+ *(string.pointer++) = '\x09'; | |
+ break; | |
+ | |
+ case 'n': | |
+ *(string.pointer++) = '\x0A'; | |
+ break; | |
+ | |
+ case 'v': | |
+ *(string.pointer++) = '\x0B'; | |
+ break; | |
+ | |
+ case 'f': | |
+ *(string.pointer++) = '\x0C'; | |
+ break; | |
+ | |
+ case 'r': | |
+ *(string.pointer++) = '\x0D'; | |
+ break; | |
+ | |
+ case 'e': | |
+ *(string.pointer++) = '\x1B'; | |
+ break; | |
+ | |
+ case ' ': | |
+ *(string.pointer++) = '\x20'; | |
+ break; | |
+ | |
+ case '"': | |
+ *(string.pointer++) = '"'; | |
+ break; | |
+ | |
+ case '\'': | |
+ *(string.pointer++) = '\''; | |
+ break; | |
+ | |
+ case '\\': | |
+ *(string.pointer++) = '\\'; | |
+ break; | |
+ | |
+ case 'N': /* NEL (#x85) */ | |
+ *(string.pointer++) = '\xC2'; | |
+ *(string.pointer++) = '\x85'; | |
+ break; | |
+ | |
+ case '_': /* #xA0 */ | |
+ *(string.pointer++) = '\xC2'; | |
+ *(string.pointer++) = '\xA0'; | |
+ break; | |
+ | |
+ case 'L': /* LS (#x2028) */ | |
+ *(string.pointer++) = '\xE2'; | |
+ *(string.pointer++) = '\x80'; | |
+ *(string.pointer++) = '\xA8'; | |
+ break; | |
+ | |
+ case 'P': /* PS (#x2029) */ | |
+ *(string.pointer++) = '\xE2'; | |
+ *(string.pointer++) = '\x80'; | |
+ *(string.pointer++) = '\xA9'; | |
+ break; | |
+ | |
+ case 'x': | |
+ code_length = 2; | |
+ break; | |
+ | |
+ case 'u': | |
+ code_length = 4; | |
+ break; | |
+ | |
+ case 'U': | |
+ code_length = 8; | |
+ break; | |
+ | |
+ default: | |
+ yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", | |
+ start_mark, "found unknown escape character"); | |
+ goto error; | |
+ } | |
+ | |
+ SKIP(parser); | |
+ SKIP(parser); | |
+ | |
+ /* Consume an arbitrary escape code. */ | |
+ | |
+ if (code_length) | |
+ { | |
+ unsigned int value = 0; | |
+ size_t k; | |
+ | |
+ /* Scan the character value. */ | |
+ | |
+ if (!CACHE(parser, code_length)) goto error; | |
+ | |
+ for (k = 0; k < code_length; k ++) { | |
+ if (!IS_HEX_AT(parser->buffer, k)) { | |
+ yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", | |
+ start_mark, "did not find expected hexdecimal number"); | |
+ goto error; | |
+ } | |
+ value = (value << 4) + AS_HEX_AT(parser->buffer, k); | |
+ } | |
+ | |
+ /* Check the value and write the character. */ | |
+ | |
+ if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) { | |
+ yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", | |
+ start_mark, "found invalid Unicode character escape code"); | |
+ goto error; | |
+ } | |
+ | |
+ if (value <= 0x7F) { | |
+ *(string.pointer++) = value; | |
+ } | |
+ else if (value <= 0x7FF) { | |
+ *(string.pointer++) = 0xC0 + (value >> 6); | |
+ *(string.pointer++) = 0x80 + (value & 0x3F); | |
+ } | |
+ else if (value <= 0xFFFF) { | |
+ *(string.pointer++) = 0xE0 + (value >> 12); | |
+ *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F); | |
+ *(string.pointer++) = 0x80 + (value & 0x3F); | |
+ } | |
+ else { | |
+ *(string.pointer++) = 0xF0 + (value >> 18); | |
+ *(string.pointer++) = 0x80 + ((value >> 12) & 0x3F); | |
+ *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F); | |
+ *(string.pointer++) = 0x80 + (value & 0x3F); | |
+ } | |
+ | |
+ /* Advance the pointer. */ | |
+ | |
+ for (k = 0; k < code_length; k ++) { | |
+ SKIP(parser); | |
+ } | |
+ } | |
+ } | |
+ | |
+ else | |
+ { | |
+ /* It is a non-escaped non-blank character. */ | |
+ | |
+ if (!READ(parser, string)) goto error; | |
+ } | |
+ | |
+ if (!CACHE(parser, 2)) goto error; | |
+ } | |
+ | |
+ /* Check if we are at the end of the scalar. */ | |
+ | |
+ if (CHECK(parser->buffer, single ? '\'' : '"')) | |
+ break; | |
+ | |
+ /* Consume blank characters. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer)) | |
+ { | |
+ if (IS_BLANK(parser->buffer)) | |
+ { | |
+ /* Consume a space or a tab character. */ | |
+ | |
+ if (!leading_blanks) { | |
+ if (!READ(parser, whitespaces)) goto error; | |
+ } | |
+ else { | |
+ SKIP(parser); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ if (!CACHE(parser, 2)) goto error; | |
+ | |
+ /* Check if it is a first line break. */ | |
+ | |
+ if (!leading_blanks) | |
+ { | |
+ CLEAR(parser, whitespaces); | |
+ if (!READ_LINE(parser, leading_break)) goto error; | |
+ leading_blanks = 1; | |
+ } | |
+ else | |
+ { | |
+ if (!READ_LINE(parser, trailing_breaks)) goto error; | |
+ } | |
+ } | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Join the whitespaces or fold line breaks. */ | |
+ | |
+ if (leading_blanks) | |
+ { | |
+ /* Do we need to fold line breaks? */ | |
+ | |
+ if (leading_break.start[0] == '\n') { | |
+ if (trailing_breaks.start[0] == '\0') { | |
+ if (!STRING_EXTEND(parser, string)) goto error; | |
+ *(string.pointer++) = ' '; | |
+ } | |
+ else { | |
+ if (!JOIN(parser, string, trailing_breaks)) goto error; | |
+ CLEAR(parser, trailing_breaks); | |
+ } | |
+ CLEAR(parser, leading_break); | |
+ } | |
+ else { | |
+ if (!JOIN(parser, string, leading_break)) goto error; | |
+ if (!JOIN(parser, string, trailing_breaks)) goto error; | |
+ CLEAR(parser, leading_break); | |
+ CLEAR(parser, trailing_breaks); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ if (!JOIN(parser, string, whitespaces)) goto error; | |
+ CLEAR(parser, whitespaces); | |
+ } | |
+ } | |
+ | |
+ /* Eat the right quote. */ | |
+ | |
+ SKIP(parser); | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ /* Create a token. */ | |
+ | |
+ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, | |
+ single ? YAML_SINGLE_QUOTED_SCALAR_STYLE : YAML_DOUBLE_QUOTED_SCALAR_STYLE, | |
+ start_mark, end_mark); | |
+ | |
+ STRING_DEL(parser, leading_break); | |
+ STRING_DEL(parser, trailing_breaks); | |
+ STRING_DEL(parser, whitespaces); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STRING_DEL(parser, string); | |
+ STRING_DEL(parser, leading_break); | |
+ STRING_DEL(parser, trailing_breaks); | |
+ STRING_DEL(parser, whitespaces); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Scan a plain scalar. | |
+ */ | |
+ | |
+static int | |
+yaml_parser_scan_plain_scalar(yaml_parser_t *parser, yaml_token_t *token) | |
+{ | |
+ yaml_mark_t start_mark; | |
+ yaml_mark_t end_mark; | |
+ yaml_string_t string = NULL_STRING; | |
+ yaml_string_t leading_break = NULL_STRING; | |
+ yaml_string_t trailing_breaks = NULL_STRING; | |
+ yaml_string_t whitespaces = NULL_STRING; | |
+ int leading_blanks = 0; | |
+ int indent = parser->indent+1; | |
+ | |
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; | |
+ if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error; | |
+ | |
+ start_mark = end_mark = parser->mark; | |
+ | |
+ /* Consume the content of the plain scalar. */ | |
+ | |
+ while (1) | |
+ { | |
+ /* Check for a document indicator. */ | |
+ | |
+ if (!CACHE(parser, 4)) goto error; | |
+ | |
+ if (parser->mark.column == 0 && | |
+ ((CHECK_AT(parser->buffer, '-', 0) && | |
+ CHECK_AT(parser->buffer, '-', 1) && | |
+ CHECK_AT(parser->buffer, '-', 2)) || | |
+ (CHECK_AT(parser->buffer, '.', 0) && | |
+ CHECK_AT(parser->buffer, '.', 1) && | |
+ CHECK_AT(parser->buffer, '.', 2))) && | |
+ IS_BLANKZ_AT(parser->buffer, 3)) break; | |
+ | |
+ /* Check for a comment. */ | |
+ | |
+ if (CHECK(parser->buffer, '#')) | |
+ break; | |
+ | |
+ /* Consume non-blank characters. */ | |
+ | |
+ while (!IS_BLANKZ(parser->buffer)) | |
+ { | |
+ /* Check for 'x:x' in the flow context. TODO: Fix the test "spec-08-13". */ | |
+ | |
+ if (parser->flow_level | |
+ && CHECK(parser->buffer, ':') | |
+ && !IS_BLANKZ_AT(parser->buffer, 1)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", | |
+ start_mark, "found unexpected ':'"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Check for indicators that may end a plain scalar. */ | |
+ | |
+ if ((CHECK(parser->buffer, ':') && IS_BLANKZ_AT(parser->buffer, 1)) | |
+ || (parser->flow_level && | |
+ (CHECK(parser->buffer, ',') || CHECK(parser->buffer, ':') | |
+ || CHECK(parser->buffer, '?') || CHECK(parser->buffer, '[') | |
+ || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '{') | |
+ || CHECK(parser->buffer, '}')))) | |
+ break; | |
+ | |
+ /* Check if we need to join whitespaces and breaks. */ | |
+ | |
+ if (leading_blanks || whitespaces.start != whitespaces.pointer) | |
+ { | |
+ if (leading_blanks) | |
+ { | |
+ /* Do we need to fold line breaks? */ | |
+ | |
+ if (leading_break.start[0] == '\n') { | |
+ if (trailing_breaks.start[0] == '\0') { | |
+ if (!STRING_EXTEND(parser, string)) goto error; | |
+ *(string.pointer++) = ' '; | |
+ } | |
+ else { | |
+ if (!JOIN(parser, string, trailing_breaks)) goto error; | |
+ CLEAR(parser, trailing_breaks); | |
+ } | |
+ CLEAR(parser, leading_break); | |
+ } | |
+ else { | |
+ if (!JOIN(parser, string, leading_break)) goto error; | |
+ if (!JOIN(parser, string, trailing_breaks)) goto error; | |
+ CLEAR(parser, leading_break); | |
+ CLEAR(parser, trailing_breaks); | |
+ } | |
+ | |
+ leading_blanks = 0; | |
+ } | |
+ else | |
+ { | |
+ if (!JOIN(parser, string, whitespaces)) goto error; | |
+ CLEAR(parser, whitespaces); | |
+ } | |
+ } | |
+ | |
+ /* Copy the character. */ | |
+ | |
+ if (!READ(parser, string)) goto error; | |
+ | |
+ end_mark = parser->mark; | |
+ | |
+ if (!CACHE(parser, 2)) goto error; | |
+ } | |
+ | |
+ /* Is it the end? */ | |
+ | |
+ if (!(IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer))) | |
+ break; | |
+ | |
+ /* Consume blank characters. */ | |
+ | |
+ if (!CACHE(parser, 1)) goto error; | |
+ | |
+ while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer)) | |
+ { | |
+ if (IS_BLANK(parser->buffer)) | |
+ { | |
+ /* Check for tab character that abuse indentation. */ | |
+ | |
+ if (leading_blanks && (int)parser->mark.column < indent | |
+ && IS_TAB(parser->buffer)) { | |
+ yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", | |
+ start_mark, "found a tab character that violates indentation"); | |
+ goto error; | |
+ } | |
+ | |
+ /* Consume a space or a tab character. */ | |
+ | |
+ if (!leading_blanks) { | |
+ if (!READ(parser, whitespaces)) goto error; | |
+ } | |
+ else { | |
+ SKIP(parser); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ if (!CACHE(parser, 2)) goto error; | |
+ | |
+ /* Check if it is a first line break. */ | |
+ | |
+ if (!leading_blanks) | |
+ { | |
+ CLEAR(parser, whitespaces); | |
+ if (!READ_LINE(parser, leading_break)) goto error; | |
+ leading_blanks = 1; | |
+ } | |
+ else | |
+ { | |
+ if (!READ_LINE(parser, trailing_breaks)) goto error; | |
+ } | |
+ } | |
+ if (!CACHE(parser, 1)) goto error; | |
+ } | |
+ | |
+ /* Check indentation level. */ | |
+ | |
+ if (!parser->flow_level && (int)parser->mark.column < indent) | |
+ break; | |
+ } | |
+ | |
+ /* Create a token. */ | |
+ | |
+ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, | |
+ YAML_PLAIN_SCALAR_STYLE, start_mark, end_mark); | |
+ | |
+ /* Note that we change the 'simple_key_allowed' flag. */ | |
+ | |
+ if (leading_blanks) { | |
+ parser->simple_key_allowed = 1; | |
+ } | |
+ | |
+ STRING_DEL(parser, leading_break); | |
+ STRING_DEL(parser, trailing_breaks); | |
+ STRING_DEL(parser, whitespaces); | |
+ | |
+ return 1; | |
+ | |
+error: | |
+ STRING_DEL(parser, string); | |
+ STRING_DEL(parser, leading_break); | |
+ STRING_DEL(parser, trailing_breaks); | |
+ STRING_DEL(parser, whitespaces); | |
+ | |
+ return 0; | |
+} | |
diff --git a/ext/psych/yaml/writer.c b/ext/psych/yaml/writer.c | |
new file mode 100644 | |
index 0000000..964973e | |
--- /dev/null | |
+++ b/ext/psych/yaml/writer.c | |
@@ -0,0 +1,140 @@ | |
+ | |
+#include "yaml_private.h" | |
+ | |
+/* | |
+ * Declarations. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_set_writer_error(yaml_emitter_t *emitter, const char *problem); | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_flush(yaml_emitter_t *emitter); | |
+ | |
+/* | |
+ * Set the writer error and return 0. | |
+ */ | |
+ | |
+static int | |
+yaml_emitter_set_writer_error(yaml_emitter_t *emitter, const char *problem) | |
+{ | |
+ emitter->error = YAML_WRITER_ERROR; | |
+ emitter->problem = problem; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Flush the output buffer. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_flush(yaml_emitter_t *emitter) | |
+{ | |
+ int low, high; | |
+ | |
+ assert(emitter); /* Non-NULL emitter object is expected. */ | |
+ assert(emitter->write_handler); /* Write handler must be set. */ | |
+ assert(emitter->encoding); /* Output encoding must be set. */ | |
+ | |
+ emitter->buffer.last = emitter->buffer.pointer; | |
+ emitter->buffer.pointer = emitter->buffer.start; | |
+ | |
+ /* Check if the buffer is empty. */ | |
+ | |
+ if (emitter->buffer.start == emitter->buffer.last) { | |
+ return 1; | |
+ } | |
+ | |
+ /* If the output encoding is UTF-8, we don't need to recode the buffer. */ | |
+ | |
+ if (emitter->encoding == YAML_UTF8_ENCODING) | |
+ { | |
+ if (emitter->write_handler(emitter->write_handler_data, | |
+ emitter->buffer.start, | |
+ emitter->buffer.last - emitter->buffer.start)) { | |
+ emitter->buffer.last = emitter->buffer.start; | |
+ emitter->buffer.pointer = emitter->buffer.start; | |
+ return 1; | |
+ } | |
+ else { | |
+ return yaml_emitter_set_writer_error(emitter, "write error"); | |
+ } | |
+ } | |
+ | |
+ /* Recode the buffer into the raw buffer. */ | |
+ | |
+ low = (emitter->encoding == YAML_UTF16LE_ENCODING ? 0 : 1); | |
+ high = (emitter->encoding == YAML_UTF16LE_ENCODING ? 1 : 0); | |
+ | |
+ while (emitter->buffer.pointer != emitter->buffer.last) | |
+ { | |
+ unsigned char octet; | |
+ unsigned int width; | |
+ unsigned int value; | |
+ size_t k; | |
+ | |
+ /* | |
+ * See the "reader.c" code for more details on UTF-8 encoding. Note | |
+ * that we assume that the buffer contains a valid UTF-8 sequence. | |
+ */ | |
+ | |
+ /* Read the next UTF-8 character. */ | |
+ | |
+ octet = emitter->buffer.pointer[0]; | |
+ | |
+ width = (octet & 0x80) == 0x00 ? 1 : | |
+ (octet & 0xE0) == 0xC0 ? 2 : | |
+ (octet & 0xF0) == 0xE0 ? 3 : | |
+ (octet & 0xF8) == 0xF0 ? 4 : 0; | |
+ | |
+ value = (octet & 0x80) == 0x00 ? octet & 0x7F : | |
+ (octet & 0xE0) == 0xC0 ? octet & 0x1F : | |
+ (octet & 0xF0) == 0xE0 ? octet & 0x0F : | |
+ (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; | |
+ | |
+ for (k = 1; k < width; k ++) { | |
+ octet = emitter->buffer.pointer[k]; | |
+ value = (value << 6) + (octet & 0x3F); | |
+ } | |
+ | |
+ emitter->buffer.pointer += width; | |
+ | |
+ /* Write the character. */ | |
+ | |
+ if (value < 0x10000) | |
+ { | |
+ emitter->raw_buffer.last[high] = value >> 8; | |
+ emitter->raw_buffer.last[low] = value & 0xFF; | |
+ | |
+ emitter->raw_buffer.last += 2; | |
+ } | |
+ else | |
+ { | |
+ /* Write the character using a surrogate pair (check "reader.c"). */ | |
+ | |
+ value -= 0x10000; | |
+ emitter->raw_buffer.last[high] = 0xD8 + (value >> 18); | |
+ emitter->raw_buffer.last[low] = (value >> 10) & 0xFF; | |
+ emitter->raw_buffer.last[high+2] = 0xDC + ((value >> 8) & 0xFF); | |
+ emitter->raw_buffer.last[low+2] = value & 0xFF; | |
+ | |
+ emitter->raw_buffer.last += 4; | |
+ } | |
+ } | |
+ | |
+ /* Write the raw buffer. */ | |
+ | |
+ if (emitter->write_handler(emitter->write_handler_data, | |
+ emitter->raw_buffer.start, | |
+ emitter->raw_buffer.last - emitter->raw_buffer.start)) { | |
+ emitter->buffer.last = emitter->buffer.start; | |
+ emitter->buffer.pointer = emitter->buffer.start; | |
+ emitter->raw_buffer.last = emitter->raw_buffer.start; | |
+ emitter->raw_buffer.pointer = emitter->raw_buffer.start; | |
+ return 1; | |
+ } | |
+ else { | |
+ return yaml_emitter_set_writer_error(emitter, "write error"); | |
+ } | |
+} | |
diff --git a/ext/psych/yaml/yaml.h b/ext/psych/yaml/yaml.h | |
new file mode 100644 | |
index 0000000..80639c4 | |
--- /dev/null | |
+++ b/ext/psych/yaml/yaml.h | |
@@ -0,0 +1,1970 @@ | |
+/** | |
+ * @file yaml.h | |
+ * @brief Public interface for libyaml. | |
+ * | |
+ * Include the header file with the code: | |
+ * @code | |
+ * #include <yaml.h> | |
+ * @endcode | |
+ */ | |
+ | |
+#ifndef YAML_H | |
+#define YAML_H | |
+ | |
+#ifdef __cplusplus | |
+extern "C" { | |
+#endif | |
+ | |
+#include <stdlib.h> | |
+#include <stdio.h> | |
+#include <string.h> | |
+ | |
+/** | |
+ * @defgroup export Export Definitions | |
+ * @{ | |
+ */ | |
+ | |
+/** The public API declaration. */ | |
+ | |
+#ifdef _WIN32 | |
+# if defined(YAML_DECLARE_STATIC) | |
+# define YAML_DECLARE(type) type | |
+# elif defined(YAML_DECLARE_EXPORT) | |
+# define YAML_DECLARE(type) __declspec(dllexport) type | |
+# else | |
+# define YAML_DECLARE(type) __declspec(dllimport) type | |
+# endif | |
+#else | |
+# define YAML_DECLARE(type) type | |
+#endif | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup version Version Information | |
+ * @{ | |
+ */ | |
+ | |
+/** | |
+ * Get the library version as a string. | |
+ * | |
+ * @returns The function returns the pointer to a static string of the form | |
+ * @c "X.Y.Z", where @c X is the major version number, @c Y is a minor version | |
+ * number, and @c Z is the patch version number. | |
+ */ | |
+ | |
+YAML_DECLARE(const char *) | |
+yaml_get_version_string(void); | |
+ | |
+/** | |
+ * Get the library version numbers. | |
+ * | |
+ * @param[out] major Major version number. | |
+ * @param[out] minor Minor version number. | |
+ * @param[out] patch Patch version number. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_get_version(int *major, int *minor, int *patch); | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup basic Basic Types | |
+ * @{ | |
+ */ | |
+ | |
+/** The character type (UTF-8 octet). */ | |
+typedef unsigned char yaml_char_t; | |
+ | |
+/** The version directive data. */ | |
+typedef struct yaml_version_directive_s { | |
+ /** The major version number. */ | |
+ int major; | |
+ /** The minor version number. */ | |
+ int minor; | |
+} yaml_version_directive_t; | |
+ | |
+/** The tag directive data. */ | |
+typedef struct yaml_tag_directive_s { | |
+ /** The tag handle. */ | |
+ yaml_char_t *handle; | |
+ /** The tag prefix. */ | |
+ yaml_char_t *prefix; | |
+} yaml_tag_directive_t; | |
+ | |
+/** The stream encoding. */ | |
+typedef enum yaml_encoding_e { | |
+ /** Let the parser choose the encoding. */ | |
+ YAML_ANY_ENCODING, | |
+ /** The default UTF-8 encoding. */ | |
+ YAML_UTF8_ENCODING, | |
+ /** The UTF-16-LE encoding with BOM. */ | |
+ YAML_UTF16LE_ENCODING, | |
+ /** The UTF-16-BE encoding with BOM. */ | |
+ YAML_UTF16BE_ENCODING | |
+} yaml_encoding_t; | |
+ | |
+/** Line break types. */ | |
+ | |
+typedef enum yaml_break_e { | |
+ /** Let the parser choose the break type. */ | |
+ YAML_ANY_BREAK, | |
+ /** Use CR for line breaks (Mac style). */ | |
+ YAML_CR_BREAK, | |
+ /** Use LN for line breaks (Unix style). */ | |
+ YAML_LN_BREAK, | |
+ /** Use CR LN for line breaks (DOS style). */ | |
+ YAML_CRLN_BREAK | |
+} yaml_break_t; | |
+ | |
+/** Many bad things could happen with the parser and emitter. */ | |
+typedef enum yaml_error_type_e { | |
+ /** No error is produced. */ | |
+ YAML_NO_ERROR, | |
+ | |
+ /** Cannot allocate or reallocate a block of memory. */ | |
+ YAML_MEMORY_ERROR, | |
+ | |
+ /** Cannot read or decode the input stream. */ | |
+ YAML_READER_ERROR, | |
+ /** Cannot scan the input stream. */ | |
+ YAML_SCANNER_ERROR, | |
+ /** Cannot parse the input stream. */ | |
+ YAML_PARSER_ERROR, | |
+ /** Cannot compose a YAML document. */ | |
+ YAML_COMPOSER_ERROR, | |
+ | |
+ /** Cannot write to the output stream. */ | |
+ YAML_WRITER_ERROR, | |
+ /** Cannot emit a YAML stream. */ | |
+ YAML_EMITTER_ERROR | |
+} yaml_error_type_t; | |
+ | |
+/** The pointer position. */ | |
+typedef struct yaml_mark_s { | |
+ /** The position index. */ | |
+ size_t index; | |
+ | |
+ /** The position line. */ | |
+ size_t line; | |
+ | |
+ /** The position column. */ | |
+ size_t column; | |
+} yaml_mark_t; | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup styles Node Styles | |
+ * @{ | |
+ */ | |
+ | |
+/** Scalar styles. */ | |
+typedef enum yaml_scalar_style_e { | |
+ /** Let the emitter choose the style. */ | |
+ YAML_ANY_SCALAR_STYLE, | |
+ | |
+ /** The plain scalar style. */ | |
+ YAML_PLAIN_SCALAR_STYLE, | |
+ | |
+ /** The single-quoted scalar style. */ | |
+ YAML_SINGLE_QUOTED_SCALAR_STYLE, | |
+ /** The double-quoted scalar style. */ | |
+ YAML_DOUBLE_QUOTED_SCALAR_STYLE, | |
+ | |
+ /** The literal scalar style. */ | |
+ YAML_LITERAL_SCALAR_STYLE, | |
+ /** The folded scalar style. */ | |
+ YAML_FOLDED_SCALAR_STYLE | |
+} yaml_scalar_style_t; | |
+ | |
+/** Sequence styles. */ | |
+typedef enum yaml_sequence_style_e { | |
+ /** Let the emitter choose the style. */ | |
+ YAML_ANY_SEQUENCE_STYLE, | |
+ | |
+ /** The block sequence style. */ | |
+ YAML_BLOCK_SEQUENCE_STYLE, | |
+ /** The flow sequence style. */ | |
+ YAML_FLOW_SEQUENCE_STYLE | |
+} yaml_sequence_style_t; | |
+ | |
+/** Mapping styles. */ | |
+typedef enum yaml_mapping_style_e { | |
+ /** Let the emitter choose the style. */ | |
+ YAML_ANY_MAPPING_STYLE, | |
+ | |
+ /** The block mapping style. */ | |
+ YAML_BLOCK_MAPPING_STYLE, | |
+ /** The flow mapping style. */ | |
+ YAML_FLOW_MAPPING_STYLE | |
+/* YAML_FLOW_SET_MAPPING_STYLE */ | |
+} yaml_mapping_style_t; | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup tokens Tokens | |
+ * @{ | |
+ */ | |
+ | |
+/** Token types. */ | |
+typedef enum yaml_token_type_e { | |
+ /** An empty token. */ | |
+ YAML_NO_TOKEN, | |
+ | |
+ /** A STREAM-START token. */ | |
+ YAML_STREAM_START_TOKEN, | |
+ /** A STREAM-END token. */ | |
+ YAML_STREAM_END_TOKEN, | |
+ | |
+ /** A VERSION-DIRECTIVE token. */ | |
+ YAML_VERSION_DIRECTIVE_TOKEN, | |
+ /** A TAG-DIRECTIVE token. */ | |
+ YAML_TAG_DIRECTIVE_TOKEN, | |
+ /** A DOCUMENT-START token. */ | |
+ YAML_DOCUMENT_START_TOKEN, | |
+ /** A DOCUMENT-END token. */ | |
+ YAML_DOCUMENT_END_TOKEN, | |
+ | |
+ /** A BLOCK-SEQUENCE-START token. */ | |
+ YAML_BLOCK_SEQUENCE_START_TOKEN, | |
+ /** A BLOCK-SEQUENCE-END token. */ | |
+ YAML_BLOCK_MAPPING_START_TOKEN, | |
+ /** A BLOCK-END token. */ | |
+ YAML_BLOCK_END_TOKEN, | |
+ | |
+ /** A FLOW-SEQUENCE-START token. */ | |
+ YAML_FLOW_SEQUENCE_START_TOKEN, | |
+ /** A FLOW-SEQUENCE-END token. */ | |
+ YAML_FLOW_SEQUENCE_END_TOKEN, | |
+ /** A FLOW-MAPPING-START token. */ | |
+ YAML_FLOW_MAPPING_START_TOKEN, | |
+ /** A FLOW-MAPPING-END token. */ | |
+ YAML_FLOW_MAPPING_END_TOKEN, | |
+ | |
+ /** A BLOCK-ENTRY token. */ | |
+ YAML_BLOCK_ENTRY_TOKEN, | |
+ /** A FLOW-ENTRY token. */ | |
+ YAML_FLOW_ENTRY_TOKEN, | |
+ /** A KEY token. */ | |
+ YAML_KEY_TOKEN, | |
+ /** A VALUE token. */ | |
+ YAML_VALUE_TOKEN, | |
+ | |
+ /** An ALIAS token. */ | |
+ YAML_ALIAS_TOKEN, | |
+ /** An ANCHOR token. */ | |
+ YAML_ANCHOR_TOKEN, | |
+ /** A TAG token. */ | |
+ YAML_TAG_TOKEN, | |
+ /** A SCALAR token. */ | |
+ YAML_SCALAR_TOKEN | |
+} yaml_token_type_t; | |
+ | |
+/** The token structure. */ | |
+typedef struct yaml_token_s { | |
+ | |
+ /** The token type. */ | |
+ yaml_token_type_t type; | |
+ | |
+ /** The token data. */ | |
+ union { | |
+ | |
+ /** The stream start (for @c YAML_STREAM_START_TOKEN). */ | |
+ struct { | |
+ /** The stream encoding. */ | |
+ yaml_encoding_t encoding; | |
+ } stream_start; | |
+ | |
+ /** The alias (for @c YAML_ALIAS_TOKEN). */ | |
+ struct { | |
+ /** The alias value. */ | |
+ yaml_char_t *value; | |
+ } alias; | |
+ | |
+ /** The anchor (for @c YAML_ANCHOR_TOKEN). */ | |
+ struct { | |
+ /** The anchor value. */ | |
+ yaml_char_t *value; | |
+ } anchor; | |
+ | |
+ /** The tag (for @c YAML_TAG_TOKEN). */ | |
+ struct { | |
+ /** The tag handle. */ | |
+ yaml_char_t *handle; | |
+ /** The tag suffix. */ | |
+ yaml_char_t *suffix; | |
+ } tag; | |
+ | |
+ /** The scalar value (for @c YAML_SCALAR_TOKEN). */ | |
+ struct { | |
+ /** The scalar value. */ | |
+ yaml_char_t *value; | |
+ /** The length of the scalar value. */ | |
+ size_t length; | |
+ /** The scalar style. */ | |
+ yaml_scalar_style_t style; | |
+ } scalar; | |
+ | |
+ /** The version directive (for @c YAML_VERSION_DIRECTIVE_TOKEN). */ | |
+ struct { | |
+ /** The major version number. */ | |
+ int major; | |
+ /** The minor version number. */ | |
+ int minor; | |
+ } version_directive; | |
+ | |
+ /** The tag directive (for @c YAML_TAG_DIRECTIVE_TOKEN). */ | |
+ struct { | |
+ /** The tag handle. */ | |
+ yaml_char_t *handle; | |
+ /** The tag prefix. */ | |
+ yaml_char_t *prefix; | |
+ } tag_directive; | |
+ | |
+ } data; | |
+ | |
+ /** The beginning of the token. */ | |
+ yaml_mark_t start_mark; | |
+ /** The end of the token. */ | |
+ yaml_mark_t end_mark; | |
+ | |
+} yaml_token_t; | |
+ | |
+/** | |
+ * Free any memory allocated for a token object. | |
+ * | |
+ * @param[in,out] token A token object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_token_delete(yaml_token_t *token); | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup events Events | |
+ * @{ | |
+ */ | |
+ | |
+/** Event types. */ | |
+typedef enum yaml_event_type_e { | |
+ /** An empty event. */ | |
+ YAML_NO_EVENT, | |
+ | |
+ /** A STREAM-START event. */ | |
+ YAML_STREAM_START_EVENT, | |
+ /** A STREAM-END event. */ | |
+ YAML_STREAM_END_EVENT, | |
+ | |
+ /** A DOCUMENT-START event. */ | |
+ YAML_DOCUMENT_START_EVENT, | |
+ /** A DOCUMENT-END event. */ | |
+ YAML_DOCUMENT_END_EVENT, | |
+ | |
+ /** An ALIAS event. */ | |
+ YAML_ALIAS_EVENT, | |
+ /** A SCALAR event. */ | |
+ YAML_SCALAR_EVENT, | |
+ | |
+ /** A SEQUENCE-START event. */ | |
+ YAML_SEQUENCE_START_EVENT, | |
+ /** A SEQUENCE-END event. */ | |
+ YAML_SEQUENCE_END_EVENT, | |
+ | |
+ /** A MAPPING-START event. */ | |
+ YAML_MAPPING_START_EVENT, | |
+ /** A MAPPING-END event. */ | |
+ YAML_MAPPING_END_EVENT | |
+} yaml_event_type_t; | |
+ | |
+/** The event structure. */ | |
+typedef struct yaml_event_s { | |
+ | |
+ /** The event type. */ | |
+ yaml_event_type_t type; | |
+ | |
+ /** The event data. */ | |
+ union { | |
+ | |
+ /** The stream parameters (for @c YAML_STREAM_START_EVENT). */ | |
+ struct { | |
+ /** The document encoding. */ | |
+ yaml_encoding_t encoding; | |
+ } stream_start; | |
+ | |
+ /** The document parameters (for @c YAML_DOCUMENT_START_EVENT). */ | |
+ struct { | |
+ /** The version directive. */ | |
+ yaml_version_directive_t *version_directive; | |
+ | |
+ /** The list of tag directives. */ | |
+ struct { | |
+ /** The beginning of the tag directives list. */ | |
+ yaml_tag_directive_t *start; | |
+ /** The end of the tag directives list. */ | |
+ yaml_tag_directive_t *end; | |
+ } tag_directives; | |
+ | |
+ /** Is the document indicator implicit? */ | |
+ int implicit; | |
+ } document_start; | |
+ | |
+ /** The document end parameters (for @c YAML_DOCUMENT_END_EVENT). */ | |
+ struct { | |
+ /** Is the document end indicator implicit? */ | |
+ int implicit; | |
+ } document_end; | |
+ | |
+ /** The alias parameters (for @c YAML_ALIAS_EVENT). */ | |
+ struct { | |
+ /** The anchor. */ | |
+ yaml_char_t *anchor; | |
+ } alias; | |
+ | |
+ /** The scalar parameters (for @c YAML_SCALAR_EVENT). */ | |
+ struct { | |
+ /** The anchor. */ | |
+ yaml_char_t *anchor; | |
+ /** The tag. */ | |
+ yaml_char_t *tag; | |
+ /** The scalar value. */ | |
+ yaml_char_t *value; | |
+ /** The length of the scalar value. */ | |
+ size_t length; | |
+ /** Is the tag optional for the plain style? */ | |
+ int plain_implicit; | |
+ /** Is the tag optional for any non-plain style? */ | |
+ int quoted_implicit; | |
+ /** The scalar style. */ | |
+ yaml_scalar_style_t style; | |
+ } scalar; | |
+ | |
+ /** The sequence parameters (for @c YAML_SEQUENCE_START_EVENT). */ | |
+ struct { | |
+ /** The anchor. */ | |
+ yaml_char_t *anchor; | |
+ /** The tag. */ | |
+ yaml_char_t *tag; | |
+ /** Is the tag optional? */ | |
+ int implicit; | |
+ /** The sequence style. */ | |
+ yaml_sequence_style_t style; | |
+ } sequence_start; | |
+ | |
+ /** The mapping parameters (for @c YAML_MAPPING_START_EVENT). */ | |
+ struct { | |
+ /** The anchor. */ | |
+ yaml_char_t *anchor; | |
+ /** The tag. */ | |
+ yaml_char_t *tag; | |
+ /** Is the tag optional? */ | |
+ int implicit; | |
+ /** The mapping style. */ | |
+ yaml_mapping_style_t style; | |
+ } mapping_start; | |
+ | |
+ } data; | |
+ | |
+ /** The beginning of the event. */ | |
+ yaml_mark_t start_mark; | |
+ /** The end of the event. */ | |
+ yaml_mark_t end_mark; | |
+ | |
+} yaml_event_t; | |
+ | |
+/** | |
+ * Create the STREAM-START event. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * @param[in] encoding The stream encoding. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_stream_start_event_initialize(yaml_event_t *event, | |
+ yaml_encoding_t encoding); | |
+ | |
+/** | |
+ * Create the STREAM-END event. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_stream_end_event_initialize(yaml_event_t *event); | |
+ | |
+/** | |
+ * Create the DOCUMENT-START event. | |
+ * | |
+ * The @a implicit argument is considered as a stylistic parameter and may be | |
+ * ignored by the emitter. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * @param[in] version_directive The %YAML directive value or | |
+ * @c NULL. | |
+ * @param[in] tag_directives_start The beginning of the %TAG | |
+ * directives list. | |
+ * @param[in] tag_directives_end The end of the %TAG directives | |
+ * list. | |
+ * @param[in] implicit If the document start indicator is | |
+ * implicit. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_start_event_initialize(yaml_event_t *event, | |
+ yaml_version_directive_t *version_directive, | |
+ yaml_tag_directive_t *tag_directives_start, | |
+ yaml_tag_directive_t *tag_directives_end, | |
+ int implicit); | |
+ | |
+/** | |
+ * Create the DOCUMENT-END event. | |
+ * | |
+ * The @a implicit argument is considered as a stylistic parameter and may be | |
+ * ignored by the emitter. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * @param[in] implicit If the document end indicator is implicit. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_end_event_initialize(yaml_event_t *event, int implicit); | |
+ | |
+/** | |
+ * Create an ALIAS event. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * @param[in] anchor The anchor value. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_alias_event_initialize(yaml_event_t *event, yaml_char_t *anchor); | |
+ | |
+/** | |
+ * Create a SCALAR event. | |
+ * | |
+ * The @a style argument may be ignored by the emitter. | |
+ * | |
+ * Either the @a tag attribute or one of the @a plain_implicit and | |
+ * @a quoted_implicit flags must be set. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * @param[in] anchor The scalar anchor or @c NULL. | |
+ * @param[in] tag The scalar tag or @c NULL. | |
+ * @param[in] value The scalar value. | |
+ * @param[in] length The length of the scalar value. | |
+ * @param[in] plain_implicit If the tag may be omitted for the plain | |
+ * style. | |
+ * @param[in] quoted_implicit If the tag may be omitted for any | |
+ * non-plain style. | |
+ * @param[in] style The scalar style. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_scalar_event_initialize(yaml_event_t *event, | |
+ yaml_char_t *anchor, yaml_char_t *tag, | |
+ yaml_char_t *value, int length, | |
+ int plain_implicit, int quoted_implicit, | |
+ yaml_scalar_style_t style); | |
+ | |
+/** | |
+ * Create a SEQUENCE-START event. | |
+ * | |
+ * The @a style argument may be ignored by the emitter. | |
+ * | |
+ * Either the @a tag attribute or the @a implicit flag must be set. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * @param[in] anchor The sequence anchor or @c NULL. | |
+ * @param[in] tag The sequence tag or @c NULL. | |
+ * @param[in] implicit If the tag may be omitted. | |
+ * @param[in] style The sequence style. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_sequence_start_event_initialize(yaml_event_t *event, | |
+ yaml_char_t *anchor, yaml_char_t *tag, int implicit, | |
+ yaml_sequence_style_t style); | |
+ | |
+/** | |
+ * Create a SEQUENCE-END event. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_sequence_end_event_initialize(yaml_event_t *event); | |
+ | |
+/** | |
+ * Create a MAPPING-START event. | |
+ * | |
+ * The @a style argument may be ignored by the emitter. | |
+ * | |
+ * Either the @a tag attribute or the @a implicit flag must be set. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * @param[in] anchor The mapping anchor or @c NULL. | |
+ * @param[in] tag The mapping tag or @c NULL. | |
+ * @param[in] implicit If the tag may be omitted. | |
+ * @param[in] style The mapping style. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_mapping_start_event_initialize(yaml_event_t *event, | |
+ yaml_char_t *anchor, yaml_char_t *tag, int implicit, | |
+ yaml_mapping_style_t style); | |
+ | |
+/** | |
+ * Create a MAPPING-END event. | |
+ * | |
+ * @param[out] event An empty event object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_mapping_end_event_initialize(yaml_event_t *event); | |
+ | |
+/** | |
+ * Free any memory allocated for an event object. | |
+ * | |
+ * @param[in,out] event An event object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_event_delete(yaml_event_t *event); | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup nodes Nodes | |
+ * @{ | |
+ */ | |
+ | |
+/** The tag @c !!null with the only possible value: @c null. */ | |
+#define YAML_NULL_TAG "tag:yaml.org,2002:null" | |
+/** The tag @c !!bool with the values: @c true and @c falce. */ | |
+#define YAML_BOOL_TAG "tag:yaml.org,2002:bool" | |
+/** The tag @c !!str for string values. */ | |
+#define YAML_STR_TAG "tag:yaml.org,2002:str" | |
+/** The tag @c !!int for integer values. */ | |
+#define YAML_INT_TAG "tag:yaml.org,2002:int" | |
+/** The tag @c !!float for float values. */ | |
+#define YAML_FLOAT_TAG "tag:yaml.org,2002:float" | |
+/** The tag @c !!timestamp for date and time values. */ | |
+#define YAML_TIMESTAMP_TAG "tag:yaml.org,2002:timestamp" | |
+ | |
+/** The tag @c !!seq is used to denote sequences. */ | |
+#define YAML_SEQ_TAG "tag:yaml.org,2002:seq" | |
+/** The tag @c !!map is used to denote mapping. */ | |
+#define YAML_MAP_TAG "tag:yaml.org,2002:map" | |
+ | |
+/** The default scalar tag is @c !!str. */ | |
+#define YAML_DEFAULT_SCALAR_TAG YAML_STR_TAG | |
+/** The default sequence tag is @c !!seq. */ | |
+#define YAML_DEFAULT_SEQUENCE_TAG YAML_SEQ_TAG | |
+/** The default mapping tag is @c !!map. */ | |
+#define YAML_DEFAULT_MAPPING_TAG YAML_MAP_TAG | |
+ | |
+/** Node types. */ | |
+typedef enum yaml_node_type_e { | |
+ /** An empty node. */ | |
+ YAML_NO_NODE, | |
+ | |
+ /** A scalar node. */ | |
+ YAML_SCALAR_NODE, | |
+ /** A sequence node. */ | |
+ YAML_SEQUENCE_NODE, | |
+ /** A mapping node. */ | |
+ YAML_MAPPING_NODE | |
+} yaml_node_type_t; | |
+ | |
+/** The forward definition of a document node structure. */ | |
+typedef struct yaml_node_s yaml_node_t; | |
+ | |
+/** An element of a sequence node. */ | |
+typedef int yaml_node_item_t; | |
+ | |
+/** An element of a mapping node. */ | |
+typedef struct yaml_node_pair_s { | |
+ /** The key of the element. */ | |
+ int key; | |
+ /** The value of the element. */ | |
+ int value; | |
+} yaml_node_pair_t; | |
+ | |
+/** The node structure. */ | |
+struct yaml_node_s { | |
+ | |
+ /** The node type. */ | |
+ yaml_node_type_t type; | |
+ | |
+ /** The node tag. */ | |
+ yaml_char_t *tag; | |
+ | |
+ /** The node data. */ | |
+ union { | |
+ | |
+ /** The scalar parameters (for @c YAML_SCALAR_NODE). */ | |
+ struct { | |
+ /** The scalar value. */ | |
+ yaml_char_t *value; | |
+ /** The length of the scalar value. */ | |
+ size_t length; | |
+ /** The scalar style. */ | |
+ yaml_scalar_style_t style; | |
+ } scalar; | |
+ | |
+ /** The sequence parameters (for @c YAML_SEQUENCE_NODE). */ | |
+ struct { | |
+ /** The stack of sequence items. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ yaml_node_item_t *start; | |
+ /** The end of the stack. */ | |
+ yaml_node_item_t *end; | |
+ /** The top of the stack. */ | |
+ yaml_node_item_t *top; | |
+ } items; | |
+ /** The sequence style. */ | |
+ yaml_sequence_style_t style; | |
+ } sequence; | |
+ | |
+ /** The mapping parameters (for @c YAML_MAPPING_NODE). */ | |
+ struct { | |
+ /** The stack of mapping pairs (key, value). */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ yaml_node_pair_t *start; | |
+ /** The end of the stack. */ | |
+ yaml_node_pair_t *end; | |
+ /** The top of the stack. */ | |
+ yaml_node_pair_t *top; | |
+ } pairs; | |
+ /** The mapping style. */ | |
+ yaml_mapping_style_t style; | |
+ } mapping; | |
+ | |
+ } data; | |
+ | |
+ /** The beginning of the node. */ | |
+ yaml_mark_t start_mark; | |
+ /** The end of the node. */ | |
+ yaml_mark_t end_mark; | |
+ | |
+}; | |
+ | |
+/** The document structure. */ | |
+typedef struct yaml_document_s { | |
+ | |
+ /** The document nodes. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ yaml_node_t *start; | |
+ /** The end of the stack. */ | |
+ yaml_node_t *end; | |
+ /** The top of the stack. */ | |
+ yaml_node_t *top; | |
+ } nodes; | |
+ | |
+ /** The version directive. */ | |
+ yaml_version_directive_t *version_directive; | |
+ | |
+ /** The list of tag directives. */ | |
+ struct { | |
+ /** The beginning of the tag directives list. */ | |
+ yaml_tag_directive_t *start; | |
+ /** The end of the tag directives list. */ | |
+ yaml_tag_directive_t *end; | |
+ } tag_directives; | |
+ | |
+ /** Is the document start indicator implicit? */ | |
+ int start_implicit; | |
+ /** Is the document end indicator implicit? */ | |
+ int end_implicit; | |
+ | |
+ /** The beginning of the document. */ | |
+ yaml_mark_t start_mark; | |
+ /** The end of the document. */ | |
+ yaml_mark_t end_mark; | |
+ | |
+} yaml_document_t; | |
+ | |
+/** | |
+ * Create a YAML document. | |
+ * | |
+ * @param[out] document An empty document object. | |
+ * @param[in] version_directive The %YAML directive value or | |
+ * @c NULL. | |
+ * @param[in] tag_directives_start The beginning of the %TAG | |
+ * directives list. | |
+ * @param[in] tag_directives_end The end of the %TAG directives | |
+ * list. | |
+ * @param[in] start_implicit If the document start indicator is | |
+ * implicit. | |
+ * @param[in] end_implicit If the document end indicator is | |
+ * implicit. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_initialize(yaml_document_t *document, | |
+ yaml_version_directive_t *version_directive, | |
+ yaml_tag_directive_t *tag_directives_start, | |
+ yaml_tag_directive_t *tag_directives_end, | |
+ int start_implicit, int end_implicit); | |
+ | |
+/** | |
+ * Delete a YAML document and all its nodes. | |
+ * | |
+ * @param[in,out] document A document object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_document_delete(yaml_document_t *document); | |
+ | |
+/** | |
+ * Get a node of a YAML document. | |
+ * | |
+ * The pointer returned by this function is valid until any of the functions | |
+ * modifying the documents are called. | |
+ * | |
+ * @param[in] document A document object. | |
+ * @param[in] index The node id. | |
+ * | |
+ * @returns the node objct or @c NULL if @c node_id is out of range. | |
+ */ | |
+ | |
+YAML_DECLARE(yaml_node_t *) | |
+yaml_document_get_node(yaml_document_t *document, int index); | |
+ | |
+/** | |
+ * Get the root of a YAML document node. | |
+ * | |
+ * The root object is the first object added to the document. | |
+ * | |
+ * The pointer returned by this function is valid until any of the functions | |
+ * modifying the documents are called. | |
+ * | |
+ * An empty document produced by the parser signifies the end of a YAML | |
+ * stream. | |
+ * | |
+ * @param[in] document A document object. | |
+ * | |
+ * @returns the node object or @c NULL if the document is empty. | |
+ */ | |
+ | |
+YAML_DECLARE(yaml_node_t *) | |
+yaml_document_get_root_node(yaml_document_t *document); | |
+ | |
+/** | |
+ * Create a SCALAR node and attach it to the document. | |
+ * | |
+ * The @a style argument may be ignored by the emitter. | |
+ * | |
+ * @param[in,out] document A document object. | |
+ * @param[in] tag The scalar tag. | |
+ * @param[in] value The scalar value. | |
+ * @param[in] length The length of the scalar value. | |
+ * @param[in] style The scalar style. | |
+ * | |
+ * @returns the node id or @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_add_scalar(yaml_document_t *document, | |
+ yaml_char_t *tag, yaml_char_t *value, int length, | |
+ yaml_scalar_style_t style); | |
+ | |
+/** | |
+ * Create a SEQUENCE node and attach it to the document. | |
+ * | |
+ * The @a style argument may be ignored by the emitter. | |
+ * | |
+ * @param[in,out] document A document object. | |
+ * @param[in] tag The sequence tag. | |
+ * @param[in] style The sequence style. | |
+ * | |
+ * @returns the node id or @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_add_sequence(yaml_document_t *document, | |
+ yaml_char_t *tag, yaml_sequence_style_t style); | |
+ | |
+/** | |
+ * Create a MAPPING node and attach it to the document. | |
+ * | |
+ * The @a style argument may be ignored by the emitter. | |
+ * | |
+ * @param[in,out] document A document object. | |
+ * @param[in] tag The sequence tag. | |
+ * @param[in] style The sequence style. | |
+ * | |
+ * @returns the node id or @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_add_mapping(yaml_document_t *document, | |
+ yaml_char_t *tag, yaml_mapping_style_t style); | |
+ | |
+/** | |
+ * Add an item to a SEQUENCE node. | |
+ * | |
+ * @param[in,out] document A document object. | |
+ * @param[in] sequence The sequence node id. | |
+ * @param[in] item The item node id. | |
+* | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_append_sequence_item(yaml_document_t *document, | |
+ int sequence, int item); | |
+ | |
+/** | |
+ * Add a pair of a key and a value to a MAPPING node. | |
+ * | |
+ * @param[in,out] document A document object. | |
+ * @param[in] mapping The mapping node id. | |
+ * @param[in] key The key node id. | |
+ * @param[in] value The value node id. | |
+* | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_document_append_mapping_pair(yaml_document_t *document, | |
+ int mapping, int key, int value); | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup parser Parser Definitions | |
+ * @{ | |
+ */ | |
+ | |
+/** | |
+ * The prototype of a read handler. | |
+ * | |
+ * The read handler is called when the parser needs to read more bytes from the | |
+ * source. The handler should write not more than @a size bytes to the @a | |
+ * buffer. The number of written bytes should be set to the @a length variable. | |
+ * | |
+ * @param[in,out] data A pointer to an application data specified by | |
+ * yaml_parser_set_input(). | |
+ * @param[out] buffer The buffer to write the data from the source. | |
+ * @param[in] size The size of the buffer. | |
+ * @param[out] size_read The actual number of bytes read from the source. | |
+ * | |
+ * @returns On success, the handler should return @c 1. If the handler failed, | |
+ * the returned value should be @c 0. On EOF, the handler should set the | |
+ * @a size_read to @c 0 and return @c 1. | |
+ */ | |
+ | |
+typedef int yaml_read_handler_t(void *data, unsigned char *buffer, size_t size, | |
+ size_t *size_read); | |
+ | |
+/** | |
+ * This structure holds information about a potential simple key. | |
+ */ | |
+ | |
+typedef struct yaml_simple_key_s { | |
+ /** Is a simple key possible? */ | |
+ int possible; | |
+ | |
+ /** Is a simple key required? */ | |
+ int required; | |
+ | |
+ /** The number of the token. */ | |
+ size_t token_number; | |
+ | |
+ /** The position mark. */ | |
+ yaml_mark_t mark; | |
+} yaml_simple_key_t; | |
+ | |
+/** | |
+ * The states of the parser. | |
+ */ | |
+typedef enum yaml_parser_state_e { | |
+ /** Expect STREAM-START. */ | |
+ YAML_PARSE_STREAM_START_STATE, | |
+ /** Expect the beginning of an implicit document. */ | |
+ YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE, | |
+ /** Expect DOCUMENT-START. */ | |
+ YAML_PARSE_DOCUMENT_START_STATE, | |
+ /** Expect the content of a document. */ | |
+ YAML_PARSE_DOCUMENT_CONTENT_STATE, | |
+ /** Expect DOCUMENT-END. */ | |
+ YAML_PARSE_DOCUMENT_END_STATE, | |
+ /** Expect a block node. */ | |
+ YAML_PARSE_BLOCK_NODE_STATE, | |
+ /** Expect a block node or indentless sequence. */ | |
+ YAML_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE, | |
+ /** Expect a flow node. */ | |
+ YAML_PARSE_FLOW_NODE_STATE, | |
+ /** Expect the first entry of a block sequence. */ | |
+ YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE, | |
+ /** Expect an entry of a block sequence. */ | |
+ YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE, | |
+ /** Expect an entry of an indentless sequence. */ | |
+ YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE, | |
+ /** Expect the first key of a block mapping. */ | |
+ YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE, | |
+ /** Expect a block mapping key. */ | |
+ YAML_PARSE_BLOCK_MAPPING_KEY_STATE, | |
+ /** Expect a block mapping value. */ | |
+ YAML_PARSE_BLOCK_MAPPING_VALUE_STATE, | |
+ /** Expect the first entry of a flow sequence. */ | |
+ YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE, | |
+ /** Expect an entry of a flow sequence. */ | |
+ YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE, | |
+ /** Expect a key of an ordered mapping. */ | |
+ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE, | |
+ /** Expect a value of an ordered mapping. */ | |
+ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE, | |
+ /** Expect the and of an ordered mapping entry. */ | |
+ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE, | |
+ /** Expect the first key of a flow mapping. */ | |
+ YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE, | |
+ /** Expect a key of a flow mapping. */ | |
+ YAML_PARSE_FLOW_MAPPING_KEY_STATE, | |
+ /** Expect a value of a flow mapping. */ | |
+ YAML_PARSE_FLOW_MAPPING_VALUE_STATE, | |
+ /** Expect an empty value of a flow mapping. */ | |
+ YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE, | |
+ /** Expect nothing. */ | |
+ YAML_PARSE_END_STATE | |
+} yaml_parser_state_t; | |
+ | |
+/** | |
+ * This structure holds aliases data. | |
+ */ | |
+ | |
+typedef struct yaml_alias_data_s { | |
+ /** The anchor. */ | |
+ yaml_char_t *anchor; | |
+ /** The node id. */ | |
+ int index; | |
+ /** The anchor mark. */ | |
+ yaml_mark_t mark; | |
+} yaml_alias_data_t; | |
+ | |
+/** | |
+ * The parser structure. | |
+ * | |
+ * All members are internal. Manage the structure using the @c yaml_parser_ | |
+ * family of functions. | |
+ */ | |
+ | |
+typedef struct yaml_parser_s { | |
+ | |
+ /** | |
+ * @name Error handling | |
+ * @{ | |
+ */ | |
+ | |
+ /** Error type. */ | |
+ yaml_error_type_t error; | |
+ /** Error description. */ | |
+ const char *problem; | |
+ /** The byte about which the problem occured. */ | |
+ size_t problem_offset; | |
+ /** The problematic value (@c -1 is none). */ | |
+ int problem_value; | |
+ /** The problem position. */ | |
+ yaml_mark_t problem_mark; | |
+ /** The error context. */ | |
+ const char *context; | |
+ /** The context position. */ | |
+ yaml_mark_t context_mark; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+ /** | |
+ * @name Reader stuff | |
+ * @{ | |
+ */ | |
+ | |
+ /** Read handler. */ | |
+ yaml_read_handler_t *read_handler; | |
+ | |
+ /** A pointer for passing to the read handler. */ | |
+ void *read_handler_data; | |
+ | |
+ /** Standard (string or file) input data. */ | |
+ union { | |
+ /** String input data. */ | |
+ struct { | |
+ /** The string start pointer. */ | |
+ const unsigned char *start; | |
+ /** The string end pointer. */ | |
+ const unsigned char *end; | |
+ /** The string current position. */ | |
+ const unsigned char *current; | |
+ } string; | |
+ | |
+ /** File input data. */ | |
+ FILE *file; | |
+ } input; | |
+ | |
+ /** EOF flag */ | |
+ int eof; | |
+ | |
+ /** The working buffer. */ | |
+ struct { | |
+ /** The beginning of the buffer. */ | |
+ yaml_char_t *start; | |
+ /** The end of the buffer. */ | |
+ yaml_char_t *end; | |
+ /** The current position of the buffer. */ | |
+ yaml_char_t *pointer; | |
+ /** The last filled position of the buffer. */ | |
+ yaml_char_t *last; | |
+ } buffer; | |
+ | |
+ /* The number of unread characters in the buffer. */ | |
+ size_t unread; | |
+ | |
+ /** The raw buffer. */ | |
+ struct { | |
+ /** The beginning of the buffer. */ | |
+ unsigned char *start; | |
+ /** The end of the buffer. */ | |
+ unsigned char *end; | |
+ /** The current position of the buffer. */ | |
+ unsigned char *pointer; | |
+ /** The last filled position of the buffer. */ | |
+ unsigned char *last; | |
+ } raw_buffer; | |
+ | |
+ /** The input encoding. */ | |
+ yaml_encoding_t encoding; | |
+ | |
+ /** The offset of the current position (in bytes). */ | |
+ size_t offset; | |
+ | |
+ /** The mark of the current position. */ | |
+ yaml_mark_t mark; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+ /** | |
+ * @name Scanner stuff | |
+ * @{ | |
+ */ | |
+ | |
+ /** Have we started to scan the input stream? */ | |
+ int stream_start_produced; | |
+ | |
+ /** Have we reached the end of the input stream? */ | |
+ int stream_end_produced; | |
+ | |
+ /** The number of unclosed '[' and '{' indicators. */ | |
+ int flow_level; | |
+ | |
+ /** The tokens queue. */ | |
+ struct { | |
+ /** The beginning of the tokens queue. */ | |
+ yaml_token_t *start; | |
+ /** The end of the tokens queue. */ | |
+ yaml_token_t *end; | |
+ /** The head of the tokens queue. */ | |
+ yaml_token_t *head; | |
+ /** The tail of the tokens queue. */ | |
+ yaml_token_t *tail; | |
+ } tokens; | |
+ | |
+ /** The number of tokens fetched from the queue. */ | |
+ size_t tokens_parsed; | |
+ | |
+ /* Does the tokens queue contain a token ready for dequeueing. */ | |
+ int token_available; | |
+ | |
+ /** The indentation levels stack. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ int *start; | |
+ /** The end of the stack. */ | |
+ int *end; | |
+ /** The top of the stack. */ | |
+ int *top; | |
+ } indents; | |
+ | |
+ /** The current indentation level. */ | |
+ int indent; | |
+ | |
+ /** May a simple key occur at the current position? */ | |
+ int simple_key_allowed; | |
+ | |
+ /** The stack of simple keys. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ yaml_simple_key_t *start; | |
+ /** The end of the stack. */ | |
+ yaml_simple_key_t *end; | |
+ /** The top of the stack. */ | |
+ yaml_simple_key_t *top; | |
+ } simple_keys; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+ /** | |
+ * @name Parser stuff | |
+ * @{ | |
+ */ | |
+ | |
+ /** The parser states stack. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ yaml_parser_state_t *start; | |
+ /** The end of the stack. */ | |
+ yaml_parser_state_t *end; | |
+ /** The top of the stack. */ | |
+ yaml_parser_state_t *top; | |
+ } states; | |
+ | |
+ /** The current parser state. */ | |
+ yaml_parser_state_t state; | |
+ | |
+ /** The stack of marks. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ yaml_mark_t *start; | |
+ /** The end of the stack. */ | |
+ yaml_mark_t *end; | |
+ /** The top of the stack. */ | |
+ yaml_mark_t *top; | |
+ } marks; | |
+ | |
+ /** The list of TAG directives. */ | |
+ struct { | |
+ /** The beginning of the list. */ | |
+ yaml_tag_directive_t *start; | |
+ /** The end of the list. */ | |
+ yaml_tag_directive_t *end; | |
+ /** The top of the list. */ | |
+ yaml_tag_directive_t *top; | |
+ } tag_directives; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+ /** | |
+ * @name Dumper stuff | |
+ * @{ | |
+ */ | |
+ | |
+ /** The alias data. */ | |
+ struct { | |
+ /** The beginning of the list. */ | |
+ yaml_alias_data_t *start; | |
+ /** The end of the list. */ | |
+ yaml_alias_data_t *end; | |
+ /** The top of the list. */ | |
+ yaml_alias_data_t *top; | |
+ } aliases; | |
+ | |
+ /** The currently parsed document. */ | |
+ yaml_document_t *document; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+} yaml_parser_t; | |
+ | |
+/** | |
+ * Initialize a parser. | |
+ * | |
+ * This function creates a new parser object. An application is responsible | |
+ * for destroying the object using the yaml_parser_delete() function. | |
+ * | |
+ * @param[out] parser An empty parser object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_initialize(yaml_parser_t *parser); | |
+ | |
+/** | |
+ * Destroy a parser. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_delete(yaml_parser_t *parser); | |
+ | |
+/** | |
+ * Set a string input. | |
+ * | |
+ * Note that the @a input pointer must be valid while the @a parser object | |
+ * exists. The application is responsible for destroing @a input after | |
+ * destroying the @a parser. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ * @param[in] input A source data. | |
+ * @param[in] size The length of the source data in bytes. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_input_string(yaml_parser_t *parser, | |
+ const unsigned char *input, size_t size); | |
+ | |
+/** | |
+ * Set a file input. | |
+ * | |
+ * @a file should be a file object open for reading. The application is | |
+ * responsible for closing the @a file. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ * @param[in] file An open file. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_input_file(yaml_parser_t *parser, FILE *file); | |
+ | |
+/** | |
+ * Set a generic input handler. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ * @param[in] handler A read handler. | |
+ * @param[in] data Any application data for passing to the read | |
+ * handler. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_input(yaml_parser_t *parser, | |
+ yaml_read_handler_t *handler, void *data); | |
+ | |
+/** | |
+ * Set the source encoding. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ * @param[in] encoding The source encoding. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_parser_set_encoding(yaml_parser_t *parser, yaml_encoding_t encoding); | |
+ | |
+/** | |
+ * Scan the input stream and produce the next token. | |
+ * | |
+ * Call the function subsequently to produce a sequence of tokens corresponding | |
+ * to the input stream. The initial token has the type | |
+ * @c YAML_STREAM_START_TOKEN while the ending token has the type | |
+ * @c YAML_STREAM_END_TOKEN. | |
+ * | |
+ * An application is responsible for freeing any buffers associated with the | |
+ * produced token object using the @c yaml_token_delete function. | |
+ * | |
+ * An application must not alternate the calls of yaml_parser_scan() with the | |
+ * calls of yaml_parser_parse() or yaml_parser_load(). Doing this will break | |
+ * the parser. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ * @param[out] token An empty token object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token); | |
+ | |
+/** | |
+ * Parse the input stream and produce the next parsing event. | |
+ * | |
+ * Call the function subsequently to produce a sequence of events corresponding | |
+ * to the input stream. The initial event has the type | |
+ * @c YAML_STREAM_START_EVENT while the ending event has the type | |
+ * @c YAML_STREAM_END_EVENT. | |
+ * | |
+ * An application is responsible for freeing any buffers associated with the | |
+ * produced event object using the yaml_event_delete() function. | |
+ * | |
+ * An application must not alternate the calls of yaml_parser_parse() with the | |
+ * calls of yaml_parser_scan() or yaml_parser_load(). Doing this will break the | |
+ * parser. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ * @param[out] event An empty event object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event); | |
+ | |
+/** | |
+ * Parse the input stream and produce the next YAML document. | |
+ * | |
+ * Call this function subsequently to produce a sequence of documents | |
+ * constituting the input stream. | |
+ * | |
+ * If the produced document has no root node, it means that the document | |
+ * end has been reached. | |
+ * | |
+ * An application is responsible for freeing any data associated with the | |
+ * produced document object using the yaml_document_delete() function. | |
+ * | |
+ * An application must not alternate the calls of yaml_parser_load() with the | |
+ * calls of yaml_parser_scan() or yaml_parser_parse(). Doing this will break | |
+ * the parser. | |
+ * | |
+ * @param[in,out] parser A parser object. | |
+ * @param[out] document An empty document object. | |
+ * | |
+ * @return @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document); | |
+ | |
+/** @} */ | |
+ | |
+/** | |
+ * @defgroup emitter Emitter Definitions | |
+ * @{ | |
+ */ | |
+ | |
+/** | |
+ * The prototype of a write handler. | |
+ * | |
+ * The write handler is called when the emitter needs to flush the accumulated | |
+ * characters to the output. The handler should write @a size bytes of the | |
+ * @a buffer to the output. | |
+ * | |
+ * @param[in,out] data A pointer to an application data specified by | |
+ * yaml_emitter_set_output(). | |
+ * @param[in] buffer The buffer with bytes to be written. | |
+ * @param[in] size The size of the buffer. | |
+ * | |
+ * @returns On success, the handler should return @c 1. If the handler failed, | |
+ * the returned value should be @c 0. | |
+ */ | |
+ | |
+typedef int yaml_write_handler_t(void *data, unsigned char *buffer, size_t size); | |
+ | |
+/** The emitter states. */ | |
+typedef enum yaml_emitter_state_e { | |
+ /** Expect STREAM-START. */ | |
+ YAML_EMIT_STREAM_START_STATE, | |
+ /** Expect the first DOCUMENT-START or STREAM-END. */ | |
+ YAML_EMIT_FIRST_DOCUMENT_START_STATE, | |
+ /** Expect DOCUMENT-START or STREAM-END. */ | |
+ YAML_EMIT_DOCUMENT_START_STATE, | |
+ /** Expect the content of a document. */ | |
+ YAML_EMIT_DOCUMENT_CONTENT_STATE, | |
+ /** Expect DOCUMENT-END. */ | |
+ YAML_EMIT_DOCUMENT_END_STATE, | |
+ /** Expect the first item of a flow sequence. */ | |
+ YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE, | |
+ /** Expect an item of a flow sequence. */ | |
+ YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE, | |
+ /** Expect the first key of a flow mapping. */ | |
+ YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE, | |
+ /** Expect a key of a flow mapping. */ | |
+ YAML_EMIT_FLOW_MAPPING_KEY_STATE, | |
+ /** Expect a value for a simple key of a flow mapping. */ | |
+ YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE, | |
+ /** Expect a value of a flow mapping. */ | |
+ YAML_EMIT_FLOW_MAPPING_VALUE_STATE, | |
+ /** Expect the first item of a block sequence. */ | |
+ YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE, | |
+ /** Expect an item of a block sequence. */ | |
+ YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE, | |
+ /** Expect the first key of a block mapping. */ | |
+ YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE, | |
+ /** Expect the key of a block mapping. */ | |
+ YAML_EMIT_BLOCK_MAPPING_KEY_STATE, | |
+ /** Expect a value for a simple key of a block mapping. */ | |
+ YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE, | |
+ /** Expect a value of a block mapping. */ | |
+ YAML_EMIT_BLOCK_MAPPING_VALUE_STATE, | |
+ /** Expect nothing. */ | |
+ YAML_EMIT_END_STATE | |
+} yaml_emitter_state_t; | |
+ | |
+/** | |
+ * The emitter structure. | |
+ * | |
+ * All members are internal. Manage the structure using the @c yaml_emitter_ | |
+ * family of functions. | |
+ */ | |
+ | |
+typedef struct yaml_emitter_s { | |
+ | |
+ /** | |
+ * @name Error handling | |
+ * @{ | |
+ */ | |
+ | |
+ /** Error type. */ | |
+ yaml_error_type_t error; | |
+ /** Error description. */ | |
+ const char *problem; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+ /** | |
+ * @name Writer stuff | |
+ * @{ | |
+ */ | |
+ | |
+ /** Write handler. */ | |
+ yaml_write_handler_t *write_handler; | |
+ | |
+ /** A pointer for passing to the white handler. */ | |
+ void *write_handler_data; | |
+ | |
+ /** Standard (string or file) output data. */ | |
+ union { | |
+ /** String output data. */ | |
+ struct { | |
+ /** The buffer pointer. */ | |
+ unsigned char *buffer; | |
+ /** The buffer size. */ | |
+ size_t size; | |
+ /** The number of written bytes. */ | |
+ size_t *size_written; | |
+ } string; | |
+ | |
+ /** File output data. */ | |
+ FILE *file; | |
+ } output; | |
+ | |
+ /** The working buffer. */ | |
+ struct { | |
+ /** The beginning of the buffer. */ | |
+ yaml_char_t *start; | |
+ /** The end of the buffer. */ | |
+ yaml_char_t *end; | |
+ /** The current position of the buffer. */ | |
+ yaml_char_t *pointer; | |
+ /** The last filled position of the buffer. */ | |
+ yaml_char_t *last; | |
+ } buffer; | |
+ | |
+ /** The raw buffer. */ | |
+ struct { | |
+ /** The beginning of the buffer. */ | |
+ unsigned char *start; | |
+ /** The end of the buffer. */ | |
+ unsigned char *end; | |
+ /** The current position of the buffer. */ | |
+ unsigned char *pointer; | |
+ /** The last filled position of the buffer. */ | |
+ unsigned char *last; | |
+ } raw_buffer; | |
+ | |
+ /** The stream encoding. */ | |
+ yaml_encoding_t encoding; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+ /** | |
+ * @name Emitter stuff | |
+ * @{ | |
+ */ | |
+ | |
+ /** If the output is in the canonical style? */ | |
+ int canonical; | |
+ /** The number of indentation spaces. */ | |
+ int best_indent; | |
+ /** The preferred width of the output lines. */ | |
+ int best_width; | |
+ /** Allow unescaped non-ASCII characters? */ | |
+ int unicode; | |
+ /** The preferred line break. */ | |
+ yaml_break_t line_break; | |
+ | |
+ /** The stack of states. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ yaml_emitter_state_t *start; | |
+ /** The end of the stack. */ | |
+ yaml_emitter_state_t *end; | |
+ /** The top of the stack. */ | |
+ yaml_emitter_state_t *top; | |
+ } states; | |
+ | |
+ /** The current emitter state. */ | |
+ yaml_emitter_state_t state; | |
+ | |
+ /** The event queue. */ | |
+ struct { | |
+ /** The beginning of the event queue. */ | |
+ yaml_event_t *start; | |
+ /** The end of the event queue. */ | |
+ yaml_event_t *end; | |
+ /** The head of the event queue. */ | |
+ yaml_event_t *head; | |
+ /** The tail of the event queue. */ | |
+ yaml_event_t *tail; | |
+ } events; | |
+ | |
+ /** The stack of indentation levels. */ | |
+ struct { | |
+ /** The beginning of the stack. */ | |
+ int *start; | |
+ /** The end of the stack. */ | |
+ int *end; | |
+ /** The top of the stack. */ | |
+ int *top; | |
+ } indents; | |
+ | |
+ /** The list of tag directives. */ | |
+ struct { | |
+ /** The beginning of the list. */ | |
+ yaml_tag_directive_t *start; | |
+ /** The end of the list. */ | |
+ yaml_tag_directive_t *end; | |
+ /** The top of the list. */ | |
+ yaml_tag_directive_t *top; | |
+ } tag_directives; | |
+ | |
+ /** The current indentation level. */ | |
+ int indent; | |
+ | |
+ /** The current flow level. */ | |
+ int flow_level; | |
+ | |
+ /** Is it the document root context? */ | |
+ int root_context; | |
+ /** Is it a sequence context? */ | |
+ int sequence_context; | |
+ /** Is it a mapping context? */ | |
+ int mapping_context; | |
+ /** Is it a simple mapping key context? */ | |
+ int simple_key_context; | |
+ | |
+ /** The current line. */ | |
+ int line; | |
+ /** The current column. */ | |
+ int column; | |
+ /** If the last character was a whitespace? */ | |
+ int whitespace; | |
+ /** If the last character was an indentation character (' ', '-', '?', ':')? */ | |
+ int indention; | |
+ /** If an explicit document end is required? */ | |
+ int open_ended; | |
+ | |
+ /** Anchor analysis. */ | |
+ struct { | |
+ /** The anchor value. */ | |
+ yaml_char_t *anchor; | |
+ /** The anchor length. */ | |
+ size_t anchor_length; | |
+ /** Is it an alias? */ | |
+ int alias; | |
+ } anchor_data; | |
+ | |
+ /** Tag analysis. */ | |
+ struct { | |
+ /** The tag handle. */ | |
+ yaml_char_t *handle; | |
+ /** The tag handle length. */ | |
+ size_t handle_length; | |
+ /** The tag suffix. */ | |
+ yaml_char_t *suffix; | |
+ /** The tag suffix length. */ | |
+ size_t suffix_length; | |
+ } tag_data; | |
+ | |
+ /** Scalar analysis. */ | |
+ struct { | |
+ /** The scalar value. */ | |
+ yaml_char_t *value; | |
+ /** The scalar length. */ | |
+ size_t length; | |
+ /** Does the scalar contain line breaks? */ | |
+ int multiline; | |
+ /** Can the scalar be expessed in the flow plain style? */ | |
+ int flow_plain_allowed; | |
+ /** Can the scalar be expressed in the block plain style? */ | |
+ int block_plain_allowed; | |
+ /** Can the scalar be expressed in the single quoted style? */ | |
+ int single_quoted_allowed; | |
+ /** Can the scalar be expressed in the literal or folded styles? */ | |
+ int block_allowed; | |
+ /** The output style. */ | |
+ yaml_scalar_style_t style; | |
+ } scalar_data; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+ /** | |
+ * @name Dumper stuff | |
+ * @{ | |
+ */ | |
+ | |
+ /** If the stream was already opened? */ | |
+ int opened; | |
+ /** If the stream was already closed? */ | |
+ int closed; | |
+ | |
+ /** The information associated with the document nodes. */ | |
+ struct { | |
+ /** The number of references. */ | |
+ int references; | |
+ /** The anchor id. */ | |
+ int anchor; | |
+ /** If the node has been emitted? */ | |
+ int serialized; | |
+ } *anchors; | |
+ | |
+ /** The last assigned anchor id. */ | |
+ int last_anchor_id; | |
+ | |
+ /** The currently emitted document. */ | |
+ yaml_document_t *document; | |
+ | |
+ /** | |
+ * @} | |
+ */ | |
+ | |
+} yaml_emitter_t; | |
+ | |
+/** | |
+ * Initialize an emitter. | |
+ * | |
+ * This function creates a new emitter object. An application is responsible | |
+ * for destroying the object using the yaml_emitter_delete() function. | |
+ * | |
+ * @param[out] emitter An empty parser object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_initialize(yaml_emitter_t *emitter); | |
+ | |
+/** | |
+ * Destroy an emitter. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_delete(yaml_emitter_t *emitter); | |
+ | |
+/** | |
+ * Set a string output. | |
+ * | |
+ * The emitter will write the output characters to the @a output buffer of the | |
+ * size @a size. The emitter will set @a size_written to the number of written | |
+ * bytes. If the buffer is smaller than required, the emitter produces the | |
+ * YAML_WRITE_ERROR error. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] output An output buffer. | |
+ * @param[in] size The buffer size. | |
+ * @param[in] size_written The pointer to save the number of written | |
+ * bytes. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_output_string(yaml_emitter_t *emitter, | |
+ unsigned char *output, size_t size, size_t *size_written); | |
+ | |
+/** | |
+ * Set a file output. | |
+ * | |
+ * @a file should be a file object open for writing. The application is | |
+ * responsible for closing the @a file. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] file An open file. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_output_file(yaml_emitter_t *emitter, FILE *file); | |
+ | |
+/** | |
+ * Set a generic output handler. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] handler A write handler. | |
+ * @param[in] data Any application data for passing to the write | |
+ * handler. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_output(yaml_emitter_t *emitter, | |
+ yaml_write_handler_t *handler, void *data); | |
+ | |
+/** | |
+ * Set the output encoding. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] encoding The output encoding. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_encoding(yaml_emitter_t *emitter, yaml_encoding_t encoding); | |
+ | |
+/** | |
+ * Set if the output should be in the "canonical" format as in the YAML | |
+ * specification. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] canonical If the output is canonical. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_canonical(yaml_emitter_t *emitter, int canonical); | |
+ | |
+/** | |
+ * Set the intendation increment. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] indent The indentation increment (1 < . < 10). | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_indent(yaml_emitter_t *emitter, int indent); | |
+ | |
+/** | |
+ * Set the preferred line width. @c -1 means unlimited. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] width The preferred line width. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_width(yaml_emitter_t *emitter, int width); | |
+ | |
+/** | |
+ * Set if unescaped non-ASCII characters are allowed. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] unicode If unescaped Unicode characters are allowed. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_unicode(yaml_emitter_t *emitter, int unicode); | |
+ | |
+/** | |
+ * Set the preferred line break. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in] line_break The preferred line break. | |
+ */ | |
+ | |
+YAML_DECLARE(void) | |
+yaml_emitter_set_break(yaml_emitter_t *emitter, yaml_break_t line_break); | |
+ | |
+/** | |
+ * Emit an event. | |
+ * | |
+ * The event object may be generated using the yaml_parser_parse() function. | |
+ * The emitter takes the responsibility for the event object and destroys its | |
+ * content after it is emitted. The event object is destroyed even if the | |
+ * function fails. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in,out] event An event object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event); | |
+ | |
+/** | |
+ * Start a YAML stream. | |
+ * | |
+ * This function should be used before yaml_emitter_dump() is called. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_open(yaml_emitter_t *emitter); | |
+ | |
+/** | |
+ * Finish a YAML stream. | |
+ * | |
+ * This function should be used after yaml_emitter_dump() is called. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_close(yaml_emitter_t *emitter); | |
+ | |
+/** | |
+ * Emit a YAML document. | |
+ * | |
+ * The documen object may be generated using the yaml_parser_load() function | |
+ * or the yaml_document_initialize() function. The emitter takes the | |
+ * responsibility for the document object and destoys its content after | |
+ * it is emitted. The document object is destroyedeven if the function fails. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * @param[in,out] document A document object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document); | |
+ | |
+/** | |
+ * Flush the accumulated characters to the output. | |
+ * | |
+ * @param[in,out] emitter An emitter object. | |
+ * | |
+ * @returns @c 1 if the function succeeded, @c 0 on error. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_emitter_flush(yaml_emitter_t *emitter); | |
+ | |
+/** @} */ | |
+ | |
+#ifdef __cplusplus | |
+} | |
+#endif | |
+ | |
+#endif /* #ifndef YAML_H */ | |
diff --git a/ext/psych/yaml/yaml_private.h b/ext/psych/yaml/yaml_private.h | |
new file mode 100644 | |
index 0000000..d3a5902 | |
--- /dev/null | |
+++ b/ext/psych/yaml/yaml_private.h | |
@@ -0,0 +1,642 @@ | |
+#ifdef RUBY_EXTCONF_H | |
+#include RUBY_EXTCONF_H | |
+#endif | |
+ | |
+#if HAVE_CONFIG_H | |
+#include <config.h> | |
+#endif | |
+ | |
+#include <yaml.h> | |
+ | |
+#include <assert.h> | |
+#include <limits.h> | |
+ | |
+/* | |
+ * Memory management. | |
+ */ | |
+ | |
+YAML_DECLARE(void *) | |
+yaml_malloc(size_t size); | |
+ | |
+YAML_DECLARE(void *) | |
+yaml_realloc(void *ptr, size_t size); | |
+ | |
+YAML_DECLARE(void) | |
+yaml_free(void *ptr); | |
+ | |
+YAML_DECLARE(yaml_char_t *) | |
+yaml_strdup(const yaml_char_t *); | |
+ | |
+/* | |
+ * Reader: Ensure that the buffer contains at least `length` characters. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_update_buffer(yaml_parser_t *parser, size_t length); | |
+ | |
+/* | |
+ * Scanner: Ensure that the token stack contains at least one token ready. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_parser_fetch_more_tokens(yaml_parser_t *parser); | |
+ | |
+/* | |
+ * The size of the input raw buffer. | |
+ */ | |
+ | |
+#define INPUT_RAW_BUFFER_SIZE 16384 | |
+ | |
+/* | |
+ * The size of the input buffer. | |
+ * | |
+ * It should be possible to decode the whole raw buffer. | |
+ */ | |
+ | |
+#define INPUT_BUFFER_SIZE (INPUT_RAW_BUFFER_SIZE*3) | |
+ | |
+/* | |
+ * The size of the output buffer. | |
+ */ | |
+ | |
+#define OUTPUT_BUFFER_SIZE 16384 | |
+ | |
+/* | |
+ * The size of the output raw buffer. | |
+ * | |
+ * It should be possible to encode the whole output buffer. | |
+ */ | |
+ | |
+#define OUTPUT_RAW_BUFFER_SIZE (OUTPUT_BUFFER_SIZE*2+2) | |
+ | |
+/* | |
+ * The size of other stacks and queues. | |
+ */ | |
+ | |
+#define INITIAL_STACK_SIZE 16 | |
+#define INITIAL_QUEUE_SIZE 16 | |
+#define INITIAL_STRING_SIZE 16 | |
+ | |
+/* | |
+ * Buffer management. | |
+ */ | |
+ | |
+#define BUFFER_INIT(context,buffer,size) \ | |
+ (((buffer).start = yaml_malloc(size)) ? \ | |
+ ((buffer).last = (buffer).pointer = (buffer).start, \ | |
+ (buffer).end = (buffer).start+(size), \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+#define BUFFER_DEL(context,buffer) \ | |
+ (yaml_free((buffer).start), \ | |
+ (buffer).start = (buffer).pointer = (buffer).end = 0) | |
+ | |
+/* | |
+ * String management. | |
+ */ | |
+ | |
+typedef struct { | |
+ yaml_char_t *start; | |
+ yaml_char_t *end; | |
+ yaml_char_t *pointer; | |
+} yaml_string_t; | |
+ | |
+YAML_DECLARE(int) | |
+yaml_string_extend(yaml_char_t **start, | |
+ yaml_char_t **pointer, yaml_char_t **end); | |
+ | |
+YAML_DECLARE(int) | |
+yaml_string_join( | |
+ yaml_char_t **a_start, yaml_char_t **a_pointer, yaml_char_t **a_end, | |
+ yaml_char_t **b_start, yaml_char_t **b_pointer, yaml_char_t **b_end); | |
+ | |
+#define NULL_STRING { NULL, NULL, NULL } | |
+ | |
+#define STRING(string,length) { (string), (string)+(length), (string) } | |
+ | |
+#define STRING_ASSIGN(value,string,length) \ | |
+ ((value).start = (string), \ | |
+ (value).end = (string)+(length), \ | |
+ (value).pointer = (string)) | |
+ | |
+#define STRING_INIT(context,string,size) \ | |
+ (((string).start = yaml_malloc(size)) ? \ | |
+ ((string).pointer = (string).start, \ | |
+ (string).end = (string).start+(size), \ | |
+ memset((string).start, 0, (size)), \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+#define STRING_DEL(context,string) \ | |
+ (yaml_free((string).start), \ | |
+ (string).start = (string).pointer = (string).end = 0) | |
+ | |
+#define STRING_EXTEND(context,string) \ | |
+ (((string).pointer+5 < (string).end) \ | |
+ || yaml_string_extend(&(string).start, \ | |
+ &(string).pointer, &(string).end)) | |
+ | |
+#define CLEAR(context,string) \ | |
+ ((string).pointer = (string).start, \ | |
+ memset((string).start, 0, (string).end-(string).start)) | |
+ | |
+#define JOIN(context,string_a,string_b) \ | |
+ ((yaml_string_join(&(string_a).start, &(string_a).pointer, \ | |
+ &(string_a).end, &(string_b).start, \ | |
+ &(string_b).pointer, &(string_b).end)) ? \ | |
+ ((string_b).pointer = (string_b).start, \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+/* | |
+ * String check operations. | |
+ */ | |
+ | |
+/* | |
+ * Check the octet at the specified position. | |
+ */ | |
+ | |
+#define CHECK_AT(string,octet,offset) \ | |
+ ((string).pointer[offset] == (yaml_char_t)(octet)) | |
+ | |
+/* | |
+ * Check the current octet in the buffer. | |
+ */ | |
+ | |
+#define CHECK(string,octet) CHECK_AT((string),(octet),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is an alphabetical | |
+ * character, a digit, '_', or '-'. | |
+ */ | |
+ | |
+#define IS_ALPHA_AT(string,offset) \ | |
+ (((string).pointer[offset] >= (yaml_char_t) '0' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) '9') || \ | |
+ ((string).pointer[offset] >= (yaml_char_t) 'A' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) 'Z') || \ | |
+ ((string).pointer[offset] >= (yaml_char_t) 'a' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) 'z') || \ | |
+ (string).pointer[offset] == '_' || \ | |
+ (string).pointer[offset] == '-') | |
+ | |
+#define IS_ALPHA(string) IS_ALPHA_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is a digit. | |
+ */ | |
+ | |
+#define IS_DIGIT_AT(string,offset) \ | |
+ (((string).pointer[offset] >= (yaml_char_t) '0' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) '9')) | |
+ | |
+#define IS_DIGIT(string) IS_DIGIT_AT((string),0) | |
+ | |
+/* | |
+ * Get the value of a digit. | |
+ */ | |
+ | |
+#define AS_DIGIT_AT(string,offset) \ | |
+ ((string).pointer[offset] - (yaml_char_t) '0') | |
+ | |
+#define AS_DIGIT(string) AS_DIGIT_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is a hex-digit. | |
+ */ | |
+ | |
+#define IS_HEX_AT(string,offset) \ | |
+ (((string).pointer[offset] >= (yaml_char_t) '0' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) '9') || \ | |
+ ((string).pointer[offset] >= (yaml_char_t) 'A' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) 'F') || \ | |
+ ((string).pointer[offset] >= (yaml_char_t) 'a' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) 'f')) | |
+ | |
+#define IS_HEX(string) IS_HEX_AT((string),0) | |
+ | |
+/* | |
+ * Get the value of a hex-digit. | |
+ */ | |
+ | |
+#define AS_HEX_AT(string,offset) \ | |
+ (((string).pointer[offset] >= (yaml_char_t) 'A' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) 'F') ? \ | |
+ ((string).pointer[offset] - (yaml_char_t) 'A' + 10) : \ | |
+ ((string).pointer[offset] >= (yaml_char_t) 'a' && \ | |
+ (string).pointer[offset] <= (yaml_char_t) 'f') ? \ | |
+ ((string).pointer[offset] - (yaml_char_t) 'a' + 10) : \ | |
+ ((string).pointer[offset] - (yaml_char_t) '0')) | |
+ | |
+#define AS_HEX(string) AS_HEX_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character is ASCII. | |
+ */ | |
+ | |
+#define IS_ASCII_AT(string,offset) \ | |
+ ((string).pointer[offset] <= (yaml_char_t) '\x7F') | |
+ | |
+#define IS_ASCII(string) IS_ASCII_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character can be printed unescaped. | |
+ */ | |
+ | |
+#define IS_PRINTABLE_AT(string,offset) \ | |
+ (((string).pointer[offset] == 0x0A) /* . == #x0A */ \ | |
+ || ((string).pointer[offset] >= 0x20 /* #x20 <= . <= #x7E */ \ | |
+ && (string).pointer[offset] <= 0x7E) \ | |
+ || ((string).pointer[offset] == 0xC2 /* #0xA0 <= . <= #xD7FF */ \ | |
+ && (string).pointer[offset+1] >= 0xA0) \ | |
+ || ((string).pointer[offset] > 0xC2 \ | |
+ && (string).pointer[offset] < 0xED) \ | |
+ || ((string).pointer[offset] == 0xED \ | |
+ && (string).pointer[offset+1] < 0xA0) \ | |
+ || ((string).pointer[offset] == 0xEE) \ | |
+ || ((string).pointer[offset] == 0xEF /* #xE000 <= . <= #xFFFD */ \ | |
+ && !((string).pointer[offset+1] == 0xBB /* && . != #xFEFF */ \ | |
+ && (string).pointer[offset+2] == 0xBF) \ | |
+ && !((string).pointer[offset+1] == 0xBF \ | |
+ && ((string).pointer[offset+2] == 0xBE \ | |
+ || (string).pointer[offset+2] == 0xBF)))) | |
+ | |
+#define IS_PRINTABLE(string) IS_PRINTABLE_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is NUL. | |
+ */ | |
+ | |
+#define IS_Z_AT(string,offset) CHECK_AT((string),'\0',(offset)) | |
+ | |
+#define IS_Z(string) IS_Z_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is BOM. | |
+ */ | |
+ | |
+#define IS_BOM_AT(string,offset) \ | |
+ (CHECK_AT((string),'\xEF',(offset)) \ | |
+ && CHECK_AT((string),'\xBB',(offset)+1) \ | |
+ && CHECK_AT((string),'\xBF',(offset)+2)) /* BOM (#xFEFF) */ | |
+ | |
+#define IS_BOM(string) IS_BOM_AT(string,0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is space. | |
+ */ | |
+ | |
+#define IS_SPACE_AT(string,offset) CHECK_AT((string),' ',(offset)) | |
+ | |
+#define IS_SPACE(string) IS_SPACE_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is tab. | |
+ */ | |
+ | |
+#define IS_TAB_AT(string,offset) CHECK_AT((string),'\t',(offset)) | |
+ | |
+#define IS_TAB(string) IS_TAB_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is blank (space or tab). | |
+ */ | |
+ | |
+#define IS_BLANK_AT(string,offset) \ | |
+ (IS_SPACE_AT((string),(offset)) || IS_TAB_AT((string),(offset))) | |
+ | |
+#define IS_BLANK(string) IS_BLANK_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character at the specified position is a line break. | |
+ */ | |
+ | |
+#define IS_BREAK_AT(string,offset) \ | |
+ (CHECK_AT((string),'\r',(offset)) /* CR (#xD)*/ \ | |
+ || CHECK_AT((string),'\n',(offset)) /* LF (#xA) */ \ | |
+ || (CHECK_AT((string),'\xC2',(offset)) \ | |
+ && CHECK_AT((string),'\x85',(offset)+1)) /* NEL (#x85) */ \ | |
+ || (CHECK_AT((string),'\xE2',(offset)) \ | |
+ && CHECK_AT((string),'\x80',(offset)+1) \ | |
+ && CHECK_AT((string),'\xA8',(offset)+2)) /* LS (#x2028) */ \ | |
+ || (CHECK_AT((string),'\xE2',(offset)) \ | |
+ && CHECK_AT((string),'\x80',(offset)+1) \ | |
+ && CHECK_AT((string),'\xA9',(offset)+2))) /* PS (#x2029) */ | |
+ | |
+#define IS_BREAK(string) IS_BREAK_AT((string),0) | |
+ | |
+#define IS_CRLF_AT(string,offset) \ | |
+ (CHECK_AT((string),'\r',(offset)) && CHECK_AT((string),'\n',(offset)+1)) | |
+ | |
+#define IS_CRLF(string) IS_CRLF_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character is a line break or NUL. | |
+ */ | |
+ | |
+#define IS_BREAKZ_AT(string,offset) \ | |
+ (IS_BREAK_AT((string),(offset)) || IS_Z_AT((string),(offset))) | |
+ | |
+#define IS_BREAKZ(string) IS_BREAKZ_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character is a line break, space, or NUL. | |
+ */ | |
+ | |
+#define IS_SPACEZ_AT(string,offset) \ | |
+ (IS_SPACE_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset))) | |
+ | |
+#define IS_SPACEZ(string) IS_SPACEZ_AT((string),0) | |
+ | |
+/* | |
+ * Check if the character is a line break, space, tab, or NUL. | |
+ */ | |
+ | |
+#define IS_BLANKZ_AT(string,offset) \ | |
+ (IS_BLANK_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset))) | |
+ | |
+#define IS_BLANKZ(string) IS_BLANKZ_AT((string),0) | |
+ | |
+/* | |
+ * Determine the width of the character. | |
+ */ | |
+ | |
+#define WIDTH_AT(string,offset) \ | |
+ (((string).pointer[offset] & 0x80) == 0x00 ? 1 : \ | |
+ ((string).pointer[offset] & 0xE0) == 0xC0 ? 2 : \ | |
+ ((string).pointer[offset] & 0xF0) == 0xE0 ? 3 : \ | |
+ ((string).pointer[offset] & 0xF8) == 0xF0 ? 4 : 0) | |
+ | |
+#define WIDTH(string) WIDTH_AT((string),0) | |
+ | |
+/* | |
+ * Move the string pointer to the next character. | |
+ */ | |
+ | |
+#define MOVE(string) ((string).pointer += WIDTH((string))) | |
+ | |
+/* | |
+ * Copy a character and move the pointers of both strings. | |
+ */ | |
+ | |
+#define COPY(string_a,string_b) \ | |
+ ((*(string_b).pointer & 0x80) == 0x00 ? \ | |
+ (*((string_a).pointer++) = *((string_b).pointer++)) : \ | |
+ (*(string_b).pointer & 0xE0) == 0xC0 ? \ | |
+ (*((string_a).pointer++) = *((string_b).pointer++), \ | |
+ *((string_a).pointer++) = *((string_b).pointer++)) : \ | |
+ (*(string_b).pointer & 0xF0) == 0xE0 ? \ | |
+ (*((string_a).pointer++) = *((string_b).pointer++), \ | |
+ *((string_a).pointer++) = *((string_b).pointer++), \ | |
+ *((string_a).pointer++) = *((string_b).pointer++)) : \ | |
+ (*(string_b).pointer & 0xF8) == 0xF0 ? \ | |
+ (*((string_a).pointer++) = *((string_b).pointer++), \ | |
+ *((string_a).pointer++) = *((string_b).pointer++), \ | |
+ *((string_a).pointer++) = *((string_b).pointer++), \ | |
+ *((string_a).pointer++) = *((string_b).pointer++)) : 0) | |
+ | |
+/* | |
+ * Stack and queue management. | |
+ */ | |
+ | |
+YAML_DECLARE(int) | |
+yaml_stack_extend(void **start, void **top, void **end); | |
+ | |
+YAML_DECLARE(int) | |
+yaml_queue_extend(void **start, void **head, void **tail, void **end); | |
+ | |
+#define STACK_INIT(context,stack,size) \ | |
+ (((stack).start = yaml_malloc((size)*sizeof(*(stack).start))) ? \ | |
+ ((stack).top = (stack).start, \ | |
+ (stack).end = (stack).start+(size), \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+#define STACK_DEL(context,stack) \ | |
+ (yaml_free((stack).start), \ | |
+ (stack).start = (stack).top = (stack).end = 0) | |
+ | |
+#define STACK_EMPTY(context,stack) \ | |
+ ((stack).start == (stack).top) | |
+ | |
+#define PUSH(context,stack,value) \ | |
+ (((stack).top != (stack).end \ | |
+ || yaml_stack_extend((void **)&(stack).start, \ | |
+ (void **)&(stack).top, (void **)&(stack).end)) ? \ | |
+ (*((stack).top++) = value, \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+#define POP(context,stack) \ | |
+ (*(--(stack).top)) | |
+ | |
+#define QUEUE_INIT(context,queue,size) \ | |
+ (((queue).start = yaml_malloc((size)*sizeof(*(queue).start))) ? \ | |
+ ((queue).head = (queue).tail = (queue).start, \ | |
+ (queue).end = (queue).start+(size), \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+#define QUEUE_DEL(context,queue) \ | |
+ (yaml_free((queue).start), \ | |
+ (queue).start = (queue).head = (queue).tail = (queue).end = 0) | |
+ | |
+#define QUEUE_EMPTY(context,queue) \ | |
+ ((queue).head == (queue).tail) | |
+ | |
+#define ENQUEUE(context,queue,value) \ | |
+ (((queue).tail != (queue).end \ | |
+ || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \ | |
+ (void **)&(queue).tail, (void **)&(queue).end)) ? \ | |
+ (*((queue).tail++) = value, \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+#define DEQUEUE(context,queue) \ | |
+ (*((queue).head++)) | |
+ | |
+#define QUEUE_INSERT(context,queue,index,value) \ | |
+ (((queue).tail != (queue).end \ | |
+ || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \ | |
+ (void **)&(queue).tail, (void **)&(queue).end)) ? \ | |
+ (memmove((queue).head+(index)+1,(queue).head+(index), \ | |
+ ((queue).tail-(queue).head-(index))*sizeof(*(queue).start)), \ | |
+ *((queue).head+(index)) = value, \ | |
+ (queue).tail++, \ | |
+ 1) : \ | |
+ ((context)->error = YAML_MEMORY_ERROR, \ | |
+ 0)) | |
+ | |
+/* | |
+ * Token initializers. | |
+ */ | |
+ | |
+#define TOKEN_INIT(token,token_type,token_start_mark,token_end_mark) \ | |
+ (memset(&(token), 0, sizeof(yaml_token_t)), \ | |
+ (token).type = (token_type), \ | |
+ (token).start_mark = (token_start_mark), \ | |
+ (token).end_mark = (token_end_mark)) | |
+ | |
+#define STREAM_START_TOKEN_INIT(token,token_encoding,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_STREAM_START_TOKEN,(start_mark),(end_mark)), \ | |
+ (token).data.stream_start.encoding = (token_encoding)) | |
+ | |
+#define STREAM_END_TOKEN_INIT(token,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_STREAM_END_TOKEN,(start_mark),(end_mark))) | |
+ | |
+#define ALIAS_TOKEN_INIT(token,token_value,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_ALIAS_TOKEN,(start_mark),(end_mark)), \ | |
+ (token).data.alias.value = (token_value)) | |
+ | |
+#define ANCHOR_TOKEN_INIT(token,token_value,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_ANCHOR_TOKEN,(start_mark),(end_mark)), \ | |
+ (token).data.anchor.value = (token_value)) | |
+ | |
+#define TAG_TOKEN_INIT(token,token_handle,token_suffix,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_TAG_TOKEN,(start_mark),(end_mark)), \ | |
+ (token).data.tag.handle = (token_handle), \ | |
+ (token).data.tag.suffix = (token_suffix)) | |
+ | |
+#define SCALAR_TOKEN_INIT(token,token_value,token_length,token_style,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_SCALAR_TOKEN,(start_mark),(end_mark)), \ | |
+ (token).data.scalar.value = (token_value), \ | |
+ (token).data.scalar.length = (token_length), \ | |
+ (token).data.scalar.style = (token_style)) | |
+ | |
+#define VERSION_DIRECTIVE_TOKEN_INIT(token,token_major,token_minor,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_VERSION_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \ | |
+ (token).data.version_directive.major = (token_major), \ | |
+ (token).data.version_directive.minor = (token_minor)) | |
+ | |
+#define TAG_DIRECTIVE_TOKEN_INIT(token,token_handle,token_prefix,start_mark,end_mark) \ | |
+ (TOKEN_INIT((token),YAML_TAG_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \ | |
+ (token).data.tag_directive.handle = (token_handle), \ | |
+ (token).data.tag_directive.prefix = (token_prefix)) | |
+ | |
+/* | |
+ * Event initializers. | |
+ */ | |
+ | |
+#define EVENT_INIT(event,event_type,event_start_mark,event_end_mark) \ | |
+ (memset(&(event), 0, sizeof(yaml_event_t)), \ | |
+ (event).type = (event_type), \ | |
+ (event).start_mark = (event_start_mark), \ | |
+ (event).end_mark = (event_end_mark)) | |
+ | |
+#define STREAM_START_EVENT_INIT(event,event_encoding,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_STREAM_START_EVENT,(start_mark),(end_mark)), \ | |
+ (event).data.stream_start.encoding = (event_encoding)) | |
+ | |
+#define STREAM_END_EVENT_INIT(event,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_STREAM_END_EVENT,(start_mark),(end_mark))) | |
+ | |
+#define DOCUMENT_START_EVENT_INIT(event,event_version_directive, \ | |
+ event_tag_directives_start,event_tag_directives_end,event_implicit,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_DOCUMENT_START_EVENT,(start_mark),(end_mark)), \ | |
+ (event).data.document_start.version_directive = (event_version_directive), \ | |
+ (event).data.document_start.tag_directives.start = (event_tag_directives_start), \ | |
+ (event).data.document_start.tag_directives.end = (event_tag_directives_end), \ | |
+ (event).data.document_start.implicit = (event_implicit)) | |
+ | |
+#define DOCUMENT_END_EVENT_INIT(event,event_implicit,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_DOCUMENT_END_EVENT,(start_mark),(end_mark)), \ | |
+ (event).data.document_end.implicit = (event_implicit)) | |
+ | |
+#define ALIAS_EVENT_INIT(event,event_anchor,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_ALIAS_EVENT,(start_mark),(end_mark)), \ | |
+ (event).data.alias.anchor = (event_anchor)) | |
+ | |
+#define SCALAR_EVENT_INIT(event,event_anchor,event_tag,event_value,event_length, \ | |
+ event_plain_implicit, event_quoted_implicit,event_style,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_SCALAR_EVENT,(start_mark),(end_mark)), \ | |
+ (event).data.scalar.anchor = (event_anchor), \ | |
+ (event).data.scalar.tag = (event_tag), \ | |
+ (event).data.scalar.value = (event_value), \ | |
+ (event).data.scalar.length = (event_length), \ | |
+ (event).data.scalar.plain_implicit = (event_plain_implicit), \ | |
+ (event).data.scalar.quoted_implicit = (event_quoted_implicit), \ | |
+ (event).data.scalar.style = (event_style)) | |
+ | |
+#define SEQUENCE_START_EVENT_INIT(event,event_anchor,event_tag, \ | |
+ event_implicit,event_style,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_SEQUENCE_START_EVENT,(start_mark),(end_mark)), \ | |
+ (event).data.sequence_start.anchor = (event_anchor), \ | |
+ (event).data.sequence_start.tag = (event_tag), \ | |
+ (event).data.sequence_start.implicit = (event_implicit), \ | |
+ (event).data.sequence_start.style = (event_style)) | |
+ | |
+#define SEQUENCE_END_EVENT_INIT(event,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_SEQUENCE_END_EVENT,(start_mark),(end_mark))) | |
+ | |
+#define MAPPING_START_EVENT_INIT(event,event_anchor,event_tag, \ | |
+ event_implicit,event_style,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_MAPPING_START_EVENT,(start_mark),(end_mark)), \ | |
+ (event).data.mapping_start.anchor = (event_anchor), \ | |
+ (event).data.mapping_start.tag = (event_tag), \ | |
+ (event).data.mapping_start.implicit = (event_implicit), \ | |
+ (event).data.mapping_start.style = (event_style)) | |
+ | |
+#define MAPPING_END_EVENT_INIT(event,start_mark,end_mark) \ | |
+ (EVENT_INIT((event),YAML_MAPPING_END_EVENT,(start_mark),(end_mark))) | |
+ | |
+/* | |
+ * Document initializer. | |
+ */ | |
+ | |
+#define DOCUMENT_INIT(document,document_nodes_start,document_nodes_end, \ | |
+ document_version_directive,document_tag_directives_start, \ | |
+ document_tag_directives_end,document_start_implicit, \ | |
+ document_end_implicit,document_start_mark,document_end_mark) \ | |
+ (memset(&(document), 0, sizeof(yaml_document_t)), \ | |
+ (document).nodes.start = (document_nodes_start), \ | |
+ (document).nodes.end = (document_nodes_end), \ | |
+ (document).nodes.top = (document_nodes_start), \ | |
+ (document).version_directive = (document_version_directive), \ | |
+ (document).tag_directives.start = (document_tag_directives_start), \ | |
+ (document).tag_directives.end = (document_tag_directives_end), \ | |
+ (document).start_implicit = (document_start_implicit), \ | |
+ (document).end_implicit = (document_end_implicit), \ | |
+ (document).start_mark = (document_start_mark), \ | |
+ (document).end_mark = (document_end_mark)) | |
+ | |
+/* | |
+ * Node initializers. | |
+ */ | |
+ | |
+#define NODE_INIT(node,node_type,node_tag,node_start_mark,node_end_mark) \ | |
+ (memset(&(node), 0, sizeof(yaml_node_t)), \ | |
+ (node).type = (node_type), \ | |
+ (node).tag = (node_tag), \ | |
+ (node).start_mark = (node_start_mark), \ | |
+ (node).end_mark = (node_end_mark)) | |
+ | |
+#define SCALAR_NODE_INIT(node,node_tag,node_value,node_length, \ | |
+ node_style,start_mark,end_mark) \ | |
+ (NODE_INIT((node),YAML_SCALAR_NODE,(node_tag),(start_mark),(end_mark)), \ | |
+ (node).data.scalar.value = (node_value), \ | |
+ (node).data.scalar.length = (node_length), \ | |
+ (node).data.scalar.style = (node_style)) | |
+ | |
+#define SEQUENCE_NODE_INIT(node,node_tag,node_items_start,node_items_end, \ | |
+ node_style,start_mark,end_mark) \ | |
+ (NODE_INIT((node),YAML_SEQUENCE_NODE,(node_tag),(start_mark),(end_mark)), \ | |
+ (node).data.sequence.items.start = (node_items_start), \ | |
+ (node).data.sequence.items.end = (node_items_end), \ | |
+ (node).data.sequence.items.top = (node_items_start), \ | |
+ (node).data.sequence.style = (node_style)) | |
+ | |
+#define MAPPING_NODE_INIT(node,node_tag,node_pairs_start,node_pairs_end, \ | |
+ node_style,start_mark,end_mark) \ | |
+ (NODE_INIT((node),YAML_MAPPING_NODE,(node_tag),(start_mark),(end_mark)), \ | |
+ (node).data.mapping.pairs.start = (node_pairs_start), \ | |
+ (node).data.mapping.pairs.end = (node_pairs_end), \ | |
+ (node).data.mapping.pairs.top = (node_pairs_start), \ | |
+ (node).data.mapping.style = (node_style)) | |
diff --git a/ext/psych/yaml_tree.c b/ext/psych/yaml_tree.c | |
deleted file mode 100644 | |
index bcf24d2..0000000 | |
--- a/ext/psych/yaml_tree.c | |
+++ /dev/null | |
@@ -1,24 +0,0 @@ | |
-#include <psych.h> | |
- | |
-VALUE cPsychVisitorsYamlTree; | |
- | |
-/* | |
- * call-seq: private_iv_get(target, prop) | |
- * | |
- * Get the private instance variable +prop+ from +target+ | |
- */ | |
-static VALUE private_iv_get(VALUE self, VALUE target, VALUE prop) | |
-{ | |
- return rb_attr_get(target, rb_intern(StringValuePtr(prop))); | |
-} | |
- | |
-void Init_psych_yaml_tree(void) | |
-{ | |
- VALUE psych = rb_define_module("Psych"); | |
- VALUE visitors = rb_define_module_under(psych, "Visitors"); | |
- VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); | |
- cPsychVisitorsYamlTree = rb_define_class_under(visitors, "YAMLTree", visitor); | |
- | |
- rb_define_private_method(cPsychVisitorsYamlTree, "private_iv_get", private_iv_get, 2); | |
-} | |
-/* vim: set noet sws=4 sw=4: */ | |
diff --git a/ext/psych/yaml_tree.h b/ext/psych/yaml_tree.h | |
deleted file mode 100644 | |
index 4628a69..0000000 | |
--- a/ext/psych/yaml_tree.h | |
+++ /dev/null | |
@@ -1,8 +0,0 @@ | |
-#ifndef PSYCH_YAML_TREE_H | |
-#define PSYCH_YAML_TREE_H | |
- | |
-#include <psych.h> | |
- | |
-void Init_psych_yaml_tree(void); | |
- | |
-#endif | |
diff --git a/test/psych/handlers/test_recorder.rb b/test/psych/handlers/test_recorder.rb | |
new file mode 100644 | |
index 0000000..96b8eac | |
--- /dev/null | |
+++ b/test/psych/handlers/test_recorder.rb | |
@@ -0,0 +1,25 @@ | |
+require 'psych/helper' | |
+require 'psych/handlers/recorder' | |
+ | |
+module Psych | |
+ module Handlers | |
+ class TestRecorder < TestCase | |
+ def test_replay | |
+ yaml = "--- foo\n...\n" | |
+ output = StringIO.new | |
+ | |
+ recorder = Psych::Handlers::Recorder.new | |
+ parser = Psych::Parser.new recorder | |
+ parser.parse yaml | |
+ | |
+ assert_equal 5, recorder.events.length | |
+ | |
+ emitter = Psych::Emitter.new output | |
+ recorder.events.each do |m, args| | |
+ emitter.send m, *args | |
+ end | |
+ assert_equal yaml, output.string | |
+ end | |
+ end | |
+ end | |
+end | |
diff --git a/test/psych/helper.rb b/test/psych/helper.rb | |
index 8108e99..f580030 100644 | |
--- a/test/psych/helper.rb | |
+++ b/test/psych/helper.rb | |
@@ -2,6 +2,7 @@ require 'minitest/autorun' | |
require 'stringio' | |
require 'tempfile' | |
require 'date' | |
+require 'psych' | |
module Psych | |
class TestCase < MiniTest::Unit::TestCase | |
@@ -53,11 +54,3 @@ module Psych | |
end | |
end | |
end | |
- | |
-require 'psych' | |
- | |
-# FIXME: remove this when syck is removed | |
-o = Object.new | |
-a = o.method(:psych_to_yaml) | |
-b = o.method(:to_yaml) | |
-raise "psych should define to_yaml" unless a == b | |
diff --git a/test/psych/test_alias_and_anchor.rb b/test/psych/test_alias_and_anchor.rb | |
index aa4773b..7cb5a6e 100644 | |
--- a/test/psych/test_alias_and_anchor.rb | |
+++ b/test/psych/test_alias_and_anchor.rb | |
@@ -24,8 +24,8 @@ EOYAML | |
def test_mri_compatibility_object_with_ivars | |
yaml = <<EOYAML | |
---- | |
-- &id001 !ruby/object:ObjectWithInstanceVariables | |
+--- | |
+- &id001 !ruby/object:ObjectWithInstanceVariables | |
var1: test1 | |
var2: test2 | |
- *id001 | |
@@ -33,7 +33,7 @@ EOYAML | |
EOYAML | |
result = Psych.load yaml | |
- result.each do |el| | |
+ result.each do |el| | |
assert_same(result[0], el) | |
assert_equal('test1', el.var1) | |
assert_equal('test2', el.var2) | |
@@ -42,8 +42,8 @@ EOYAML | |
def test_mri_compatibility_substring_with_ivars | |
yaml = <<EOYAML | |
---- | |
-- &id001 !str:SubStringWithInstanceVariables | |
+--- | |
+- &id001 !str:SubStringWithInstanceVariables | |
str: test | |
"@var1": test | |
- *id001 | |
diff --git a/test/psych/test_deprecated.rb b/test/psych/test_deprecated.rb | |
index cfce082..3740b6f 100644 | |
--- a/test/psych/test_deprecated.rb | |
+++ b/test/psych/test_deprecated.rb | |
@@ -145,7 +145,7 @@ module Psych | |
end | |
class YamlAs | |
- yaml_as 'helloworld' | |
+ psych_yaml_as 'helloworld' # this should be yaml_as but to avoid syck | |
end | |
def test_yaml_as | |
diff --git a/test/psych/test_engine_manager.rb b/test/psych/test_engine_manager.rb | |
index b52fd09..e7bfb2e 100644 | |
--- a/test/psych/test_engine_manager.rb | |
+++ b/test/psych/test_engine_manager.rb | |
@@ -3,10 +3,6 @@ require 'yaml' | |
module Psych | |
class TestEngineManager < TestCase | |
- def teardown | |
- YAML::ENGINE.yamler = 'syck' | |
- end | |
- | |
def test_bad_engine | |
assert_raises(ArgumentError) do | |
YAML::ENGINE.yamler = 'foooo' | |
@@ -19,12 +15,6 @@ module Psych | |
assert_equal 'psych', YAML::ENGINE.yamler | |
end | |
- def test_set_syck | |
- YAML::ENGINE.yamler = 'syck' | |
- assert_equal ::Syck, YAML | |
- assert_equal 'syck', YAML::ENGINE.yamler | |
- end | |
- | |
A = Struct.new(:name) | |
def test_dump_types | |
diff --git a/test/psych/test_exception.rb b/test/psych/test_exception.rb | |
index c6d98d7..46dece4 100644 | |
--- a/test/psych/test_exception.rb | |
+++ b/test/psych/test_exception.rb | |
@@ -126,5 +126,27 @@ module Psych | |
assert_equal 1, w.foo | |
assert_nil w.bar | |
end | |
+ | |
+ def test_psych_syntax_error | |
+ Tempfile.open(['parsefile', 'yml']) do |t| | |
+ t.binmode | |
+ t.write '--- `' | |
+ t.close | |
+ | |
+ begin | |
+ Psych.parse_file t.path | |
+ rescue StandardError | |
+ assert true # count assertion | |
+ ensure | |
+ t.close(true) | |
+ return unless $! | |
+ | |
+ ancestors = $!.class.ancestors.inspect | |
+ | |
+ flunk "Psych::SyntaxError not rescued by StandardError: #{ancestors}" | |
+ end | |
+ end | |
+ end | |
+ | |
end | |
end | |
diff --git a/test/psych/test_merge_keys.rb b/test/psych/test_merge_keys.rb | |
index bf5968f..c31f9b8 100644 | |
--- a/test/psych/test_merge_keys.rb | |
+++ b/test/psych/test_merge_keys.rb | |
@@ -2,6 +2,57 @@ require 'psych/helper' | |
module Psych | |
class TestMergeKeys < TestCase | |
+ def test_merge_nil | |
+ yaml = <<-eoyml | |
+defaults: &defaults | |
+development: | |
+ <<: *defaults | |
+ eoyml | |
+ assert_equal({'<<' => nil }, Psych.load(yaml)['development']) | |
+ end | |
+ | |
+ def test_merge_array | |
+ yaml = <<-eoyml | |
+foo: &hello | |
+- 1 | |
+baz: | |
+ <<: *hello | |
+ eoyml | |
+ assert_equal({'<<' => [1]}, Psych.load(yaml)['baz']) | |
+ end | |
+ | |
+ def test_merge_is_not_partial | |
+ yaml = <<-eoyml | |
+default: &default | |
+ hello: world | |
+foo: &hello | |
+- 1 | |
+baz: | |
+ <<: [*hello, *default] | |
+ eoyml | |
+ doc = Psych.load yaml | |
+ refute doc['baz'].key? 'hello' | |
+ assert_equal({'<<' => [[1], {"hello"=>"world"}]}, Psych.load(yaml)['baz']) | |
+ end | |
+ | |
+ def test_merge_seq_nil | |
+ yaml = <<-eoyml | |
+foo: &hello | |
+baz: | |
+ <<: [*hello] | |
+ eoyml | |
+ assert_equal({'<<' => [nil]}, Psych.load(yaml)['baz']) | |
+ end | |
+ | |
+ def test_bad_seq_merge | |
+ yaml = <<-eoyml | |
+defaults: &defaults [1, 2, 3] | |
+development: | |
+ <<: *defaults | |
+ eoyml | |
+ assert_equal({'<<' => [1,2,3]}, Psych.load(yaml)['development']) | |
+ end | |
+ | |
def test_missing_merge_key | |
yaml = <<-eoyml | |
bar: | |
diff --git a/test/psych/test_numeric.rb b/test/psych/test_numeric.rb | |
index bae723a..0858e79 100644 | |
--- a/test/psych/test_numeric.rb | |
+++ b/test/psych/test_numeric.rb | |
@@ -7,6 +7,19 @@ module Psych | |
# http://yaml.org/type/float.html | |
# http://yaml.org/type/int.html | |
class TestNumeric < TestCase | |
+ def setup | |
+ @old_debug = $DEBUG | |
+ $DEBUG = true | |
+ end | |
+ | |
+ def teardown | |
+ $DEBUG = @old_debug | |
+ end | |
+ | |
+ def test_load_float_with_dot | |
+ assert_equal 1.0, Psych.load('--- 1.') | |
+ end | |
+ | |
def test_non_float_with_0 | |
str = Psych.load('--- 090') | |
assert_equal '090', str | |
@@ -21,5 +34,12 @@ module Psych | |
decimal = BigDecimal("12.34") | |
assert_cycle decimal | |
end | |
+ | |
+ def test_does_not_attempt_numeric | |
+ str = Psych.load('--- 4 roses') | |
+ assert_equal '4 roses', str | |
+ str = Psych.load('--- 1.1.1') | |
+ assert_equal '1.1.1', str | |
+ end | |
end | |
end | |
diff --git a/test/psych/test_object_references.rb b/test/psych/test_object_references.rb | |
index 77cc96e..51d8085 100644 | |
--- a/test/psych/test_object_references.rb | |
+++ b/test/psych/test_object_references.rb | |
@@ -28,14 +28,14 @@ module Psych | |
def assert_reference_trip obj | |
yml = Psych.dump([obj, obj]) | |
- assert_match(/\*\d+/, yml) | |
+ assert_match(/\*-?\d+/, yml) | |
data = Psych.load yml | |
assert_equal data.first.object_id, data.last.object_id | |
end | |
def test_float_references | |
data = Psych.load <<-eoyml | |
---- | |
+---\s | |
- &name 1.2 | |
- *name | |
eoyml | |
@@ -56,7 +56,7 @@ module Psych | |
def test_regexp_references | |
data = Psych.load <<-eoyml | |
---- | |
+---\s | |
- &name !ruby/regexp /pattern/i | |
- *name | |
eoyml | |
diff --git a/test/psych/test_parser.rb b/test/psych/test_parser.rb | |
index d8c53f2..acbdd96 100644 | |
--- a/test/psych/test_parser.rb | |
+++ b/test/psych/test_parser.rb | |
@@ -32,6 +32,13 @@ module Psych | |
@handler.parser = @parser | |
end | |
+ def test_ast_roundtrip | |
+ parser = Psych.parser | |
+ parser.parse('null') | |
+ ast = parser.handler.root | |
+ assert_match(/^null/, ast.yaml) | |
+ end | |
+ | |
def test_exception_memory_leak | |
yaml = <<-eoyaml | |
%YAML 1.1 | |
diff --git a/test/psych/test_psych.rb b/test/psych/test_psych.rb | |
index 9986699..5433675 100644 | |
--- a/test/psych/test_psych.rb | |
+++ b/test/psych/test_psych.rb | |
@@ -20,7 +20,7 @@ class TestPsych < Psych::TestCase | |
def test_canonical | |
yml = Psych.dump({:a => {'b' => 'c'}}, {:canonical => true}) | |
- assert_match(/\? ! "b/, yml) | |
+ assert_match(/\? "b/, yml) | |
end | |
def test_header | |
@@ -68,6 +68,7 @@ class TestPsych < Psych::TestCase | |
assert_equal io, Psych.dump(hash, io) | |
io.rewind | |
assert_equal Psych.dump(hash), io.read | |
+ io.close(true) | |
end | |
end | |
diff --git a/test/psych/test_scalar_scanner.rb b/test/psych/test_scalar_scanner.rb | |
index cf0dfff..8483eab 100644 | |
--- a/test/psych/test_scalar_scanner.rb | |
+++ b/test/psych/test_scalar_scanner.rb | |
@@ -21,6 +21,17 @@ module Psych | |
end | |
end | |
+ def test_scan_bad_time | |
+ [ '2001-12-15T02:59:73.1Z', | |
+ '2001-12-14t90:59:43.10-05:00', | |
+ '2001-92-14 21:59:43.10 -5', | |
+ '2001-12-15 92:59:43.10', | |
+ '2011-02-24 81:17:06 -0800', | |
+ ].each do |time_str| | |
+ assert_equal time_str, @ss.tokenize(time_str) | |
+ end | |
+ end | |
+ | |
def test_scan_bad_dates | |
x = '2000-15-01' | |
assert_equal x, @ss.tokenize(x) | |
@@ -87,5 +98,9 @@ module Psych | |
def test_scan_true | |
assert_equal true, ss.tokenize('true') | |
end | |
+ | |
+ def test_scan_strings_starting_with_underscores | |
+ assert_equal "_100", ss.tokenize('_100') | |
+ end | |
end | |
end | |
diff --git a/test/psych/test_string.rb b/test/psych/test_string.rb | |
index 77aefc6..aa6866b 100644 | |
--- a/test/psych/test_string.rb | |
+++ b/test/psych/test_string.rb | |
@@ -9,6 +9,12 @@ module Psych | |
attr_accessor :val | |
end | |
+ class Z < String | |
+ def initialize | |
+ force_encoding Encoding::US_ASCII | |
+ end | |
+ end | |
+ | |
def test_another_subclass_with_attributes | |
y = Psych.load Psych.dump Y.new("foo").tap {|y| y.val = 1} | |
assert_equal "foo", y | |
@@ -28,6 +34,12 @@ module Psych | |
assert_equal X, x.class | |
end | |
+ def test_empty_character_subclass | |
+ assert_match "!ruby/string:#{Z}", Psych.dump(Z.new) | |
+ x = Psych.load Psych.dump Z.new | |
+ assert_equal Z, x.class | |
+ end | |
+ | |
def test_subclass_with_attributes | |
y = Psych.load Psych.dump Y.new.tap {|y| y.val = 1} | |
assert_equal Y, y.class | |
@@ -40,8 +52,8 @@ module Psych | |
assert_equal '01:03:05', Psych.load(yaml) | |
end | |
- def test_tagged_binary_should_be_dumped_as_binary | |
- string = "hello world!" | |
+ def test_nonascii_string_as_binary | |
+ string = "hello \x80 world!" | |
string.force_encoding 'ascii-8bit' | |
yml = Psych.dump string | |
assert_match(/binary/, yml) | |
@@ -69,6 +81,13 @@ module Psych | |
assert_equal string, Psych.load(yml) | |
end | |
+ def test_ascii_only_8bit_string | |
+ string = "abc".encode(Encoding::ASCII_8BIT) | |
+ yml = Psych.dump string | |
+ refute_match(/binary/, yml) | |
+ assert_equal string, Psych.load(yml) | |
+ end | |
+ | |
def test_string_with_ivars | |
food = "is delicious" | |
ivar = "on rock and roll" | |
@@ -83,6 +102,10 @@ module Psych | |
assert_cycle string | |
end | |
+ def test_float_confusion | |
+ assert_cycle '1.' | |
+ end | |
+ | |
def binary_string percentage = 0.31, length = 100 | |
string = '' | |
(percentage * length).to_i.times do |i| | |
diff --git a/test/psych/test_struct.rb b/test/psych/test_struct.rb | |
index 39e38f7..977aae0 100644 | |
--- a/test/psych/test_struct.rb | |
+++ b/test/psych/test_struct.rb | |
@@ -24,9 +24,7 @@ module Psych | |
loaded = Psych.load(Psych.dump(ss)) | |
assert_instance_of(StructSubclass, loaded.foo) | |
- # FIXME: This seems to cause an infinite loop. wtf. Must report a bug | |
- # in ruby. | |
- # assert_equal(ss, loaded) | |
+ assert_equal(ss, loaded) | |
end | |
def test_roundtrip | |
diff --git a/test/psych/test_yaml.rb b/test/psych/test_yaml.rb | |
index 807c058..9891349 100644 | |
--- a/test/psych/test_yaml.rb | |
+++ b/test/psych/test_yaml.rb | |
@@ -1,4 +1,4 @@ | |
-# -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*- | |
+# -*- coding: us-ascii; mode: ruby; ruby-indent-level: 4; tab-width: 4 -*- | |
# vim:sw=4:ts=4 | |
# $Id$ | |
# | |
@@ -1266,4 +1266,24 @@ EOY | |
Psych.load("2000-01-01 00:00:00.#{"0"*1000} +00:00\n") | |
# '[ruby-core:13735]' | |
end | |
+ | |
+ def test_multiline_string_uses_literal_style | |
+ yaml = Psych.dump("multi\nline\nstring") | |
+ assert_match("|", yaml) | |
+ end | |
+ | |
+ def test_string_starting_with_non_word_character_uses_double_quotes_without_exclamation_mark | |
+ yaml = Psych.dump("@123'abc") | |
+ refute_match("!", yaml) | |
+ end | |
+ | |
+ def test_string_dump_with_colon | |
+ yaml = Psych.dump 'x: foo' | |
+ refute_match '!', yaml | |
+ end | |
+ | |
+ def test_string_dump_starting_with_star | |
+ yaml = Psych.dump '*foo' | |
+ refute_match '!', yaml | |
+ end | |
end | |
diff --git a/test/psych/test_yamldbm.rb b/test/psych/test_yamldbm.rb | |
new file mode 100644 | |
index 0000000..d978003 | |
--- /dev/null | |
+++ b/test/psych/test_yamldbm.rb | |
@@ -0,0 +1,197 @@ | |
+# -*- coding: UTF-8 -*- | |
+ | |
+require 'psych/helper' | |
+require 'tmpdir' | |
+ | |
+begin | |
+ require 'yaml/dbm' | |
+rescue LoadError | |
+end | |
+ | |
+module Psych | |
+ ::Psych::DBM = ::YAML::DBM unless defined?(::Psych::DBM) | |
+ | |
+ class YAMLDBMTest < TestCase | |
+ def setup | |
+ @engine, YAML::ENGINE.yamler = YAML::ENGINE.yamler, 'psych' | |
+ @dir = Dir.mktmpdir("rubytest-file") | |
+ File.chown(-1, Process.gid, @dir) | |
+ @yamldbm_file = make_tmp_filename("yamldbm") | |
+ @yamldbm = YAML::DBM.new(@yamldbm_file) | |
+ end | |
+ | |
+ def teardown | |
+ YAML::ENGINE.yamler = @engine | |
+ @yamldbm.clear | |
+ @yamldbm.close | |
+ FileUtils.remove_entry_secure @dir | |
+ end | |
+ | |
+ def make_tmp_filename(prefix) | |
+ @dir + "/" + prefix + File.basename(__FILE__) + ".#{$$}.test" | |
+ end | |
+ | |
+ def test_store | |
+ @yamldbm.store('a','b') | |
+ @yamldbm.store('c','d') | |
+ assert_equal 'b', @yamldbm['a'] | |
+ assert_equal 'd', @yamldbm['c'] | |
+ assert_nil @yamldbm['e'] | |
+ end | |
+ | |
+ def test_store_using_carret | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal 'b', @yamldbm['a'] | |
+ assert_equal 'd', @yamldbm['c'] | |
+ assert_nil @yamldbm['e'] | |
+ end | |
+ | |
+ def test_to_a | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal([['a','b'],['c','d']], @yamldbm.to_a.sort) | |
+ end | |
+ | |
+ def test_to_hash | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal({'a'=>'b','c'=>'d'}, @yamldbm.to_hash) | |
+ end | |
+ | |
+ def test_has_value? | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal true, @yamldbm.has_value?('b') | |
+ assert_equal true, @yamldbm.has_value?('d') | |
+ assert_equal false, @yamldbm.has_value?('f') | |
+ end | |
+ | |
+ # Note: | |
+ # YAML::DBM#index makes warning from internal of ::DBM#index. | |
+ # It says 'DBM#index is deprecated; use DBM#key', but DBM#key | |
+ # behaves not same as DBM#index. | |
+ # | |
+ # def test_index | |
+ # @yamldbm['a'] = 'b' | |
+ # @yamldbm['c'] = 'd' | |
+ # assert_equal 'a', @yamldbm.index('b') | |
+ # assert_equal 'c', @yamldbm.index('d') | |
+ # assert_nil @yamldbm.index('f') | |
+ # end | |
+ | |
+ def test_key | |
+ skip 'only on ruby 2.0.0' if RUBY_VERSION < '2.0.0' | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal 'a', @yamldbm.key('b') | |
+ assert_equal 'c', @yamldbm.key('d') | |
+ assert_nil @yamldbm.key('f') | |
+ end | |
+ | |
+ def test_fetch | |
+ assert_equal('bar', @yamldbm['foo']='bar') | |
+ assert_equal('bar', @yamldbm.fetch('foo')) | |
+ assert_nil @yamldbm.fetch('bar') | |
+ assert_equal('baz', @yamldbm.fetch('bar', 'baz')) | |
+ assert_equal('foobar', @yamldbm.fetch('bar') {|key| 'foo' + key }) | |
+ end | |
+ | |
+ def test_shift | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal([['a','b'], ['c','d']], | |
+ [@yamldbm.shift, @yamldbm.shift].sort) | |
+ assert_nil @yamldbm.shift | |
+ end | |
+ | |
+ def test_invert | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal({'b'=>'a','d'=>'c'}, @yamldbm.invert) | |
+ end | |
+ | |
+ def test_update | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ @yamldbm.update({'c'=>'d','e'=>'f'}) | |
+ assert_equal 'b', @yamldbm['a'] | |
+ assert_equal 'd', @yamldbm['c'] | |
+ assert_equal 'f', @yamldbm['e'] | |
+ end | |
+ | |
+ def test_replace | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ @yamldbm.replace({'c'=>'d','e'=>'f'}) | |
+ assert_nil @yamldbm['a'] | |
+ assert_equal 'd', @yamldbm['c'] | |
+ assert_equal 'f', @yamldbm['e'] | |
+ end | |
+ | |
+ def test_delete | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal 'b', @yamldbm.delete('a') | |
+ assert_nil @yamldbm['a'] | |
+ assert_equal 'd', @yamldbm['c'] | |
+ assert_nil @yamldbm.delete('e') | |
+ end | |
+ | |
+ def test_delete_if | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ @yamldbm['e'] = 'f' | |
+ | |
+ @yamldbm.delete_if {|k,v| k == 'a'} | |
+ assert_nil @yamldbm['a'] | |
+ assert_equal 'd', @yamldbm['c'] | |
+ assert_equal 'f', @yamldbm['e'] | |
+ | |
+ @yamldbm.delete_if {|k,v| v == 'd'} | |
+ assert_nil @yamldbm['c'] | |
+ assert_equal 'f', @yamldbm['e'] | |
+ | |
+ @yamldbm.delete_if {|k,v| false } | |
+ assert_equal 'f', @yamldbm['e'] | |
+ end | |
+ | |
+ def test_reject | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ @yamldbm['e'] = 'f' | |
+ assert_equal({'c'=>'d','e'=>'f'}, @yamldbm.reject {|k,v| k == 'a'}) | |
+ assert_equal({'a'=>'b','e'=>'f'}, @yamldbm.reject {|k,v| v == 'd'}) | |
+ assert_equal({'a'=>'b','c'=>'d','e'=>'f'}, @yamldbm.reject {false}) | |
+ end | |
+ | |
+ def test_values | |
+ assert_equal [], @yamldbm.values | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal ['b','d'], @yamldbm.values.sort | |
+ end | |
+ | |
+ def test_values_at | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ assert_equal ['b','d'], @yamldbm.values_at('a','c') | |
+ end | |
+ | |
+ def test_selsct | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ @yamldbm['e'] = 'f' | |
+ assert_equal(['b','d'], @yamldbm.select('a','c')) | |
+ end | |
+ | |
+ def test_selsct_with_block | |
+ @yamldbm['a'] = 'b' | |
+ @yamldbm['c'] = 'd' | |
+ @yamldbm['e'] = 'f' | |
+ assert_equal([['a','b']], @yamldbm.select {|k,v| k == 'a'}) | |
+ assert_equal([['c','d']], @yamldbm.select {|k,v| v == 'd'}) | |
+ assert_equal([], @yamldbm.select {false}) | |
+ end | |
+ end | |
+end if defined?(YAML::DBM) && defined?(Psych) | |
diff --git a/test/psych/test_yamlstore.rb b/test/psych/test_yamlstore.rb | |
new file mode 100644 | |
index 0000000..5d6fcb7 | |
--- /dev/null | |
+++ b/test/psych/test_yamlstore.rb | |
@@ -0,0 +1,87 @@ | |
+require 'psych/helper' | |
+require 'yaml/store' | |
+require 'tmpdir' | |
+ | |
+module Psych | |
+ Psych::Store = YAML::Store unless defined?(Psych::Store) | |
+ | |
+ class YAMLStoreTest < TestCase | |
+ def setup | |
+ @engine, YAML::ENGINE.yamler = YAML::ENGINE.yamler, 'psych' | |
+ @dir = Dir.mktmpdir("rubytest-file") | |
+ File.chown(-1, Process.gid, @dir) | |
+ @yamlstore_file = make_tmp_filename("yamlstore") | |
+ @yamlstore = YAML::Store.new(@yamlstore_file) | |
+ end | |
+ | |
+ def teardown | |
+ YAML::ENGINE.yamler = @engine | |
+ FileUtils.remove_entry_secure @dir | |
+ end | |
+ | |
+ def make_tmp_filename(prefix) | |
+ @dir + "/" + prefix + File.basename(__FILE__) + ".#{$$}.test" | |
+ end | |
+ | |
+ def test_opening_new_file_in_readonly_mode_should_result_in_empty_values | |
+ @yamlstore.transaction(true) do | |
+ assert_nil @yamlstore[:foo] | |
+ assert_nil @yamlstore[:bar] | |
+ end | |
+ end | |
+ | |
+ def test_opening_new_file_in_readwrite_mode_should_result_in_empty_values | |
+ @yamlstore.transaction do | |
+ assert_nil @yamlstore[:foo] | |
+ assert_nil @yamlstore[:bar] | |
+ end | |
+ end | |
+ | |
+ def test_data_should_be_loaded_correctly_when_in_readonly_mode | |
+ @yamlstore.transaction do | |
+ @yamlstore[:foo] = "bar" | |
+ end | |
+ @yamlstore.transaction(true) do | |
+ assert_equal "bar", @yamlstore[:foo] | |
+ end | |
+ end | |
+ | |
+ def test_data_should_be_loaded_correctly_when_in_readwrite_mode | |
+ @yamlstore.transaction do | |
+ @yamlstore[:foo] = "bar" | |
+ end | |
+ @yamlstore.transaction do | |
+ assert_equal "bar", @yamlstore[:foo] | |
+ end | |
+ end | |
+ | |
+ def test_changes_after_commit_are_discarded | |
+ @yamlstore.transaction do | |
+ @yamlstore[:foo] = "bar" | |
+ @yamlstore.commit | |
+ @yamlstore[:foo] = "baz" | |
+ end | |
+ @yamlstore.transaction(true) do | |
+ assert_equal "bar", @yamlstore[:foo] | |
+ end | |
+ end | |
+ | |
+ def test_changes_are_not_written_on_abort | |
+ @yamlstore.transaction do | |
+ @yamlstore[:foo] = "bar" | |
+ @yamlstore.abort | |
+ end | |
+ @yamlstore.transaction(true) do | |
+ assert_nil @yamlstore[:foo] | |
+ end | |
+ end | |
+ | |
+ def test_writing_inside_readonly_transaction_raises_error | |
+ assert_raises(PStore::Error) do | |
+ @yamlstore.transaction(true) do | |
+ @yamlstore[:foo] = "bar" | |
+ end | |
+ end | |
+ end | |
+ end | |
+end if defined?(Psych) | |
diff --git a/test/psych/visitors/test_to_ruby.rb b/test/psych/visitors/test_to_ruby.rb | |
index 5b0702c..ee473c9 100644 | |
--- a/test/psych/visitors/test_to_ruby.rb | |
+++ b/test/psych/visitors/test_to_ruby.rb | |
@@ -1,3 +1,4 @@ | |
+# coding: US-ASCII | |
require 'psych/helper' | |
module Psych | |
diff --git a/test/psych/visitors/test_yaml_tree.rb b/test/psych/visitors/test_yaml_tree.rb | |
index df77563..496cdd0 100644 | |
--- a/test/psych/visitors/test_yaml_tree.rb | |
+++ b/test/psych/visitors/test_yaml_tree.rb | |
@@ -8,6 +8,24 @@ module Psych | |
@v = Visitors::YAMLTree.new | |
end | |
+ def test_tree_can_be_called_twice | |
+ @v.start | |
+ @v << Object.new | |
+ t = @v.tree | |
+ assert_equal t, @v.tree | |
+ end | |
+ | |
+ def test_yaml_tree_can_take_an_emitter | |
+ io = StringIO.new | |
+ e = Psych::Emitter.new io | |
+ v = Visitors::YAMLTree.new({}, e) | |
+ v.start | |
+ v << "hello world" | |
+ v.finish | |
+ | |
+ assert_match "hello world", io.string | |
+ end | |
+ | |
def test_binary_formatting | |
gif = "GIF89a\f\x00\f\x00\x84\x00\x00\xFF\xFF\xF7\xF5\xF5\xEE\xE9\xE9\xE5fff\x00\x00\x00\xE7\xE7\xE7^^^\xF3\xF3\xED\x8E\x8E\x8E\xE0\xE0\xE0\x9F\x9F\x9F\x93\x93\x93\xA7\xA7\xA7\x9E\x9E\x9Eiiiccc\xA3\xA3\xA3\x84\x84\x84\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9!\xFE\x0EMade with GIMP\x00,\x00\x00\x00\x00\f\x00\f\x00\x00\x05, \x8E\x810\x9E\xE3@\x14\xE8i\x10\xC4\xD1\x8A\b\x1C\xCF\x80M$z\xEF\xFF0\x85p\xB8\xB01f\r\e\xCE\x01\xC3\x01\x1E\x10' \x82\n\x01\x00;" | |
@v << gif | |
diff --git a/eval.c b/eval.c | |
index 708f899..86c814b 100644 | |
--- a/eval.c | |
+++ b/eval.c | |
@@ -823,7 +823,12 @@ rb_frame_caller(void) | |
void | |
rb_frame_pop(void) | |
{ | |
+ ID mid; | |
+ VALUE klass; | |
rb_thread_t *th = GET_THREAD(); | |
+ if (rb_thread_method_id_and_class(th, &mid, &klass)) { | |
+ EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, klass, mid, klass); | |
+ } | |
th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); | |
} | |
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb | |
index d6c6d06..97ce61b 100644 | |
--- a/test/ruby/test_settracefunc.rb | |
+++ b/test/ruby/test_settracefunc.rb | |
@@ -389,6 +389,91 @@ class TestSetTraceFunc < Test::Unit::TestCase | |
assert_equal(self, ok, bug3921) | |
end | |
+ def test_const_missing | |
+ bug59398 = '[ruby-core:59398]' | |
+ events = [] | |
+ assert !defined?(MISSING_CONSTANT_59398) | |
+ eval <<-EOF.gsub(/^.*?: /, "") | |
+ 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| | |
+ 2: events << [event, mid, klass] if event =~ /call|return/ && mid.to_s =~ /const_missing/ | |
+ 3: }) | |
+ 4: MISSING_CONSTANT_59398 rescue nil | |
+ 5: set_trace_func(nil) | |
+ EOF | |
+ if events.map{|e|e[1]}.include?(:rake_original_const_missing) | |
+ assert_equal([ | |
+ ["call", :const_missing, Module], | |
+ ["c-call", :rake_original_const_missing, Module], | |
+ ["c-return", :const_missing, Module], # 1.9.3 misses frame_called_id() and therefore reports the wrong method | |
+ ["return", :const_missing, Module] | |
+ ], events, bug59398) | |
+ else | |
+ assert_equal([ | |
+ ["c-call", :const_missing, Module], | |
+ ["c-return", :const_missing, Module] | |
+ ], events, bug59398) | |
+ end | |
+ end | |
+ | |
+ def test_aliased_ruby_method | |
+ events = [] | |
+ eval <<-EOF.gsub(/^.*?: /, "") | |
+ 1: class AliasedRubyMethod | |
+ 2: def foo; 1; end; | |
+ 3: alias bar foo | |
+ 4: end | |
+ 5: aliased = AliasedRubyMethod.new | |
+ 6: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| | |
+ 7: events << [event, lineno, mid, klass] if event =~ /^(call|return)/ | |
+ 8: }) | |
+ 9: aliased.bar | |
+ 10: set_trace_func(nil) | |
+ EOF | |
+ assert_equal([ | |
+ ["call", 2, :foo, TestSetTraceFunc::AliasedRubyMethod], | |
+ ["return", 2, :foo, TestSetTraceFunc::AliasedRubyMethod] | |
+ ], events, "should use original method name for tracing ruby methods") | |
+ end | |
+ | |
+ def test_aliased_c_method | |
+ events = [] | |
+ eval <<-EOF.gsub(/^.*?: /, "") | |
+ 1: class AliasedCMethod < Hash | |
+ 2: alias original_size size | |
+ 3: def size; original_size; end | |
+ 4: end | |
+ 5: aliased = AliasedCMethod.new | |
+ 6: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| | |
+ 7: events << [event, lineno, mid, klass] if event =~ /call|return/ && klass == TestSetTraceFunc::AliasedCMethod | |
+ 8: }) | |
+ 9: aliased.size | |
+ 10: set_trace_func(nil) | |
+ EOF | |
+ assert_equal([ | |
+ ["call", 3, :size, TestSetTraceFunc::AliasedCMethod], | |
+ ["c-call", 3, :original_size, TestSetTraceFunc::AliasedCMethod], | |
+ ["c-return", 3, :original_size, TestSetTraceFunc::AliasedCMethod], | |
+ ["return", 3, :size, TestSetTraceFunc::AliasedCMethod] | |
+ ], events, "should use alias method name for tracing c methods") | |
+ end | |
+ | |
+ def test_method_missing | |
+ bug59398 = '[ruby-core:59398]' | |
+ events = [] | |
+ assert !respond_to?(:missing_method_59398) | |
+ eval <<-EOF.gsub(/^.*?: /, "") | |
+ 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| | |
+ 2: events << [event, mid, klass] if event =~ /call|return/ && mid.to_s =~ /method_missing/ | |
+ 3: }) | |
+ 4: missing_method_59398 rescue nil | |
+ 5: set_trace_func(nil) | |
+ EOF | |
+ assert_equal([ | |
+ ["c-call", :method_missing, BasicObject], | |
+ ["c-return", :method_missing, BasicObject] | |
+ ], events, bug59398) | |
+ end | |
+ | |
class << self | |
define_method(:method_added, Module.method(:method_added)) | |
end | |
diff --git a/vm_eval.c b/vm_eval.c | |
index 2291001..cfc5eb3 100644 | |
--- a/vm_eval.c | |
+++ b/vm_eval.c | |
@@ -554,7 +554,7 @@ raise_method_missing(rb_thread_t *th, int argc, const VALUE *argv, VALUE obj, | |
exc = rb_class_new_instance(n, args, exc); | |
if (!(last_call_status & NOEX_MISSING)) { | |
- th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); | |
+ rb_frame_pop(); | |
} | |
rb_exc_raise(exc); | |
} | |
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb | |
index 83ff379..0d6a3eb 100644 | |
--- a/test/ruby/test_process.rb | |
+++ b/test/ruby/test_process.rb | |
@@ -1308,6 +1308,15 @@ class TestProcess < Test::Unit::TestCase | |
assert_equal("ok?\n", data) | |
end | |
+ def test_daemon_pid | |
+ cpid, dpid = IO.popen("-", "r+") do |f| | |
+ break f.pid, Integer(f.read) if f | |
+ Process.daemon(false, true) | |
+ puts $$ | |
+ end | |
+ assert_not_equal(cpid, dpid) | |
+ end | |
+ | |
if File.directory?("/proc/self/task") | |
def test_daemon_no_threads | |
pid, data = IO.popen("-", "r+") do |f| |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment