2023-02-02 02:18:20 -05:00
|
|
|
/** @license 2023 Neil Edelman, distributed under the terms of the
|
|
|
|
[MIT License](https://opensource.org/licenses/MIT).
|
|
|
|
@std C11 */
|
2023-03-19 00:30:13 -04:00
|
|
|
#include "../src/flights.h"
|
2023-02-02 02:18:20 -05:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
|
|
|
|
static void flight_to_string(const union line64 line, const struct flight *u,
|
|
|
|
char (*const a)[12]) { (void)u; date32_to_string(line.date, a); }
|
|
|
|
static int flight_compare(const union line64 a, const union line64 b)
|
|
|
|
{ return a.u64 > b.u64; }
|
|
|
|
#define TREE_NAME flight
|
|
|
|
#define TREE_KEY union line64
|
|
|
|
#define TREE_VALUE struct flight
|
|
|
|
#define TREE_COMPARE
|
|
|
|
#define TREE_TO_STRING
|
2023-03-19 00:30:13 -04:00
|
|
|
#define TREE_BODY
|
2023-02-02 02:18:20 -05:00
|
|
|
#include "../src/tree.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*!conditions:re2c*/
|
|
|
|
|
2023-03-28 17:08:51 -04:00
|
|
|
static int scan(struct flight_tree *const f,
|
2023-02-12 23:53:15 -05:00
|
|
|
union date32 date, const char *const text) {
|
2023-02-14 00:32:43 -05:00
|
|
|
const char *YYCURSOR, *YYMARKER, *yyt1, *yyt2, *s0, *s1, *t0, *t1;
|
2023-02-02 02:18:20 -05:00
|
|
|
enum YYCONDTYPE condition = yycline;
|
|
|
|
size_t line = 1;
|
|
|
|
char datestr[12] = {0};
|
|
|
|
const char *why = "unexpected";
|
2023-02-12 23:53:15 -05:00
|
|
|
struct flight *flight = 0;
|
|
|
|
assert(f && text);
|
|
|
|
YYCURSOR = YYMARKER = yyt1 = text;
|
2023-02-02 02:18:20 -05:00
|
|
|
/*!re2c /**/
|
|
|
|
re2c:define:YYCTYPE = char;
|
|
|
|
re2c:yyfill:enable = 0;
|
|
|
|
re2c:define:YYGETCONDITION = "condition";
|
|
|
|
re2c:define:YYSETCONDITION = "condition = @@;";
|
|
|
|
re2c:define:YYGETCONDITION:naked = 1;
|
|
|
|
re2c:define:YYSETCONDITION:naked = 1;
|
|
|
|
|
|
|
|
ws = [ \t];
|
2023-02-14 17:39:26 -05:00
|
|
|
glyph = [^\x00-\x20\x7f]; // [^\x00\n\t ] + all weird
|
2023-02-12 23:53:15 -05:00
|
|
|
semitext = glyph \ ";";
|
2023-02-02 02:18:20 -05:00
|
|
|
natural = [1-9][0-9]*;
|
2023-02-14 00:32:43 -05:00
|
|
|
minutes = [0-5][0-9];
|
2023-02-15 00:08:19 -05:00
|
|
|
zero_natural = natural | "0";
|
2023-02-13 01:21:42 -05:00
|
|
|
airport = [A-Z0-9]{4,4};
|
2023-02-02 02:18:20 -05:00
|
|
|
*/
|
|
|
|
for( ; ; ) { /*!re2c /**/
|
|
|
|
/* Default ignore. */
|
|
|
|
<skip> [^\n\x00] { continue; }
|
|
|
|
<skip> "\x00" { why = "no newline at end of file"; goto catch; }
|
|
|
|
<line> "\x00" { return 1; }
|
|
|
|
<line, skip> "\n" => line { line++; continue; }
|
|
|
|
<line> * :=> skip
|
|
|
|
|
2023-02-14 00:32:43 -05:00
|
|
|
/* Except this . . . */
|
2023-02-12 23:53:15 -05:00
|
|
|
<line> "[glider]" :=> glider_type
|
2023-02-13 01:21:42 -05:00
|
|
|
/* type, reg, launch, how, height, landing, pilot, dual, instr, remarks
|
|
|
|
eg, [glider] 2-33A; C-GCLK; CYQQ; A; 2000'; CYQQ; ;:13;; Peters D1 */
|
2023-02-12 23:53:15 -05:00
|
|
|
<glider_type> * { why = "type unrecognized"; goto catch; }
|
|
|
|
<glider_reg> * { why = "reg unrecognized"; goto catch; }
|
|
|
|
<glider_launch> * { why = "launch unrecognized"; goto catch; }
|
2023-02-13 01:21:42 -05:00
|
|
|
<glider_how> * { why = "how unrecognized"; goto catch; }
|
|
|
|
<glider_height> * { why = "height unrecognized"; goto catch; }
|
|
|
|
<glider_landing> * { why = "landing unrecognized"; goto catch; }
|
|
|
|
<glider_pilot> * { why = "pilot unrecognized"; goto catch; }
|
|
|
|
<glider_dual> * { why = "dual unrecognized"; goto catch; }
|
|
|
|
<glider_instr> * { why = "instr unrecognized"; goto catch; }
|
2023-02-13 01:30:04 -05:00
|
|
|
<glider_remarks> * { why = "remarks unrecognized"; goto catch; }
|
2023-02-12 23:53:15 -05:00
|
|
|
<glider_type> ws* @s0 semitext+ @s1 ws* ";" => glider_reg {
|
|
|
|
const union line64 key
|
|
|
|
= {{ (uint32_t)line, {{ date.day, date.month, date.year }} }};
|
|
|
|
assert(!flight);
|
|
|
|
if(line > UINT32_MAX) { why = "line overflow"; goto catch; }
|
2023-03-28 17:08:51 -04:00
|
|
|
switch(flight_tree_try(f, key, &flight)) {
|
2023-02-12 23:53:15 -05:00
|
|
|
case TREE_PRESENT: why = "duplicate key";
|
|
|
|
case TREE_ERROR: goto catch;
|
|
|
|
case TREE_ABSENT: flight->type = GLIDER; break;
|
|
|
|
}
|
|
|
|
flight->glider.type.a = s0, flight->glider.type.b = s1;
|
2023-02-13 01:21:42 -05:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
<glider_reg> ws* @s0 semitext+ @s1 ws* ";" => glider_launch
|
|
|
|
{ flight->glider.reg.a = s0, flight->glider.reg.b = s1; continue; }
|
|
|
|
<glider_launch> ws* @s0 airport @s1 ws* ";" => glider_how
|
|
|
|
{ flight->glider.launch.a = s0, flight->glider.launch.b = s1;
|
|
|
|
continue; }
|
|
|
|
<glider_how> ws* @s0 [MWA] ws* ";" => glider_height {
|
|
|
|
switch(*s0) {
|
|
|
|
case 'M': flight->glider.how = MotorCarTow; break;
|
|
|
|
case 'W': flight->glider.how = Winch; break;
|
|
|
|
case 'A': flight->glider.how = AeroTow; break;
|
|
|
|
default: assert(0); break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
<glider_height> ws* @s0 natural @s1 "'" ws* ";" => glider_landing
|
|
|
|
{ if(!pair_to_natural(s0, s1, &flight->glider.height_ft));
|
|
|
|
continue; }
|
|
|
|
<glider_landing> ws* @s0 airport @s1 ws* ";" => glider_pilot
|
|
|
|
{ flight->glider.landing.a = s0, flight->glider.landing.b = s1;
|
|
|
|
continue; }
|
|
|
|
<glider_pilot> ws* ";" => glider_dual /* not PIC */
|
|
|
|
{ flight->glider.pilot_min = 0; continue; }
|
2023-02-14 00:32:43 -05:00
|
|
|
<glider_pilot> ws* @s0 natural? @s1 ":" @t0 minutes @t1 ws* ";"
|
2023-02-14 18:07:42 -05:00
|
|
|
=> glider_dual { if(!pair_colon_to_minutes(s0, s1, t0, t1,
|
|
|
|
&flight->glider.pilot_min)) { why = "pilot time"; goto catch; }
|
|
|
|
continue; }
|
2023-02-13 01:21:42 -05:00
|
|
|
<glider_dual> ws* ";" => glider_instr
|
|
|
|
{ flight->glider.dual_min = 0; continue; }
|
2023-02-14 00:32:43 -05:00
|
|
|
<glider_dual> ws* @s0 natural? @s1 ":" @t0 minutes @t1 ws* ";"
|
2023-02-14 18:07:42 -05:00
|
|
|
=> glider_instr { if(!pair_colon_to_minutes(s0, s1, t0, t1,
|
|
|
|
&flight->glider.dual_min)) { why = "dual time"; goto catch; }
|
|
|
|
continue; }
|
2023-02-13 01:30:04 -05:00
|
|
|
<glider_instr> ws* ";" => glider_remarks
|
2023-02-13 01:21:42 -05:00
|
|
|
{ flight->glider.instr_min = 0; continue; }
|
2023-02-14 00:32:43 -05:00
|
|
|
<glider_instr> ws* @s0 natural? @s1 ":" @t0 minutes @t1 ws* ";"
|
2023-02-14 18:07:42 -05:00
|
|
|
=> glider_remarks { if(!pair_hours_to_minutes(s0, s1, t0, t1,
|
|
|
|
&flight->glider.instr_min)) { why = "instr time"; goto catch; }
|
|
|
|
continue; }
|
2023-02-14 00:32:43 -05:00
|
|
|
<glider_remarks> ws* "\n" => line
|
|
|
|
{ flight->glider.remarks.a = flight->glider.remarks.b = 0;
|
|
|
|
flight = 0; line++; continue; }
|
2023-02-15 00:08:19 -05:00
|
|
|
<glider_remarks> ws* @s0 glyph+ (ws+ glyph+)* @s1 "\n" => line
|
|
|
|
{ flight->glider.remarks.a = s0, flight->glider.remarks.b = s1;
|
|
|
|
flight = 0; line++; continue; }
|
2023-02-14 00:32:43 -05:00
|
|
|
|
|
|
|
/* And this . . . */
|
|
|
|
<line> "[flight]" :=> flight_type
|
|
|
|
/* type; registration; launch -- landing; pic; sic;
|
|
|
|
single engine day dual; pilot; instrument simulated; actual; remarks */
|
|
|
|
<flight_type> * { why = "type unrecognized"; goto catch; }
|
|
|
|
<flight_reg> * { why = "reg unrecognized"; goto catch; }
|
|
|
|
<flight_airports> * { why = "airports unrecognized"; goto catch; }
|
|
|
|
<flight_pic> * { why = "pic unrecognized"; goto catch; }
|
|
|
|
<flight_sic> * { why = "sic unrecognized"; goto catch; }
|
2023-02-14 17:39:26 -05:00
|
|
|
<flight_dual> * { why = "dual time unrecognized"; goto catch; }
|
|
|
|
<flight_pilot> * { why = "pilot time unrecognized"; goto catch; }
|
|
|
|
<flight_ifrsim> * { why = "simulated ifr time unrecognized";
|
|
|
|
goto catch; }
|
|
|
|
<flight_ifr> * { why = "ifr time unrecognized"; goto catch; }
|
2023-02-14 00:32:43 -05:00
|
|
|
<flight_remarks> * { why = "remarks unrecognized"; goto catch; }
|
|
|
|
<flight_type> ws* @s0 semitext+ @s1 ws* ";" => flight_reg {
|
|
|
|
const union line64 key
|
|
|
|
= {{ (uint32_t)line, {{ date.day, date.month, date.year }} }};
|
|
|
|
assert(!flight);
|
|
|
|
if(line > UINT32_MAX) { why = "line overflow"; goto catch; }
|
2023-03-28 17:08:51 -04:00
|
|
|
switch(flight_tree_try(f, key, &flight)) {
|
2023-02-14 00:32:43 -05:00
|
|
|
case TREE_PRESENT: why = "duplicate key";
|
|
|
|
case TREE_ERROR: goto catch;
|
|
|
|
case TREE_ABSENT: flight->type = POWER; break;
|
|
|
|
}
|
|
|
|
flight->power.type.a = s0, flight->power.type.b = s1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
<flight_reg> ws* @s0 semitext+ @s1 ws* ";" => flight_airports
|
|
|
|
{ flight->power.reg.a = s0, flight->power.reg.b = s1; continue; }
|
|
|
|
<flight_airports> ws* @s0 airport @s1 ws* "--"
|
|
|
|
ws* @t0 airport @t1 ws* ";" => flight_pic {
|
|
|
|
flight->power.launch.a = s0, flight->power.launch.b = s1;
|
|
|
|
flight->power.landing.a = t0, flight->power.landing.b = t1;
|
|
|
|
continue;
|
|
|
|
}
|
2023-02-14 18:07:42 -05:00
|
|
|
<flight_pic> ws* @s0 semitext+ (ws+ semitext+)* @s1 /* ws*? */";"
|
2023-02-14 17:39:26 -05:00
|
|
|
=> flight_sic { flight->power.pilot.a = s0,
|
|
|
|
flight->power.pilot.b = s1; continue; }
|
|
|
|
<flight_sic> ws* ";" => flight_dual
|
|
|
|
{ flight->power.copilot.a = flight->power.copilot.b = 0; continue; }
|
2023-02-14 18:07:42 -05:00
|
|
|
<flight_sic> ws* @s0 semitext+ (ws+ semitext+)* @s1 ";"
|
2023-02-14 17:39:26 -05:00
|
|
|
=> flight_dual { flight->power.copilot.a = s0,
|
|
|
|
flight->power.copilot.b = s1; continue; }
|
2023-02-14 18:07:42 -05:00
|
|
|
<flight_dual> ws* ";" => flight_pilot
|
|
|
|
{ flight->power.dual_min = 0; continue; }
|
2023-02-15 00:08:19 -05:00
|
|
|
<flight_dual> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
|
2023-02-14 18:07:42 -05:00
|
|
|
=> flight_pilot { if(!pair_hours_to_minutes(s0, s1, t0, t1,
|
|
|
|
&flight->power.dual_min)) { why = "dual time"; goto catch; }
|
|
|
|
continue; }
|
|
|
|
<flight_pilot> ws* ";" => flight_ifrsim
|
|
|
|
{ flight->power.pilot_min = 0; continue; }
|
2023-02-15 00:08:19 -05:00
|
|
|
<flight_pilot> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
|
2023-02-14 18:07:42 -05:00
|
|
|
=> flight_ifrsim { if(!pair_hours_to_minutes(s0, s1, t0, t1,
|
|
|
|
&flight->power.pilot_min)) { why = "pilot time"; goto catch; }
|
|
|
|
continue; }
|
2023-02-15 00:08:19 -05:00
|
|
|
<flight_ifrsim> ws* ";" => flight_ifr
|
|
|
|
{ flight->power.ifrsim_min = 0; continue; }
|
|
|
|
<flight_ifrsim> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
|
|
|
|
=> flight_ifr { if(!pair_hours_to_minutes(s0, s1, t0, t1,
|
|
|
|
&flight->power.ifrsim_min)) { why = "simulated ifr time";
|
|
|
|
goto catch; } continue; }
|
|
|
|
<flight_ifr> ws* ";" => flight_remarks
|
|
|
|
{ flight->power.ifr_min = 0; continue; }
|
|
|
|
<flight_ifr> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
|
|
|
|
=> flight_remarks { if(!pair_hours_to_minutes(s0, s1, t0, t1,
|
|
|
|
&flight->power.ifr_min)) { why = "pilot time"; goto catch; }
|
|
|
|
continue; }
|
|
|
|
<flight_remarks> ws* "\n" => line
|
|
|
|
{ flight->power.remarks.a = flight->power.remarks.b = 0;
|
|
|
|
flight = 0; line++; continue; }
|
|
|
|
<flight_remarks> ws* @s0 glyph+ (ws+ glyph+)* @s1 "\n" => line
|
|
|
|
{ flight->power.remarks.a = s0, flight->power.remarks.b = s1;
|
|
|
|
flight = 0; line++; continue; }
|
2023-02-02 02:18:20 -05:00
|
|
|
*/ }
|
|
|
|
assert(0); /* Never gets here. */
|
|
|
|
catch:
|
|
|
|
if(!errno) errno = EILSEQ;
|
|
|
|
date32_to_string(date, &datestr);
|
2023-02-17 17:06:21 -05:00
|
|
|
fprintf(stderr, "%s line %zu: %s.\n", datestr, line, why);
|
2023-02-02 02:18:20 -05:00
|
|
|
return 0;
|
|
|
|
}
|
2023-03-28 17:08:51 -04:00
|
|
|
void flights_(struct flight_tree *const f) { flight_tree_(f); }
|
|
|
|
struct flight_tree flights(struct journal *const j) {
|
|
|
|
struct flight_tree f = flight_tree();
|
2023-02-02 02:18:20 -05:00
|
|
|
struct journal_iterator it;
|
2023-02-12 23:53:15 -05:00
|
|
|
union date32 date;
|
|
|
|
const char *text;
|
2023-02-02 02:18:20 -05:00
|
|
|
assert(j);
|
2023-02-12 01:18:08 -05:00
|
|
|
it = journal_iterator(j);
|
2023-02-12 23:53:15 -05:00
|
|
|
while(journal_next(&it, &date, &text)) if(!scan(&f, date, text)) goto catch;
|
2023-02-02 02:18:20 -05:00
|
|
|
goto finally;
|
|
|
|
catch:
|
|
|
|
flights_(&f);
|
|
|
|
finally:
|
|
|
|
return f;
|
|
|
|
}
|
2023-03-28 17:08:51 -04:00
|
|
|
const char *flights_to_string(const struct flight_tree *const f)
|
|
|
|
{ return flight_tree_to_string(f); }
|
|
|
|
struct flight_tree_iterator flights_iterator(struct flight_tree *const f)
|
|
|
|
{ return flight_tree_iterator(f); }
|
|
|
|
int flights_next(struct flight_tree_iterator *const it, union line64 *const k,
|
2023-02-15 02:30:57 -05:00
|
|
|
const struct flight **const v) {
|
|
|
|
assert(it && k && v);
|
2023-03-28 17:08:51 -04:00
|
|
|
if(!flight_tree_next(it)) return 0;
|
|
|
|
*k = flight_tree_key(it);
|
|
|
|
*v = flight_tree_value(it);
|
2023-02-15 02:30:57 -05:00
|
|
|
return 1;
|
2023-02-02 02:18:20 -05:00
|
|
|
}
|