summaryrefslogtreecommitdiff
path: root/src/runtime/c/gu/yaml.c
diff options
context:
space:
mode:
authorkr.angelov <kr.angelov@gmail.com>2012-01-20 13:41:10 +0000
committerkr.angelov <kr.angelov@gmail.com>2012-01-20 13:41:10 +0000
commit2eee382a62a909d5a3f2f5eda94f30fe68fd5335 (patch)
treeb0b0d513535895f244214aebf6358e172b8dce6d /src/runtime/c/gu/yaml.c
parentb9728357126f8b9a6311cca17d9f0dcc2a7bfb9b (diff)
initial import of the C runtime
Diffstat (limited to 'src/runtime/c/gu/yaml.c')
-rw-r--r--src/runtime/c/gu/yaml.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/runtime/c/gu/yaml.c b/src/runtime/c/gu/yaml.c
new file mode 100644
index 000000000..4d769a4aa
--- /dev/null
+++ b/src/runtime/c/gu/yaml.c
@@ -0,0 +1,339 @@
+#include <gu/yaml.h>
+#include <gu/seq.h>
+#include <gu/assert.h>
+#include <gu/read.h>
+#include <gu/ucs.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+
+const GuYamlAnchor gu_yaml_null_anchor = 0;
+
+typedef const struct GuYamlState GuYamlState;
+
+struct GuYamlState {
+ const char* prefix;
+ const char* suffix;
+ GuYamlState* next;
+};
+
+static const struct {
+ GuYamlState document, first_key, key, value, first_elem, elem;
+} gu_yaml_states = {
+ .document = {
+ .prefix = "---\n",
+ .suffix = "\n...\n",
+ .next = &gu_yaml_states.document,
+ },
+ .key = {
+ .prefix = "? ",
+ .next = &gu_yaml_states.value,
+ },
+ .value = {
+ .prefix = ": ",
+ .suffix = ",",
+ .next = &gu_yaml_states.key,
+ },
+ .elem = {
+ .suffix = ",",
+ .next = &gu_yaml_states.elem,
+ },
+};
+
+typedef const struct GuYamlFrameClass GuYamlFrameClass;
+
+struct GuYamlFrameClass {
+ const char* open;
+ GuYamlState* first;
+ const char* close;
+};
+
+static const struct {
+ GuYamlFrameClass document, mapping, sequence;
+} gu_yaml_frame_classes = {
+ .mapping = {
+ .open = "{",
+ .first = &gu_yaml_states.key,
+ .close = "}",
+ },
+ .sequence = {
+ .open = "[",
+ .first = &gu_yaml_states.elem,
+ .close = "]",
+ },
+};
+
+typedef struct GuYamlFrame GuYamlFrame;
+
+struct GuYamlFrame {
+ GuYamlFrameClass* klass;
+ GuYamlState* next;
+};
+
+typedef GuBuf GuYamlStack;
+
+struct GuYaml {
+ GuWriter* wtr;
+ GuExn* err;
+ GuPool* pool;
+ GuYamlState* state;
+ bool in_node;
+ bool have_anchor;
+ bool have_tag;
+ int next_anchor;
+ bool indent;
+ int indent_level;
+ bool indented;
+ GuYamlStack* stack;
+};
+
+
+GuYaml*
+gu_new_yaml(GuWriter* wtr, GuExn* err, GuPool* pool)
+{
+ GuYaml* yaml = gu_new(GuYaml, pool);
+ yaml->wtr = wtr;
+ yaml->pool = pool;
+ yaml->err = err;
+ yaml->state = &gu_yaml_states.document;
+ yaml->in_node = false;
+ yaml->have_anchor = false;
+ yaml->have_tag = false;
+ yaml->next_anchor = 1;
+ yaml->stack = gu_new_buf(GuYamlFrame, pool);
+ yaml->indent = true;
+ yaml->indent_level = 0;
+ yaml->indented = false;
+ return yaml;
+}
+
+static void
+gu_yaml_printf(GuYaml* yaml, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gu_vprintf(fmt, args, yaml->wtr, yaml->err);
+ va_end(args);
+}
+
+static void
+gu_yaml_putc(GuYaml* yaml, char c)
+{
+ gu_putc(c, yaml->wtr, yaml->err);
+}
+
+static void
+gu_yaml_puts(GuYaml* yaml, const char* str)
+{
+ gu_puts(str, yaml->wtr, yaml->err);
+}
+
+static void
+gu_yaml_begin_line(GuYaml* yaml)
+{
+ if (yaml->indent && !yaml->indented) {
+ for (int i = 0; i < yaml->indent_level; i++) {
+ gu_yaml_putc(yaml, ' ');
+ }
+ yaml->indented = true;
+ }
+}
+
+static void
+gu_yaml_end_line(GuYaml* yaml)
+{
+ if (yaml->indent) {
+ gu_yaml_putc(yaml, '\n');
+ }
+ yaml->indented = false;
+}
+
+
+static void
+gu_yaml_begin_node(GuYaml* yaml)
+{
+ gu_yaml_begin_line(yaml);
+ if (!yaml->in_node) {
+ if (yaml->state->prefix != NULL) {
+ gu_yaml_puts(yaml, yaml->state->prefix);
+ }
+ yaml->in_node = true;
+ }
+}
+
+static void
+gu_yaml_end_node(GuYaml* yaml)
+{
+ gu_assert(yaml->in_node);
+ if (yaml->state->suffix != NULL) {
+ gu_yaml_puts(yaml, yaml->state->suffix);
+ }
+ gu_yaml_end_line(yaml);
+ yaml->in_node = false;
+ yaml->have_anchor = false;
+ yaml->have_tag = false;
+ if (yaml->state != NULL) {
+ yaml->state = yaml->state->next;
+ }
+}
+
+static void
+gu_yaml_begin(GuYaml* yaml, GuYamlFrameClass* klass)
+{
+ gu_yaml_begin_node(yaml);
+ gu_yaml_puts(yaml, klass->open);
+ gu_buf_push(yaml->stack, GuYamlFrame,
+ ((GuYamlFrame) { .klass = klass, .next = yaml->state}));
+ yaml->state = klass->first;
+ yaml->in_node = yaml->have_anchor = yaml->have_tag = false;
+ gu_yaml_end_line(yaml);
+ yaml->indent_level++;
+}
+
+void
+gu_yaml_begin_mapping(GuYaml* yaml)
+{
+ gu_yaml_begin(yaml, &gu_yaml_frame_classes.mapping);
+}
+
+void
+gu_yaml_begin_sequence(GuYaml* yaml)
+{
+ gu_yaml_begin(yaml, &gu_yaml_frame_classes.sequence);
+}
+
+void
+gu_yaml_end(GuYaml* yaml)
+{
+ gu_assert(!yaml->in_node);
+ yaml->indent_level--;
+ gu_yaml_begin_line(yaml);
+ GuYamlFrame f = gu_buf_pop(yaml->stack, GuYamlFrame);
+ gu_yaml_puts(yaml, f.klass->close);
+ yaml->state = f.next;
+ yaml->in_node = true;
+ gu_yaml_end_node(yaml);
+}
+
+
+void
+gu_yaml_scalar(GuYaml* yaml, GuString s)
+{
+ gu_yaml_begin_node(yaml);
+ gu_yaml_putc(yaml, '"');
+ GuPool* tmp_pool = gu_local_pool();
+ GuReader* rdr = gu_string_reader(s, tmp_pool);
+ GuExn* err = gu_exn(yaml->err, GuEOF, NULL);
+
+ static const char esc[0x20] = {
+ [0x00] = '0',
+ [0x07] = 'a', 'b', 't', 'n', 'v', 'f', 'r',
+ [0x1b] = 'e'
+ };
+
+ while (true) {
+ GuUCS u = gu_read_ucs(rdr, err);
+ if (!gu_ok(err)) {
+ break;
+ }
+ if (GU_LIKELY(u >= 0x20 && u < 0x7f)) {
+ if (GU_UNLIKELY(u == 0x22 || u == 0x5c)) {
+ gu_yaml_putc(yaml, '\\');
+ }
+ gu_ucs_write(u, yaml->wtr, yaml->err);
+ } else if (GU_UNLIKELY(u < 0x20 && esc[u])) {
+ gu_yaml_printf(yaml, "\\%c", esc[u]);
+ } else if (GU_UNLIKELY(u <= 0x9f)) {
+ gu_yaml_printf(yaml, "\\x%02x", (unsigned) u);
+ } else if (GU_UNLIKELY((u >= 0xd800 && u <= 0xdfff) ||
+ (u >= 0xfffe && u <= 0xffff))) {
+ gu_yaml_printf(yaml, "\\u%04x", (unsigned) u);
+ } else {
+ gu_ucs_write(u, yaml->wtr, yaml->err);
+ }
+ }
+ gu_pool_free(tmp_pool);
+ gu_yaml_putc(yaml, '"');
+ gu_yaml_end_node(yaml);
+}
+
+static void
+gu_yaml_tag(GuYaml* yaml, const char* format, ...)
+{
+ gu_yaml_begin_node(yaml);
+ gu_assert(!yaml->have_tag);
+ gu_yaml_putc(yaml, '!');
+ va_list args;
+ va_start(args, format);
+ gu_vprintf(format, args, yaml->wtr, yaml->err);
+ va_end(args);
+ gu_yaml_putc(yaml, ' ');
+ yaml->have_tag = true;
+}
+
+void
+gu_yaml_tag_primary(GuYaml* yaml, const char* tag)
+{
+ // TODO: check tag validity
+ gu_yaml_tag(yaml, "%s", tag);
+}
+
+void
+gu_yaml_tag_secondary(GuYaml* yaml, const char* tag)
+{
+ // TODO: check tag validity
+ gu_yaml_tag(yaml, "!%s", tag);
+}
+
+void
+gu_yaml_tag_named(GuYaml* yaml, const char* handle, const char* tag)
+{
+ // TODO: check tag validity
+ gu_yaml_tag(yaml, "%s!%s", handle, tag);
+}
+
+void
+gu_yaml_tag_verbatim(GuYaml* yaml, const char* uri)
+{
+ // XXX: uri escaping?
+ gu_yaml_tag(yaml, "<%s>", uri);
+}
+
+void
+gu_yaml_tag_non_specific(GuYaml* yaml)
+{
+ gu_yaml_tag(yaml, "");
+}
+
+GuYamlAnchor
+gu_yaml_anchor(GuYaml* yaml)
+{
+ gu_yaml_begin_node(yaml);
+ gu_assert(!yaml->have_anchor);
+ yaml->have_anchor = true;
+ int anchor = yaml->next_anchor++;
+ gu_yaml_printf(yaml, "&%d ", anchor);
+ return anchor;
+}
+
+void
+gu_yaml_alias(GuYaml* yaml, GuYamlAnchor anchor)
+{
+ gu_yaml_begin_node(yaml);
+ gu_assert(!yaml->have_anchor && !yaml->have_tag);
+ gu_yaml_printf(yaml, "*%d ", anchor);
+ gu_yaml_end_node(yaml);
+ return;
+}
+
+void gu_yaml_comment(GuYaml* yaml, GuString s)
+{
+ gu_yaml_begin_line(yaml);
+ gu_yaml_puts(yaml, "# ");
+ // TODO: verify no newlines in comment
+ gu_string_write(s, yaml->wtr, yaml->err);
+ gu_yaml_puts(yaml, "\n");
+ yaml->indented = false;
+}
+