summaryrefslogtreecommitdiff
path: root/src/ragel/fspec.rl
blob: 8493cf182ebf0441b382d25f1724f54f579d7ab0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#include "fspec.h"
#include "ragel.h"

// It's pretty good base so far.
// ragel_search_str for typechecking variable delcaration is hack.
// State should have hashmap for fields/containers.
//
// XXX: Maybe drop whole container thing and just give field const char *parent; that points to keypath of container.
//      Then we would have flat structure like, "foo, foo.var, foo.b, ..."

static const struct fspec_container default_container = {0};
static const struct fspec_field default_field = { .array.nmemb = 1 };

enum stack_type {
   STACK_VAR,
   STACK_STR,
   STACK_NUM,
};

struct stack {
   enum stack_type type;

   union {
      struct fspec_bytes str;
      const char *var;
      uint64_t num;
   };
};

struct state {
   struct ragel ragel;
   struct stack stack;
   struct fspec_field field;
   struct fspec_container container;
   size_t container_data_offset;
};

static const char*
stack_type_to_str(const enum stack_type type)
{
   switch (type) {
      case STACK_VAR: return "var";
      case STACK_STR: return "str";
      case STACK_NUM: return "num";
   };

   assert(0 && "should not happen");
   return "unknown";
}

static void
stack_check_type(const struct ragel *ragel, const struct stack *stack, const enum stack_type type)
{
   assert(ragel && stack);

   if (stack->type != type)
      ragel_throw_error(ragel, "tried to get '%s' from stack, but the last pushed type was '%s'", stack_type_to_str(type), stack_type_to_str(stack->type));
}

static const char*
stack_get_var(const struct ragel *ragel, const struct stack *stack)
{
   assert(ragel && stack);
   stack_check_type(ragel, stack, STACK_VAR);
   return stack->var;
}

static const struct fspec_bytes*
stack_get_str(const struct ragel *ragel, const struct stack *stack)
{
   assert(ragel && stack);
   stack_check_type(ragel, stack, STACK_STR);
   return &stack->str;
}

static uint64_t
stack_get_num(const struct ragel *ragel, const struct stack *stack)
{
   assert(ragel && stack);
   stack_check_type(ragel, stack, STACK_NUM);
   return stack->num;
}

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

static void
fspec_type_from_str(const struct ragel *ragel, const char *str, struct fspec_type *out_type)
{
   assert(ragel && str);

   const struct fspec_type types[] = {
      { .name = "u8", .size = sizeof(uint8_t) },
      { .name = "u16", .size = sizeof(uint16_t) },
      { .name = "u32", .size = sizeof(uint32_t) },
      { .name = "u64", .size = sizeof(uint64_t) },
      { .name = "s8", .size = sizeof(int8_t), .flags = FSPEC_TYPE_SIGNED },
      { .name = "s16", .size = sizeof(int16_t), .flags = FSPEC_TYPE_SIGNED },
      { .name = "s32", .size = sizeof(int32_t), .flags = FSPEC_TYPE_SIGNED },
      { .name = "s64", .size = sizeof(int64_t), .flags = FSPEC_TYPE_SIGNED },
   };

   for (size_t i = 0; i < ARRAY_SIZE(types); ++i) {
      if (strcmp(str, types[i].name))
         continue;

      *out_type = types[i];
      return;
   }

   if (ragel_search_str(ragel, 0, str)) {
      *out_type = (struct fspec_type){ .name = str, .flags = FSPEC_TYPE_CONTAINER };
      return;
   }

   ragel_throw_error(ragel, "invalid type");
}

static void
fspec_kind_from_str(const struct ragel *ragel, const char *str, struct fspec_kind *out_kind)
{
   assert(ragel && str);

   const struct fspec_kind kinds[] = {
      { .name = "pad", .flags = FSPEC_KIND_IGNORE },
      { .name = "hex", .flags = FSPEC_KIND_HEXADECIMAL },
      { .name = "ascii", .flags = FSPEC_KIND_ENCODING },
      { .name = "utf8", .flags = FSPEC_KIND_ENCODING },
      { .name = "sjis", .flags = FSPEC_KIND_ENCODING },
   };

   for (size_t i = 0; i < ARRAY_SIZE(kinds); ++i) {
      if (strcmp(str, kinds[i].name))
         continue;

      *out_kind = kinds[i];
      return;
   }

   ragel_throw_error(ragel, "invalid kind");
}

static void
check_field_kind(const struct ragel *ragel, const struct fspec_field *field)
{
   assert(ragel && field);

   if ((field->kind.flags & FSPEC_KIND_ENCODING) && field->type.size != sizeof(uint8_t))
      ragel_throw_error(ragel, "invalid kind: %s kind only allowed for u8 and s8 types", field->kind.name);
}

%%{
   # File specification parser.

   machine fspec;
   variable p state.ragel.p;
   variable pe state.ragel.pe;
   variable eof state.ragel.eof;
   write data noerror nofinal;

   action field {
      fspec->ops.field(fspec, &state.container, &state.field);
   }

   action field_kind {
      fspec_kind_from_str(&state.ragel, stack_get_var(&state.ragel, &state.stack), &state.field.kind);
      check_field_kind(&state.ragel, &state.field);
   }

   action field_array {
      switch (state.stack.type) {
         case STACK_NUM:
            state.field.array.type = FSPEC_ARRAY_FIXED;
            state.field.array.nmemb = stack_get_num(&state.ragel, &state.stack);
            break;

         case STACK_STR:
            state.field.array.type = FSPEC_ARRAY_MATCH;
            state.field.array.match = *stack_get_str(&state.ragel, &state.stack);
            break;

         case STACK_VAR:
            state.field.array.type = FSPEC_ARRAY_VAR;
            state.field.array.var = stack_get_var(&state.ragel, &state.stack);

            if (!ragel_search_str(&state.ragel, state.container_data_offset, state.field.array.var))
               ragel_throw_error(&state.ragel, "undeclared variable '%s'", state.field.array.var);
            break;

         default:
            ragel_throw_error(&state.ragel, "array can't contain the stack type of '%s'", stack_type_to_str(state.stack.type));
            break;
      }
   }

   action field_name {
      state.field.name = stack_get_var(&state.ragel, &state.stack);
   }

   action field_type {
      state.field = default_field;
      fspec_type_from_str(&state.ragel, stack_get_var(&state.ragel, &state.stack), &state.field.type);
   }

   action container_name {
      state.container = default_container;
      state.container.name = stack_get_var(&state.ragel, &state.stack);
      state.container_data_offset = state.ragel.mem.cur - state.ragel.mem.data;
   }

   action push_var {
      state.stack.type = STACK_VAR;
      state.stack.var = (char*)state.ragel.mem.cur;
   }

   action push_hex {
      state.stack.type = STACK_NUM;
      state.stack.num = strtoll((char*)state.ragel.mem.cur, NULL, 16);
   }

   action push_dec {
      state.stack.type = STACK_NUM;
      state.stack.num = strtoll((char*)state.ragel.mem.cur, NULL, 10);
   }

   action push_str {
      state.stack.type = STACK_STR;
      state.stack.str.data = state.ragel.mem.cur;
      state.stack.str.size = (state.ragel.mem.data + state.ragel.mem.written) - state.ragel.mem.cur;
   }

   action convert_escape {
      ragel_convert_escape(&state.ragel);
   }

   action remove {
      ragel_remove_last_data(&state.ragel);
   }

   action finish {
      ragel_finish_data(&state.ragel);
   }

   action store {
      ragel_store_data(&state.ragel);
   }

   action begin {
      ragel_begin_data(&state.ragel);
   }

   action invalid_kind {
      ragel_throw_error(&state.ragel, "invalid kind");
   }

   action invalid_type {
      ragel_throw_error(&state.ragel, "invalid type");
   }

   action error {
      ragel_throw_error(&state.ragel, "malformed input (machine failed here or in previous or next expression)");
   }

   action line {
      ragel_advance_line(&state.ragel);
   }

   # Semantic
   ws = space;
   valid = ^cntrl;
   es = '\\';
   delim = ';';
   quote = ['"];
   bopen = '{';
   bclose = '}';
   newline = '\n';
   octal = [0-7];
   hex = '0x' <: xdigit+;
   decimal = ([1-9] <: digit*) | '0';
   comment = '//' <: valid* :>> newline;
   escape = es <: ('x' <: xdigit+ | [abfnrtv\\'"e] | octal{1,3});
   type = 'u8' | 'u16' | 'u32' | 'u64' | 's8' | 's16' | 's32' | 's64';
   kind = 'ascii' | 'utf8' | 'sjis' | 'hex' | 'pad';
   reserved = 'struct' | type | kind;
   var = ((alpha | '_') <: (alnum | '_')*) - reserved;

   # Catchers
   catch_var = var >begin $store %finish %push_var;
   catch_struct = ('struct' $store ws+ >store <: var $store) >begin %finish %push_var;
   catch_type = (catch_struct | type >begin $store %push_var %remove) $!invalid_type;
   catch_hex = hex >begin $store %push_hex %remove;
   catch_decimal = decimal >begin $store %push_dec %remove;
   catch_string = quote <: (escape %convert_escape | print)* >begin $store %finish %push_str :>> quote;
   catch_array = '[' <: (catch_hex | catch_decimal | catch_string | catch_var) :>> ']';
   catch_kind = '=' ws* <: kind >begin $store %push_var %remove $!invalid_kind;

   # Actions
   field = catch_type %field_type ws+ <: catch_var %field_name ws* <: (catch_array %field_array ws*)? <: (catch_kind %field_kind ws*)? :>> delim %field;
   container = catch_struct %container_name ws* :>> bopen <: (ws | comment | field)* :>> bclose ws* delim;
   line = valid* :>> newline @line;
   main := (ws | comment | container)* & line* $!error;
}%%

void
fspec_parse(struct fspec *fspec)
{
   int cs;
   %% write init;

   (void)fspec_en_main;
   assert(fspec);
   assert(fspec->ops.read);
   assert(fspec->ops.field);

   struct state state = {
      .ragel = {
         .lineno = 1,
         .mem = {
            .data = fspec->mem.data,
            .size = fspec->mem.size,
         },
      },
   };

   for (bool ok = true; ok;) {
      const size_t bytes = fspec->ops.read(fspec, state.ragel.buf, 1sizeof(state.ragel.buf));
      ok = ragel_confirm_input(&state.ragel, bytes);
      %% write exec;
   }
}