From b2b84b8a960f1b96cbd30d4c77ad4cf10286641e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phan=20Kochen?= Date: Mon, 30 Jan 2012 13:32:06 +0100 Subject: [PATCH] Move emitter event type helpers to JS. --- binding.cc | 242 +++++++++++++++++------------------------------------ yaml.js | 88 ++++++++++++++----- 2 files changed, 141 insertions(+), 189 deletions(-) diff --git a/binding.cc b/binding.cc index bfaba01..3bdb42a 100644 --- a/binding.cc +++ b/binding.cc @@ -198,6 +198,71 @@ EventToJs(yaml_event_t &event) } +// Create a LibYAML event from an input object. +static inline yaml_event_t * +JsToEvent(Local &obj) +{ + yaml_event_t *event = new yaml_event_t; + event->type = YAML_NO_EVENT; + + Local type = obj->Get(type_symbol); + + if (type->StrictEquals(stream_start_symbol)) { + // FIXME: Detect endianness? + yaml_stream_start_event_initialize(event, YAML_ANY_ENCODING); + } + + else if (type->StrictEquals(stream_end_symbol)) { + yaml_stream_end_event_initialize(event); + } + + else if (type->StrictEquals(document_start_symbol)) { + yaml_document_start_event_initialize(event, NULL, NULL, NULL, 0); + } + + else if (type->StrictEquals(document_end_symbol)) { + yaml_document_end_event_initialize(event, 0); + } + + else if (type->StrictEquals(alias_symbol)) { + Local tmp = obj->Get(anchor_symbol); + if (!tmp->IsString()) goto end; + String::AsciiValue anchor(tmp->ToString()); + + yaml_alias_event_initialize(event, (yaml_char_t *)*anchor); + } + + else if (type->StrictEquals(scalar_symbol)) { + Local tmp = obj->Get(value_symbol); + if (!tmp->IsString()) goto end; + String::AsciiValue value(tmp->ToString()); + + yaml_scalar_event_initialize(event, NULL, NULL, + (yaml_char_t *)*value, value.length(), + 1, 1, YAML_ANY_SCALAR_STYLE); + } + + else if (type->StrictEquals(sequence_start_symbol)) { + yaml_sequence_start_event_initialize(event, NULL, NULL, 0, YAML_ANY_SEQUENCE_STYLE); + } + + else if (type->StrictEquals(sequence_end_symbol)) { + yaml_sequence_end_event_initialize(event); + } + + else if (type->StrictEquals(mapping_start_symbol)) { + yaml_mapping_start_event_initialize(event, NULL, NULL, 0, YAML_ANY_MAPPING_STYLE); + } + + else if (type->StrictEquals(mapping_end_symbol)) { + yaml_mapping_end_event_initialize(event); + } + +end: + return event; +} + + // Create an Error from the LibYAML parser state. static inline Local ParserErrorToJs(yaml_parser_t &parser) @@ -330,21 +395,12 @@ Parse(const Arguments &args) } -// Binding to LibYAML's stream emitter. The usage is more or less the opposite of `parse`. -// Instead of getting callbacks, the user makes the calls, for example: +// Binding to LibYAML's stream emitter. The usage is more or less the opposite of `parse`: // -// var emitter = yaml.createEmitter(); -// emitter.stream(function() { -// emitter.document(function() { -// emitter.scalar("foobar"); -// }); -// }); +// var emitter = new Emitter(function(data) { /* ... */ }; +// emitter.event({ type: 'streamStart' }); // -// YAML stream events are exposed as methods on the emitter. The `YAML_*_START_EVENT` and -// `YAML_*_END_EVENT` types are exposed as single methods taking a function to wrap with -// the start and end events. -// -// As libYAML produces output, an array `emitter.chunks` is appended to with strings. +// The constructor takes a function called with output data as LibYAML provides it. class Emitter : ObjectWrap { public: @@ -354,12 +410,7 @@ class Emitter : ObjectWrap Local t = FunctionTemplate::New(New); t->InstanceTemplate()->SetInternalFieldCount(1); - NODE_SET_PROTOTYPE_METHOD(t, "stream", Stream); - NODE_SET_PROTOTYPE_METHOD(t, "document", Document); - NODE_SET_PROTOTYPE_METHOD(t, "sequence", Sequence); - NODE_SET_PROTOTYPE_METHOD(t, "mapping", Mapping); - NODE_SET_PROTOTYPE_METHOD(t, "alias", Alias); - NODE_SET_PROTOTYPE_METHOD(t, "scalar", Scalar); + NODE_SET_PROTOTYPE_METHOD(t, "event", Event); target->Set(String::NewSymbol("Emitter"), t->GetFunction()); } @@ -399,161 +450,20 @@ class Emitter : ObjectWrap return e->handle_; } - static inline Emitter * - GetEmitter(const Arguments &args) - { - return ObjectWrap::Unwrap(args.This()); - } - - static Handle - Stream(const Arguments &args) - { - if (args.Length() != 1) - return ThrowException(Exception::TypeError( - String::New("Expected one argument"))); - if (!args[0]->IsFunction()) - return ThrowException(Exception::TypeError( - String::New("Expected a function"))); - Local block = Local::Cast(args[0]); - - Emitter *e = GetEmitter(args); - yaml_event_t *ev; - - ev = new yaml_event_t; - yaml_stream_start_event_initialize(ev, YAML_ANY_ENCODING); - yaml_emitter_emit(&e->emitter_, ev); - - block->Call(Context::GetCurrent()->Global(), 0, NULL); - - ev = new yaml_event_t; - yaml_stream_end_event_initialize(ev); - yaml_emitter_emit(&e->emitter_, ev); - - return Undefined(); - } - - static Handle - Document(const Arguments &args) - { - // FIXME: Event options. - if (args.Length() != 1) - return ThrowException(Exception::TypeError( - String::New("Expected one argument"))); - if (!args[0]->IsFunction()) - return ThrowException(Exception::TypeError( - String::New("Expected a function"))); - Local block = Local::Cast(args[0]); - - Emitter *e = GetEmitter(args); - yaml_event_t *ev; - - ev = new yaml_event_t; - yaml_document_start_event_initialize(ev, NULL, NULL, NULL, 0); - yaml_emitter_emit(&e->emitter_, ev); - - block->Call(Context::GetCurrent()->Global(), 0, NULL); - - ev = new yaml_event_t; - yaml_document_end_event_initialize(ev, 0); - yaml_emitter_emit(&e->emitter_, ev); - - return Undefined(); - } - static Handle - Sequence(const Arguments &args) + Event(const Arguments &args) { - // FIXME: Event options. if (args.Length() != 1) return ThrowException(Exception::TypeError( String::New("Expected one argument"))); - if (!args[0]->IsFunction()) + if (!args[0]->IsObject()) return ThrowException(Exception::TypeError( - String::New("Expected a function"))); - Local block = Local::Cast(args[0]); + String::New("Expected an object"))); + Local obj = Local::Cast(args[0]); - Emitter *e = GetEmitter(args); - yaml_event_t *ev; + Emitter *e = ObjectWrap::Unwrap(args.This()); - ev = new yaml_event_t; - yaml_sequence_start_event_initialize(ev, NULL, NULL, 0, YAML_ANY_SEQUENCE_STYLE); - yaml_emitter_emit(&e->emitter_, ev); - - block->Call(Context::GetCurrent()->Global(), 0, NULL); - - ev = new yaml_event_t; - yaml_sequence_end_event_initialize(ev); - yaml_emitter_emit(&e->emitter_, ev); - - return Undefined(); - } - - static Handle - Mapping(const Arguments &args) - { - // FIXME: Event options. - if (args.Length() != 1) - return ThrowException(Exception::TypeError( - String::New("Expected one argument"))); - if (!args[0]->IsFunction()) - return ThrowException(Exception::TypeError( - String::New("Expected a function"))); - Local block = Local::Cast(args[0]); - - Emitter *e = GetEmitter(args); - yaml_event_t *ev; - - ev = new yaml_event_t; - yaml_mapping_start_event_initialize(ev, NULL, NULL, 0, YAML_ANY_MAPPING_STYLE); - yaml_emitter_emit(&e->emitter_, ev); - - block->Call(Context::GetCurrent()->Global(), 0, NULL); - - ev = new yaml_event_t; - yaml_mapping_end_event_initialize(ev); - yaml_emitter_emit(&e->emitter_, ev); - - return Undefined(); - } - - static Handle - Alias(const Arguments &args) - { - if (args.Length() != 1) - return ThrowException(Exception::TypeError( - String::New("Expected one argument"))); - if (!args[0]->IsString()) - return ThrowException(Exception::TypeError( - String::New("Expected a string"))); - String::AsciiValue anchor(args[0]->ToString()); - - Emitter *e = GetEmitter(args); - - yaml_event_t *ev = new yaml_event_t; - yaml_alias_event_initialize(ev, (yaml_char_t *)*anchor); - yaml_emitter_emit(&e->emitter_, ev); - - return Undefined(); - } - - static Handle - Scalar(const Arguments &args) - { - // FIXME: Event options. - if (args.Length() != 1) - return ThrowException(Exception::TypeError( - String::New("Expected one argument"))); - if (!args[0]->IsString()) - return ThrowException(Exception::TypeError( - String::New("Expected a string"))); - String::Utf8Value value(args[0]->ToString()); - - Emitter *e = GetEmitter(args); - - yaml_event_t *ev = new yaml_event_t; - yaml_scalar_event_initialize(ev, NULL, NULL, - (yaml_char_t *)*value, value.length(), - 1, 1, YAML_ANY_SCALAR_STYLE); + yaml_event_t *ev = JsToEvent(obj); yaml_emitter_emit(&e->emitter_, ev); return Undefined(); diff --git a/yaml.js b/yaml.js index 75a9c24..cdb6706 100644 --- a/yaml.js +++ b/yaml.js @@ -2,15 +2,16 @@ // MIT-licensed. (See the included LICENSE file.) var fs = require('fs'), + EventEmitter = require('events').EventEmitter, binding = exports.capi = require('./build/Release/binding'); // Low-level YAML stream exports. -exports.stream = {} +exports.stream = {}; -// Parse a YAML stream and call back with events. +// Create a raw event stream from YAML input. // -// yaml.stream.parse(input, handler); +// var output = yaml.stream.parse(input, handler); // // The handler can be an object that exposes methods for each LibYAML parser event. These are // named `onScalar`, `onSequenceStart`, etc. All of these methods take an event object that is @@ -31,6 +32,66 @@ exports.stream.parse = function(input, handler) { binding.parse(input, handler); }; +// Create a YAML data stream from raw events. +// +// var emitter = yaml.stream.createEmitter(function(data) { /* ... */ }); +// emitter.stream(function() { +// emitter.document(function() { +// emitter.scalar("foobar"); +// }); +// }); +// +// YAML stream events are exposed as methods on the emitter, e.g. 'streamStart', 'scalar', etc. +// Additionally, events that come in start/end pairs are exposed as single methods taking a +// function to wrap with the start and end events. +// +// As libYAML produces output, `data` events are emitted. The `createEmitter` factory can take +// a function argument which is immediately installed as a `data` event listener. +var YamlStreamEmitter = function() { + var callback = this.emit.bind(this, 'data'); + this.emitter_ = new binding.Emitter(callback); +}; +YamlStreamEmitter.prototype = new EventEmitter(); + +YamlStreamEmitter.event = function(obj) { + if (typeof(obj) === 'string') + obj = { type: obj }; + this.emitter_.event(obj); +}; + +['stream', 'document', 'sequence', 'mapping'].forEach(function(pre) { + var startEvent = pre + 'Start'; + YamlStreamEmitter.prototype[startEvent] = function() { + this.emitter_.event({ type: startEvent }); + }; + + var endEvent = pre + 'End'; + YamlStreamEmitter.prototype[endEvent] = function() { + this.emitter_.event({ type: endEvent }); + }; + + YamlStreamEmitter.prototype[pre] = function(block) { + this.emitter_.event({ type: startEvent }); + var result = block(); + this.emitter_.event({ type: endEvent }); + return result; + }; +}); + +YamlStreamEmitter.prototype.alias = function(anchor) { + this.emitter_.event({ type: 'alias', anchor: anchor }); +}; + +YamlStreamEmitter.prototype.scalar = function(value) { + this.emitter_.event({ type: 'scalar', value: value }); +}; + +exports.stream.createEmitter = function(handler) { + var result = new YamlStreamEmitter(); + if (handler) result.on('data', handler); + return result; +}; + // Most of these were derived from: http://yaml.org/type/ // Also borrows from tenderlove's `Psych::ScalarScanner`. (MIT-licensed) @@ -258,25 +319,6 @@ require.extensions[".yaml"] = require.extensions[".yml"] = function (module) { }; -// Binding to LibYAML's stream emitter. The usage is more or less the opposite of `parse`. -// Instead of getting callbacks, the user makes the calls, for example: -// -// var emitter = yaml.createEmitter(); -// emitter.stream(function() { -// emitter.document(function() { -// emitter.scalar("foobar"); -// }); -// }); -// -// YAML stream events are exposed as methods on the emitter. The `YAML_*_START_EVENT` and -// `YAML_*_END_EVENT` types are exposed as single methods taking a function to wrap with -// the start and end events. -// -// As libYAML produces output, an array `emitter.chunks` is appended to with strings. -exports.createEmitter = function() { - return new binding.Emitter(); -}; - // Helper function that emits a serialized version of the given item. var serialize = function(emitter, item) { // FIXME: throw on circulars @@ -330,7 +372,7 @@ var serialize = function(emitter, item) { // treated as a single document to serialize. The return value is a string. exports.dump = function() { var documents = arguments, chunks = [], emitter; - emitter = new binding.Emitter(function(chunk) { + emitter = new exports.stream.createEmitter(function(chunk) { chunks.push(chunk); }); emitter.stream(function() {