Start filling in data structure in parser actions, make tariff variable

/*
   File: p1-parser.rl

   	  Ragel state-machine definition and supporting functions
   	  to parse Dutch Smart Meter P1-telegrams.

   	  (c)2017, Levien van Zon (levien at zonnetjes.net, https://github.com/lvzon)
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>

#include <time.h>

#include "logmsg.h"

#include "p1-parser.h"

long long int TST_to_time (struct parser *fsm, int arg_idx) {

	// Get TST timestamp fields from stack and create a UNIX timestamp
	// The TST fields are: YYMMDDhhmmssX, with X = W for winter time or X = S for summer time

	struct tm tm;
	time_t time;

	tm.tm_year = fsm->arg[arg_idx] + 100;	// Years since 1900, our value was years since 2000
	tm.tm_mon = fsm->arg[arg_idx + 1] - 1;	// Months since start of year, starts at 0 (for January)
	tm.tm_mday = fsm->arg[arg_idx + 2];		// Ordinal day of the month
	tm.tm_hour = fsm->arg[arg_idx + 3];		// Hours past midnight, starts at 0
	tm.tm_min = fsm->arg[arg_idx + 4];		// Minutes past the hour
	tm.tm_sec = fsm->arg[arg_idx + 5];		// Seconds past the minute

	if (fsm->arg[arg_idx + 6] == 'S')		// Daylight saving time flag
		tm.tm_isdst = 1;						// Positive for daylight saving time (summer time)
	else if (fsm->arg[arg_idx + 6] == 'W')
		tm.tm_isdst = 0;						// Zero if DST is not in effect (winter time)
	else
		tm.tm_isdst = -1;						// Negative if DST information is not available

	logmsg(LL_DEBUG, "Time: %d %d %d %d %d %d %d\n", tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_isdst);

	if (!fsm->meter_timezone)
		fsm->meter_timezone = METER_TIMEZONE;

	const char *TZ = "TZ";
	char *oldval_TZ = getenv(TZ);
	setenv(TZ, fsm->meter_timezone, 1);		// Set TZ timezone environment variable to meter timezone

	time = mktime(&tm);

	if (oldval_TZ)
		setenv(TZ, oldval_TZ, 1);			// Restore TZ timezone environment variable

	return time;
}

/* Ragel state-machine definition */

%%{
	machine parser;
	access fsm->;

	include parser "parser-tools.rl";

	rest_of_line := [^\r^\n]* [\r\n]{2} @clearargs @{ fgoto main; };	# Helper machine to parse the rest of the line in case of errors

	# Actions associated with commands

	action header {
		logmsg(LL_VERBOSE, "Header: %s\n", fsm->strarg[0]);
		strncpy((char *)(fsm->data.header), fsm->strarg[0], LEN_HEADER);
	}

	action crc {
		logmsg(LL_VERBOSE, "CRC: 0x%x\n", (unsigned int)fsm->arg[0]);
		fsm->crc16 = fsm->arg[0];
	}

	action P1_version {
		fsm->data.P1_version_major = fsm->arg[0] >> 4;
		fsm->data.P1_version_minor = fsm->arg[0] & 0xf;
		logmsg(LL_VERBOSE, "P1 version: %d.%d\n", (int)(fsm->data.P1_version_major), (int)(fsm->data.P1_version_minor));
	}

	action timestamp {
		fsm->data.timestamp = TST_to_time(fsm, 0);
		logmsg(LL_VERBOSE, "Timestamp: %lu\n", (unsigned long)(fsm->data.timestamp));
	}

	action equipment_id {
		logmsg(LL_VERBOSE, "Equipment ID: %s\n", fsm->strarg[0]);
		strncpy((char *)(fsm->data.equipment_id), fsm->strarg[0], LEN_EQUIPMENT_ID);
	}

	action tariff {
		fsm->data.tariff = fsm->arg[0];
		logmsg(LL_VERBOSE, "Tariff: %u\n", (unsigned int)(fsm->data.tariff));
	}

	action switchpos {
		fsm->data.switchpos = fsm->arg[0];
		logmsg(LL_VERBOSE, "Switch position: %d\n", (int)(fsm->data.switchpos));
	}

	action E_in {
		unsigned int tariff = fsm->arg[0];
		double value = (double)fsm->arg[1] / (double)fsm->arg[2];
		if (tariff > MAX_TARIFFS) {
			logmsg(LL_ERROR, "Tariff %u out of range, max. %u, E_in %f %s\n", tariff, MAX_TARIFFS, value, fsm->strarg[0]);
		} else {
			fsm->data.E_in[tariff] = value;
			strncpy((char *)(fsm->unit_E_in[tariff]), fsm->strarg[0], LEN_UNIT + 1);
			logmsg(LL_VERBOSE, "Energy in, tariff %u: %f %s\n", value, fsm->strarg[0]);
		}
	}

	action E_out {
		unsigned int tariff = fsm->arg[0];
		double value = (double)fsm->arg[1] / (double)fsm->arg[2];
		if (tariff > MAX_TARIFFS) {
			logmsg(LL_ERROR, "Tariff %u out of range, max. %u, E_out %f %s\n", tariff, MAX_TARIFFS, value, fsm->strarg[0]);
		} else {
			fsm->data.E_out[tariff] = value;
			strncpy((char *)(fsm->unit_E_out[tariff]), fsm->strarg[0], LEN_UNIT + 1);
			logmsg(LL_VERBOSE, "Energy out, tariff %u: %f %s\n", value, fsm->strarg[0]);
		}
	}

	action E_in_t1 { logmsg(LL_VERBOSE, "Energy in, tariff 1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_in_t2 { logmsg(LL_VERBOSE, "Energy in, tariff 2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_out_t1 { logmsg(LL_VERBOSE, "Energy out, tariff 1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_out_t2 { logmsg(LL_VERBOSE, "Energy out, tariff 2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }

	action P_in { logmsg(LL_VERBOSE, "Power in: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out { logmsg(LL_VERBOSE, "Power out: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_threshold { logmsg(LL_VERBOSE, "Power threshold: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action I_L1 { logmsg(LL_VERBOSE, "Current L1: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_L2 { logmsg(LL_VERBOSE, "Current L2: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_L3 { logmsg(LL_VERBOSE, "Current L3: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_threshold { logmsg(LL_VERBOSE, "Current threshold: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action V_L1 { logmsg(LL_VERBOSE, "Voltage L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action V_L2 { logmsg(LL_VERBOSE, "Voltage L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action V_L3 { logmsg(LL_VERBOSE, "Voltage L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L1 { logmsg(LL_VERBOSE, "Power in L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L2 { logmsg(LL_VERBOSE, "Power in L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L3 { logmsg(LL_VERBOSE, "Power in L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L1 { logmsg(LL_VERBOSE, "Power out L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L2 { logmsg(LL_VERBOSE, "Power out L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L3 { logmsg(LL_VERBOSE, "Power out L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action pfail { logmsg(LL_VERBOSE, "Power failures: %lld\n", fsm->arg[0]);}
	action longpfail { logmsg(LL_VERBOSE, "Long power failures: %lld\n", fsm->arg[0]);}
	action pfailevent { logmsg(LL_VERBOSE, "Power failure event at %lld, %lld %s\n", TST_to_time(fsm, 0), fsm->arg[7], fsm->strarg[0]);}
	action pfailevents { logmsg(LL_VERBOSE, "Power failure events: %lld\n", fsm->arg[0]);}
	action V_sags_L1 { logmsg(LL_VERBOSE, "Voltage sags L1: %lld\n", fsm->arg[0]);}
	action V_sags_L2 { logmsg(LL_VERBOSE, "Voltage sags L2: %lld\n", fsm->arg[0]);}
	action V_sags_L3 { logmsg(LL_VERBOSE, "Voltage sags L3: %lld\n", fsm->arg[0]);}
	action V_swells_L1 { logmsg(LL_VERBOSE, "Voltage swells L1: %lld\n", fsm->arg[0]);}
	action V_swells_L2 { logmsg(LL_VERBOSE, "Voltage swells L2: %lld\n", fsm->arg[0]);}
	action V_swells_L3 { logmsg(LL_VERBOSE, "Voltage swells L3: %lld\n", fsm->arg[0]);}
	action textmsgcodes { logmsg(LL_VERBOSE, "Text message codes: %s\n", fsm->strarg[0]);}
	action textmsg { logmsg(LL_VERBOSE, "Text message: %s\n", fsm->strarg[0]); }
	action dev_type { logmsg(LL_VERBOSE, "Device %lld type: %lld\n", fsm->arg[0], fsm->arg[1]);}
	action dev_id { logmsg(LL_VERBOSE, "Device %lld ID: %s\n", fsm->arg[0], fsm->strarg[0]);}
	action dev_counter { logmsg(LL_VERBOSE, "Device %lld counter at %lld: %f %s\n", fsm->arg[0], TST_to_time(fsm, 1), (double)fsm->arg[8] / (double)fsm->arg[9], fsm->strarg[0]); }
	action dev_valve { logmsg(LL_VERBOSE, "Device %lld valve position: %lld\n", fsm->arg[0], fsm->arg[1]);}
	action dev_timeseries_head { logmsg(LL_VERBOSE, "Device %lld timeseries, starting time %lld, status %lld, period %lld, values %lld:\n", fsm->arg[0], TST_to_time(fsm, 1), fsm->arg[7], fsm->arg[8], fsm->arg[9]); }
	action dev_timeseries_counter_head { logmsg(LL_VERBOSE, "counter values, unit %s\n", fsm->strarg[0]); }
	action dev_timeseries_counter_cold_head { logmsg(LL_VERBOSE, "cold counter values, unit %s\n", fsm->strarg[0]); }
	action dev_timeseries_counterval { logmsg(LL_VERBOSE, "counter value: %f\n", (double)fsm->arg[0] / (double)fsm->arg[1]); }
	action gas_id_old { logmsg(LL_VERBOSE, "Gas meter ID: %s\n", fsm->strarg[0]);}
	action gas_count_old { logmsg(LL_VERBOSE, "Gas meter counter: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action gas_valve_old { logmsg(LL_VERBOSE, "Gas meter valve position: %lld\n", fsm->arg[0]);}
	action error {logmsg(LL_VERBOSE, "Error while parsing\n"); fsm->parse_errors++ ; fhold ; fgoto rest_of_line; }

	action unknown { logmsg(LL_VERBOSE, "Unknown: %s\n", fsm->strarg[0]); fsm->strargc = 0 ; fsm->buflen = 0; }

	# Helpers that collect arguments

	digitpair = (digit @add_digit){2}  >cleararg %addarg;	# Parse and store two digits
	dst = ([SW] >cleararg @addchar);	# Parse and store daylight saving time character

	mbusid = ( [1234] @add_digit ) >cleararg %addarg;	# We can have 4 additional MBUS devices: gas meter, water meter, thermal meter, slave meter

	# Definitions of statements and parts of statements

	crlf = '\r\n';	# Lines are terminated by carriage return + line feed
	#crlf = [\r\n]{2};	# Lines are terminated by carriage return + line feed, but we'll also match some converted line ends

	fixedpoint = fpval;			# Fixed point value, stored as an integer value and an integer divider

	TST = digitpair{6} dst;
	TST_old = digitpair{6};
	unit = [* ] ([^)]+ >addstr $str_append %str_term);		# Unit separator is '*', but some meters use ' '
	timeseries_unit = [^)]+ >addstr $str_append %str_term;

	headerstr = ([^\r^\n]+ >addstr $str_append %str_term);
	#msgstr = ([^)]* >addstr $str_append %str_term);
	msgstr = hexstring;
	idstr = hexstring;

	header = '/' headerstr crlf crlf @header @clearargs;
	end = '!' hexint? crlf @crc @clearargs;		# Telegram end with optional CRC

	fixedpointval = '(' fixedpoint unit ')';
	tstval = '(' TST ')';
	tstval_old = '(' TST_old ')';

	# DSMR COSEM-objects

	P1_version = '1-3:0.2.8(' hexint ')' crlf @P1_version;		# P1 version
	timestamp = '0-0:1.0.0' tstval crlf @timestamp;			# Telegram timestamp

	equipment_id = '0-0:96.1.1(' idstr ')' crlf @equipment_id;	# Equipment ID

	E_in = '1-0:1.8.' uinteger fixedpointval crlf @E_in;	# Electricity delivered to client
	E_out = '1-0:2.8.' uinteger fixedpointval crlf @E_out;	# Electricity delivered by client

	# 1-0:1.8.0*255(0091158 kWh)

	E_in_t1 = '1-0:1.8.1' fixedpointval crlf @E_in_t1;	# Electricity delivered to client in tariff 1
	E_in_t2 = '1-0:1.8.2' fixedpointval crlf @E_in_t2;	# Electricity delivered to client in tariff 2
	E_out_t1 = '1-0:2.8.1' fixedpointval crlf @E_out_t1;	# Electricity delivered by client in tariff 1
	E_out_t2 = '1-0:2.8.2' fixedpointval crlf @E_out_t2;	# Electricity delivered by client in tariff 2

	tariff = '0-0:96.14.0(' uinteger ')' crlf @tariff;		# TODO: can be non-integer, in theory?
	switchpos = '0-0:' ('96.3.10' | '24.4.0') '(' uinteger ')' crlf @switchpos;	# Switch position electricity (in/out/enabled), absent from DSMR>=4.0.7

	P_in = '1-0:1.7.0' fixedpointval crlf @P_in;	# Actual power delivered to client
	P_out = '1-0:2.7.0' fixedpointval crlf @P_out;	# Actual power delivered by client
	P_threshold = '0-0:17.0.0' fixedpointval crlf @P_threshold;

	pfail = '0-0:96.7.21(' uinteger ')' crlf @pfail;
	longpfail = '0-0:96.7.9(' uinteger ')' crlf @longpfail;

	pfailevents = '1-0:99.97.0(' uinteger ')' @pfailevents @clearargs;	# Power failure events
	pfailevent = tstval '(' uinteger unit ')' @pfailevent @clearargs;		# Single power failure event
	pfaileventlog = pfailevents '(0-0:96.7.19)'? pfailevent* crlf;	# Power failure event log, with zero or more events

	V_sags_L1 = '1-0:32.32.0(' uinteger ')' crlf @V_sags_L1;
	V_sags_L2 = '1-0:52.32.0(' uinteger ')' crlf @V_sags_L2;
	V_sags_L3 = '1-0:72.32.0(' uinteger ')' crlf @V_sags_L3;

	V_swells_L1 = '1-0:32.36.0(' uinteger ')' crlf @V_swells_L1;
	V_swells_L2 = '1-0:52.36.0(' uinteger ')' crlf @V_swells_L2;
	V_swells_L3 = '1-0:72.36.0(' uinteger ')' crlf @V_swells_L3;

	textmsgcodes = '0-0:96.13.1(' msgstr ')' crlf @textmsgcodes;
	textmsgcodes_empty = '0-0:96.13.1()' crlf;
	textmsg = '0-0:96.13.0(' msgstr ')' crlf @textmsg;
	textmsg_empty = '0-0:96.13.0()' crlf;

	V_L1 = '1-0:32.7.0' fixedpointval crlf @V_L1;
	V_L2 = '1-0:52.7.0' fixedpointval crlf @V_L2;
	V_L3 = '1-0:72.7.0' fixedpointval crlf @V_L3;

	I_L1 = '1-0:31.7.0(' uinteger unit ')' crlf @I_L1;
	I_L2 = '1-0:51.7.0(' uinteger unit ')' crlf @I_L2;
	I_L3 = '1-0:71.7.0(' uinteger unit ')' crlf @I_L3;

	I_threshold = '0-0:17.0.0(' uinteger unit ')' crlf @I_threshold;

	P_in_L1 = '1-0:21.7.0' fixedpointval crlf @P_in_L1;
	P_in_L2 = '1-0:41.7.0' fixedpointval crlf @P_in_L2;
	P_in_L3 = '1-0:61.7.0' fixedpointval crlf @P_in_L3;

	P_out_L1 = '1-0:22.7.0' fixedpointval crlf @P_out_L1;
	P_out_L2 = '1-0:42.7.0' fixedpointval crlf @P_out_L2;
	P_out_L3 = '1-0:62.7.0' fixedpointval crlf @P_out_L3;

	dev_type = '0-' mbusid ':24.1.0(' uinteger ')' crlf @dev_type;
	dev_id = '0-' mbusid ':96.1.0(' idstr ')' crlf @dev_id;
	dev_counter = '0-' mbusid ':24.2.1' tstval fixedpointval crlf @dev_counter;
	dev_valve = '0-' mbusid ':24.4.0(' uinteger ')' crlf @dev_valve;	# Valve position (on/off/released), absent from DSMR>=4.0.7

	# This describes the rather horrible "profile generic dataset" representation used in DSMR 3.x
	dev_timeseries_head = '0-' mbusid ':24.3.0' tstval_old '(' uinteger ')' '(' uinteger ')' '(' uinteger ')' @dev_timeseries_head @clearargs;
	dev_timeseries_counter_head = '(0-' digit+ ':24.2.1)(' timeseries_unit ')' @dev_timeseries_counter_head @clearargs;
	dev_timeseries_counter_cold_head = '(0-' digit+ ':24.3.1)(' timeseries_unit ')'  @dev_timeseries_counter_cold_head @clearargs;
	dev_timeseries_counterval = '(' fixedpoint ')' @dev_timeseries_counterval @clearargs;
	dev_counter_timeseries = dev_timeseries_head dev_timeseries_counter_head dev_timeseries_counterval+ crlf;
	dev_counter_cold_timeseries = dev_timeseries_head dev_timeseries_counter_cold_head dev_timeseries_counterval+ crlf;

	gas_id_old = '7-0:0.0.0(' idstr ')' crlf @gas_id_old;
	gas_count_old = '7-0:23.1.0' tstval fixedpointval crlf @gas_count_old;
	#gas_count_Tcomp_old = '7-0:23.2.0' tstval fixedpointval crlf @gas_count_Tcomp_old;
	#gas_valve_old = '7-0:24.4.0(' uinteger ')' crlf @gas_valve_old;
	#heat_id_old = '5-0:0.0.0(' idstr ')' crlf @heat_id_old;
	#cold_id_old = '6-0:0.0.0(' idstr ')' crlf @cold_id_old;
	#water_id_old = '8-0:0.0.0(' idstr ')' crlf @water_id_old;
	#heat_count_old = '5-0:1.0.0' tstval fixedpointval crlf @heat_count_old;
	#cold_count_old = '6-0:1.0.0' tstval fixedpointval crlf @cold_count_old;
	#water_count_old = '8-0:1.0.0' tstval fixedpointval crlf @water_count_old;

	# P1 Telegram components

	metadata_object = P1_version | timestamp;
	emeter_object = equipment_id | tariff | switchpos | E_in_t1 | E_in_t2 | E_out_t1 | E_out_t2;
	power_object = P_in | P_out | P_in_L1 | P_out_L1 | P_in_L2 | P_out_L2 | P_in_L3 | P_out_L3 | P_threshold;
	current_object = I_L1 | I_L2 | I_L3 | I_threshold;
	voltage_object = V_L1 | V_L2 | V_L3;
	power_quality_object = pfail | longpfail | pfaileventlog | V_sags_L1 | V_swells_L1 | V_sags_L2 | V_swells_L2 | V_sags_L3 | V_swells_L3;
	mbusdev_object = dev_type | dev_id | dev_counter | dev_valve | dev_counter_timeseries | dev_counter_cold_timeseries;
	slavedev_legacy_object = gas_id_old | gas_count_old;
	message_object = textmsgcodes | textmsg | textmsgcodes_empty | textmsg_empty;

	object = 	metadata_object | emeter_object | power_object | current_object | voltage_object | power_quality_object |
				message_object | mbusdev_object | slavedev_legacy_object;

	line = object $err(error) @clearargs;	# Clear argument stacks at the end of each line, handle parsing errors

	telegram = header? line* end;	# Make header optional, so we can recover from errors in the middle of a telegram

	main := telegram* $err(error);		# Parse zero or more telegrams

}%%

%% write data;

void parser_init( struct parser *fsm )
{
	int arg;

	fsm->buflen = 0;
	fsm->argc = 0;
	for (arg = 0 ; arg < PARSER_MAXARGS ; arg++)
		fsm->arg[arg] = 0;
	fsm->multiplier = 1;
	fsm->bitcount = 0;
	fsm->strargc = 0;
	for (arg = 0 ; arg < PARSER_MAXARGS ; arg++)
		fsm->strarg[arg] = NULL;
	fsm->parse_errors = 0;

	%% write init;
}

void parser_execute(struct parser *fsm, const char *data, int len, int eofflag)
{
	const char *p = data;
	const char *pe = data + len;
	const char *eof = 0;

	if (eofflag)
		eof = pe;

	%% write exec;

	fsm->pe = pe;
}

int parser_finish(struct parser *fsm)
{
	if ( fsm->cs == parser_error )			// Machine failed before matching
		return -1;
	if ( fsm->cs >= parser_first_final )	// Final state reached
		return 1;
	return 0;								// Final state not reached
}

/*
   File: p1-parser.rl

   	  Ragel state-machine definition and supporting functions
   	  to parse Dutch Smart Meter P1-telegrams.

   	  (c)2017, Levien van Zon (levien at zonnetjes.net, https://github.com/lvzon)
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>

#include <time.h>

#include "logmsg.h"

#include "p1-parser.h"

long long int TST_to_time (struct parser *fsm, int arg_idx) {

	// Get TST timestamp fields from stack and create a UNIX timestamp
	// The TST fields are: YYMMDDhhmmssX, with X = W for winter time or X = S for summer time

	struct tm tm;
	time_t time;

	tm.tm_year = fsm->arg[arg_idx] + 100;	// Years since 1900, our value was years since 2000
	tm.tm_mon = fsm->arg[arg_idx + 1] - 1;	// Months since start of year, starts at 0 (for January)
	tm.tm_mday = fsm->arg[arg_idx + 2];		// Ordinal day of the month
	tm.tm_hour = fsm->arg[arg_idx + 3];		// Hours past midnight, starts at 0
	tm.tm_min = fsm->arg[arg_idx + 4];		// Minutes past the hour
	tm.tm_sec = fsm->arg[arg_idx + 5];		// Seconds past the minute

	if (fsm->arg[arg_idx + 6] == 'S')		// Daylight saving time flag
		tm.tm_isdst = 1;						// Positive for daylight saving time (summer time)
	else if (fsm->arg[arg_idx + 6] == 'W')
		tm.tm_isdst = 0;						// Zero if DST is not in effect (winter time)
	else
		tm.tm_isdst = -1;						// Negative if DST information is not available

	logmsg(LL_DEBUG, "Time: %d %d %d %d %d %d %d\n", tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_isdst);

	if (!fsm->meter_timezone)
		fsm->meter_timezone = METER_TIMEZONE;

	const char *TZ = "TZ";
	char *oldval_TZ = getenv(TZ);
	setenv(TZ, fsm->meter_timezone, 1);		// Set TZ timezone environment variable to meter timezone

	time = mktime(&tm);

	if (oldval_TZ)
		setenv(TZ, oldval_TZ, 1);			// Restore TZ timezone environment variable

	return time;
}

/* Ragel state-machine definition */

%%{
	machine parser;
	access fsm->;

	include parser "parser-tools.rl";

	rest_of_line := [^\r^\n]* [\r\n]{2} @clearargs @{ fgoto main; };	# Helper machine to parse the rest of the line in case of errors

	# Actions associated with commands

	action header { logmsg(LL_VERBOSE, "Header: %s\n", fsm->strarg[0]); }
	action crc { logmsg(LL_VERBOSE, "CRC: 0x%x\n", (unsigned int)fsm->arg[0]); fsm->crc16 = fsm->arg[0]; }
	action P1_version { logmsg(LL_VERBOSE, "P1 version: %lld.%lld\n", fsm->arg[0] >> 4, fsm->arg[0] & 0xf); }
	action timestamp { logmsg(LL_VERBOSE, "Timestamp: %lld\n", TST_to_time(fsm, 0));}
	action equipment_id { logmsg(LL_VERBOSE, "Equipment ID: %s\n", fsm->strarg[0]);}
	action tariff { logmsg(LL_VERBOSE, "Tariff: %lld\n", fsm->arg[0]);}
	action switchpos { logmsg(LL_VERBOSE, "Switch position: %lld\n", fsm->arg[0]);}
	action E_in_t1 { logmsg(LL_VERBOSE, "Energy in, tariff 1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_in_t2 { logmsg(LL_VERBOSE, "Energy in, tariff 2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_out_t1 { logmsg(LL_VERBOSE, "Energy out, tariff 1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_out_t2 { logmsg(LL_VERBOSE, "Energy out, tariff 2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in { logmsg(LL_VERBOSE, "Power in: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out { logmsg(LL_VERBOSE, "Power out: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_threshold { logmsg(LL_VERBOSE, "Power threshold: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action I_L1 { logmsg(LL_VERBOSE, "Current L1: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_L2 { logmsg(LL_VERBOSE, "Current L2: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_L3 { logmsg(LL_VERBOSE, "Current L3: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_threshold { logmsg(LL_VERBOSE, "Current threshold: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action V_L1 { logmsg(LL_VERBOSE, "Voltage L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action V_L2 { logmsg(LL_VERBOSE, "Voltage L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action V_L3 { logmsg(LL_VERBOSE, "Voltage L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L1 { logmsg(LL_VERBOSE, "Power in L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L2 { logmsg(LL_VERBOSE, "Power in L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L3 { logmsg(LL_VERBOSE, "Power in L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L1 { logmsg(LL_VERBOSE, "Power out L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L2 { logmsg(LL_VERBOSE, "Power out L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L3 { logmsg(LL_VERBOSE, "Power out L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action pfail { logmsg(LL_VERBOSE, "Power failures: %lld\n", fsm->arg[0]);}
	action longpfail { logmsg(LL_VERBOSE, "Long power failures: %lld\n", fsm->arg[0]);}
	action pfailevent { logmsg(LL_VERBOSE, "Power failure event at %lld, %lld %s\n", TST_to_time(fsm, 0), fsm->arg[7], fsm->strarg[0]);}
	action pfailevents { logmsg(LL_VERBOSE, "Power failure events: %lld\n", fsm->arg[0]);}
	action V_sags_L1 { logmsg(LL_VERBOSE, "Voltage sags L1: %lld\n", fsm->arg[0]);}
	action V_sags_L2 { logmsg(LL_VERBOSE, "Voltage sags L2: %lld\n", fsm->arg[0]);}
	action V_sags_L3 { logmsg(LL_VERBOSE, "Voltage sags L3: %lld\n", fsm->arg[0]);}
	action V_swells_L1 { logmsg(LL_VERBOSE, "Voltage swells L1: %lld\n", fsm->arg[0]);}
	action V_swells_L2 { logmsg(LL_VERBOSE, "Voltage swells L2: %lld\n", fsm->arg[0]);}
	action V_swells_L3 { logmsg(LL_VERBOSE, "Voltage swells L3: %lld\n", fsm->arg[0]);}
	action textmsgcodes { logmsg(LL_VERBOSE, "Text message codes: %s\n", fsm->strarg[0]);}
	action textmsg { logmsg(LL_VERBOSE, "Text message: %s\n", fsm->strarg[0]); }
	action dev_type { logmsg(LL_VERBOSE, "Device %lld type: %lld\n", fsm->arg[0], fsm->arg[1]);}
	action dev_id { logmsg(LL_VERBOSE, "Device %lld ID: %s\n", fsm->arg[0], fsm->strarg[0]);}
	action dev_counter { logmsg(LL_VERBOSE, "Device %lld counter at %lld: %f %s\n", fsm->arg[0], TST_to_time(fsm, 1), (double)fsm->arg[8] / (double)fsm->arg[9], fsm->strarg[0]); }
	action dev_valve { logmsg(LL_VERBOSE, "Device %lld valve position: %lld\n", fsm->arg[0], fsm->arg[1]);}
	action dev_timeseries_head { logmsg(LL_VERBOSE, "Device %lld timeseries, starting time %lld, status %lld, period %lld, values %lld:\n", fsm->arg[0], TST_to_time(fsm, 1), fsm->arg[7], fsm->arg[8], fsm->arg[9]); }
	action dev_timeseries_counter_head { logmsg(LL_VERBOSE, "counter values, unit %s\n", fsm->strarg[0]); }
	action dev_timeseries_counter_cold_head { logmsg(LL_VERBOSE, "cold counter values, unit %s\n", fsm->strarg[0]); }
	action dev_timeseries_counterval { logmsg(LL_VERBOSE, "counter value: %f\n", (double)fsm->arg[0] / (double)fsm->arg[1]); }
	action gas_id_old { logmsg(LL_VERBOSE, "Gas meter ID: %s\n", fsm->strarg[0]);}
	action gas_count_old { logmsg(LL_VERBOSE, "Gas meter counter: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action gas_valve_old { logmsg(LL_VERBOSE, "Gas meter valve position: %lld\n", fsm->arg[0]);}
	action error {logmsg(LL_VERBOSE, "Error while parsing\n"); fsm->parse_errors++ ; fhold ; fgoto rest_of_line; }

	action unknown { logmsg(LL_VERBOSE, "Unknown: %s\n", fsm->strarg[0]); fsm->strargc = 0 ; fsm->buflen = 0; }

	# Helpers that collect arguments

	digitpair = (digit @add_digit){2}  >cleararg %addarg;	# Parse and store two digits
	dst = ([SW] >cleararg @addchar);	# Parse and store daylight saving time character

	mbusid = ( [1234] @add_digit ) >cleararg %addarg;	# We can have 4 additional MBUS devices: gas meter, water meter, thermal meter, slave meter

	# Definitions of statements and parts of statements

	crlf = '\r\n';	# Lines are terminated by carriage return + line feed
	#crlf = [\r\n]{2};	# Lines are terminated by carriage return + line feed, but we'll also match some converted line ends

	fixedpoint = fpval;			# Fixed point value, stored as an integer value and an integer divider

	TST = digitpair{6} dst;
	TST_old = digitpair{6};
	unit = '*' ([^)]+ >addstr $str_append %str_term);
	timeseries_unit = [^)]+ >addstr $str_append %str_term;

	headerstr = ([^\r^\n]+ >addstr $str_append %str_term);
	#msgstr = ([^)]* >addstr $str_append %str_term);
	msgstr = hexstring;
	idstr = hexstring;

	header = '/' headerstr crlf crlf @header @clearargs;
	end = '!' hexint? crlf @crc @clearargs;		# Telegram end with optional CRC

	fixedpointval = '(' fixedpoint unit ')';
	tstval = '(' TST ')';
	tstval_old = '(' TST_old ')';

	# DSMR COSEM-objects

	P1_version = '1-3:0.2.8(' hexint ')' crlf @P1_version;		# P1 version
	timestamp = '0-0:1.0.0' tstval crlf @timestamp;			# Telegram timestamp

	equipment_id = '0-0:96.1.1(' idstr ')' crlf @equipment_id;	# Equipment ID

	E_in_t1 = '1-0:1.8.1' fixedpointval crlf @E_in_t1;	# Electricity delivered to client in tariff 1
	E_in_t2 = '1-0:1.8.2' fixedpointval crlf @E_in_t2;	# Electricity delivered to client in tariff 2
	E_out_t1 = '1-0:2.8.1' fixedpointval crlf @E_out_t1;	# Electricity delivered by client in tariff 1
	E_out_t2 = '1-0:2.8.2' fixedpointval crlf @E_out_t2;	# Electricity delivered by client in tariff 2
	tariff = '0-0:96.14.0(' uinteger ')' crlf @tariff;		# TODO: can be non-integer, in theory?
	switchpos = '0-0:' ('96.3.10' | '24.4.0') '(' uinteger ')' crlf @switchpos;	# Switch position electricity (in/out/enabled), absent from DSMR>=4.0.7

	P_in = '1-0:1.7.0' fixedpointval crlf @P_in;	# Actual power delivered to client
	P_out = '1-0:2.7.0' fixedpointval crlf @P_out;	# Actual power delivered by client
	P_threshold = '0-0:17.0.0' fixedpointval crlf @P_threshold;

	pfail = '0-0:96.7.21(' uinteger ')' crlf @pfail;
	longpfail = '0-0:96.7.9(' uinteger ')' crlf @longpfail;

	pfailevents = '1-0:99.97.0(' uinteger ')' @pfailevents @clearargs;	# Power failure events
	pfailevent = tstval '(' uinteger unit ')' @pfailevent @clearargs;		# Single power failure event
	pfaileventlog = pfailevents '(0-0:96.7.19)'? pfailevent* crlf;	# Power failure event log, with zero or more events

	V_sags_L1 = '1-0:32.32.0(' uinteger ')' crlf @V_sags_L1;
	V_sags_L2 = '1-0:52.32.0(' uinteger ')' crlf @V_sags_L2;
	V_sags_L3 = '1-0:72.32.0(' uinteger ')' crlf @V_sags_L3;

	V_swells_L1 = '1-0:32.36.0(' uinteger ')' crlf @V_swells_L1;
	V_swells_L2 = '1-0:52.36.0(' uinteger ')' crlf @V_swells_L2;
	V_swells_L3 = '1-0:72.36.0(' uinteger ')' crlf @V_swells_L3;

	textmsgcodes = '0-0:96.13.1(' msgstr ')' crlf @textmsgcodes;
	textmsgcodes_empty = '0-0:96.13.1()' crlf;
	textmsg = '0-0:96.13.0(' msgstr ')' crlf @textmsg;
	textmsg_empty = '0-0:96.13.0()' crlf;

	V_L1 = '1-0:32.7.0' fixedpointval crlf @V_L1;
	V_L2 = '1-0:52.7.0' fixedpointval crlf @V_L2;
	V_L3 = '1-0:72.7.0' fixedpointval crlf @V_L3;

	I_L1 = '1-0:31.7.0(' uinteger unit ')' crlf @I_L1;
	I_L2 = '1-0:51.7.0(' uinteger unit ')' crlf @I_L2;
	I_L3 = '1-0:71.7.0(' uinteger unit ')' crlf @I_L3;

	I_threshold = '0-0:17.0.0(' uinteger unit ')' crlf @I_threshold;

	P_in_L1 = '1-0:21.7.0' fixedpointval crlf @P_in_L1;
	P_in_L2 = '1-0:41.7.0' fixedpointval crlf @P_in_L2;
	P_in_L3 = '1-0:61.7.0' fixedpointval crlf @P_in_L3;

	P_out_L1 = '1-0:22.7.0' fixedpointval crlf @P_out_L1;
	P_out_L2 = '1-0:42.7.0' fixedpointval crlf @P_out_L2;
	P_out_L3 = '1-0:62.7.0' fixedpointval crlf @P_out_L3;

	dev_type = '0-' mbusid ':24.1.0(' uinteger ')' crlf @dev_type;
	dev_id = '0-' mbusid ':96.1.0(' idstr ')' crlf @dev_id;
	dev_counter = '0-' mbusid ':24.2.1' tstval fixedpointval crlf @dev_counter;
	dev_valve = '0-' mbusid ':24.4.0(' uinteger ')' crlf @dev_valve;	# Valve position (on/off/released), absent from DSMR>=4.0.7

	# This describes the rather horrible "profile generic dataset" representation used in DSMR 3.x
	dev_timeseries_head = '0-' mbusid ':24.3.0' tstval_old '(' uinteger ')' '(' uinteger ')' '(' uinteger ')' @dev_timeseries_head @clearargs;
	dev_timeseries_counter_head = '(0-' digit+ ':24.2.1)(' timeseries_unit ')' @dev_timeseries_counter_head @clearargs;
	dev_timeseries_counter_cold_head = '(0-' digit+ ':24.3.1)(' timeseries_unit ')'  @dev_timeseries_counter_cold_head @clearargs;
	dev_timeseries_counterval = '(' fixedpoint ')' @dev_timeseries_counterval @clearargs;
	dev_counter_timeseries = dev_timeseries_head dev_timeseries_counter_head dev_timeseries_counterval+ crlf;
	dev_counter_cold_timeseries = dev_timeseries_head dev_timeseries_counter_cold_head dev_timeseries_counterval+ crlf;

	gas_id_old = '7-0:0.0.0(' idstr ')' crlf @gas_id_old;
	gas_count_old = '7-0:23.1.0' tstval fixedpointval crlf @gas_count_old;
	#gas_count_Tcomp_old = '7-0:23.2.0' tstval fixedpointval crlf @gas_count_Tcomp_old;
	#gas_valve_old = '7-0:24.4.0(' uinteger ')' crlf @gas_valve_old;
	#heat_id_old = '5-0:0.0.0(' idstr ')' crlf @heat_id_old;
	#cold_id_old = '6-0:0.0.0(' idstr ')' crlf @cold_id_old;
	#water_id_old = '8-0:0.0.0(' idstr ')' crlf @water_id_old;
	#heat_count_old = '5-0:1.0.0' tstval fixedpointval crlf @heat_count_old;
	#cold_count_old = '6-0:1.0.0' tstval fixedpointval crlf @cold_count_old;
	#water_count_old = '8-0:1.0.0' tstval fixedpointval crlf @water_count_old;

	# P1 Telegram components

	metadata_object = P1_version | timestamp;
	emeter_object = equipment_id | tariff | switchpos | E_in_t1 | E_in_t2 | E_out_t1 | E_out_t2;
	power_object = P_in | P_out | P_in_L1 | P_out_L1 | P_in_L2 | P_out_L2 | P_in_L3 | P_out_L3 | P_threshold;
	current_object = I_L1 | I_L2 | I_L3 | I_threshold;
	voltage_object = V_L1 | V_L2 | V_L3;
	power_quality_object = pfail | longpfail | pfaileventlog | V_sags_L1 | V_swells_L1 | V_sags_L2 | V_swells_L2 | V_sags_L3 | V_swells_L3;
	mbusdev_object = dev_type | dev_id | dev_counter | dev_valve | dev_counter_timeseries | dev_counter_cold_timeseries;
	slavedev_legacy_object = gas_id_old | gas_count_old;
	message_object = textmsgcodes | textmsg | textmsgcodes_empty | textmsg_empty;

	object = 	metadata_object | emeter_object | power_object | current_object | voltage_object | power_quality_object |
				message_object | mbusdev_object | slavedev_legacy_object;

	line = object $err(error) @clearargs;	# Clear argument stacks at the end of each line, handle parsing errors

	telegram = header? line* end;	# Make header optional, so we can recover from errors in the middle of a telegram

	main := telegram* $err(error);		# Parse zero or more telegrams

}%%

%% write data;

void parser_init( struct parser *fsm )
{
	int arg;

	fsm->buflen = 0;
	fsm->argc = 0;
	for (arg = 0 ; arg < PARSER_MAXARGS ; arg++)
		fsm->arg[arg] = 0;
	fsm->multiplier = 1;
	fsm->bitcount = 0;
	fsm->strargc = 0;
	for (arg = 0 ; arg < PARSER_MAXARGS ; arg++)
		fsm->strarg[arg] = NULL;
	fsm->parse_errors = 0;

	%% write init;
}

void parser_execute(struct parser *fsm, const char *data, int len, int eofflag)
{
	const char *p = data;
	const char *pe = data + len;
	const char *eof = 0;

	if (eofflag)
		eof = pe;

	%% write exec;

	fsm->pe = pe;
}

int parser_finish(struct parser *fsm)
{
	if ( fsm->cs == parser_error )			// Machine failed before matching
		return -1;
	if ( fsm->cs >= parser_first_final )	// Final state reached
		return 1;
	return 0;								// Final state not reached
}


Modify parser to process a slightly more general subset of IEC 62056-21

- Allow IEC 62056-21 billing period specifier in equipment ID and energy
counter values
- Allow space as unit separator (used in some non-conforming IEC
62056-21 Iskra-meters)
- Allow IEC 62056-21 equipment ID (0.0.0)

/*
   File: p1-parser.rl

   	  Ragel state-machine definition and supporting functions
   	  to parse Dutch Smart Meter P1-telegrams (and a subset of generic IEC 62056-21 smart meter telegrams).

   	  (c)2017-2018, Levien van Zon (levien at zonnetjes.net, https://github.com/lvzon)
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>

#include <time.h>

#include "logmsg.h"

#include "p1-parser.h"

long long int TST_to_time (struct parser *fsm, int arg_idx) {

	// Get TST timestamp fields from stack and create a UNIX timestamp
	// The TST fields are: YYMMDDhhmmssX, with X = W for winter time or X = S for summer time

	struct tm tm;
	time_t time;

	tm.tm_year = fsm->arg[arg_idx] + 100;	// Years since 1900, our value was years since 2000
	tm.tm_mon = fsm->arg[arg_idx + 1] - 1;	// Months since start of year, starts at 0 (for January)
	tm.tm_mday = fsm->arg[arg_idx + 2];		// Ordinal day of the month
	tm.tm_hour = fsm->arg[arg_idx + 3];		// Hours past midnight, starts at 0
	tm.tm_min = fsm->arg[arg_idx + 4];		// Minutes past the hour
	tm.tm_sec = fsm->arg[arg_idx + 5];		// Seconds past the minute

	if (fsm->arg[arg_idx + 6] == 'S')		// Daylight saving time flag
		tm.tm_isdst = 1;						// Positive for daylight saving time (summer time)
	else if (fsm->arg[arg_idx + 6] == 'W')
		tm.tm_isdst = 0;						// Zero if DST is not in effect (winter time)
	else
		tm.tm_isdst = -1;						// Negative if DST information is not available

	logmsg(LL_DEBUG, "Time: %d %d %d %d %d %d %d\n", tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_isdst);

	if (!fsm->meter_timezone)
		fsm->meter_timezone = METER_TIMEZONE;

	const char *TZ = "TZ";
	char *oldval_TZ = getenv(TZ);
	setenv(TZ, fsm->meter_timezone, 1);		// Set TZ timezone environment variable to meter timezone

	time = mktime(&tm);

	if (oldval_TZ)
		setenv(TZ, oldval_TZ, 1);			// Restore TZ timezone environment variable

	return time;
}

/* Ragel state-machine definition */

%%{
	machine parser;
	access fsm->;

	include parser "parser-tools.rl";

	rest_of_line := [^\r^\n]* [\r\n]{2} @clearargs @{ fgoto main; };	# Helper machine to parse the rest of the line in case of errors

	# Actions associated with commands

	action header { logmsg(LL_VERBOSE, "Header: %s\n", fsm->strarg[0]); }
	action crc { logmsg(LL_VERBOSE, "CRC: 0x%x\n", (unsigned int)fsm->arg[0]); fsm->crc16 = fsm->arg[0]; }
	action P1_version { logmsg(LL_VERBOSE, "P1 version: %lld.%lld\n", fsm->arg[0] >> 4, fsm->arg[0] & 0xf); }
	action timestamp { logmsg(LL_VERBOSE, "Timestamp: %lld\n", TST_to_time(fsm, 0));}
	action equipment_id { logmsg(LL_VERBOSE, "Equipment ID: %s\n", fsm->strarg[0]);}
	action tariff { logmsg(LL_VERBOSE, "Tariff: %lld\n", fsm->arg[0]);}
	action switchpos { logmsg(LL_VERBOSE, "Switch position: %lld\n", fsm->arg[0]);}
	action E_in_t1 { logmsg(LL_VERBOSE, "Energy in, tariff 1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_in_t2 { logmsg(LL_VERBOSE, "Energy in, tariff 2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_out_t1 { logmsg(LL_VERBOSE, "Energy out, tariff 1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action E_out_t2 { logmsg(LL_VERBOSE, "Energy out, tariff 2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in { logmsg(LL_VERBOSE, "Power in: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out { logmsg(LL_VERBOSE, "Power out: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_threshold { logmsg(LL_VERBOSE, "Power threshold: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action I_L1 { logmsg(LL_VERBOSE, "Current L1: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_L2 { logmsg(LL_VERBOSE, "Current L2: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_L3 { logmsg(LL_VERBOSE, "Current L3: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action I_threshold { logmsg(LL_VERBOSE, "Current threshold: %lld %s\n", fsm->arg[0], fsm->strarg[0]); }
	action V_L1 { logmsg(LL_VERBOSE, "Voltage L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action V_L2 { logmsg(LL_VERBOSE, "Voltage L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action V_L3 { logmsg(LL_VERBOSE, "Voltage L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L1 { logmsg(LL_VERBOSE, "Power in L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L2 { logmsg(LL_VERBOSE, "Power in L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_in_L3 { logmsg(LL_VERBOSE, "Power in L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L1 { logmsg(LL_VERBOSE, "Power out L1: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L2 { logmsg(LL_VERBOSE, "Power out L2: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action P_out_L3 { logmsg(LL_VERBOSE, "Power out L3: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action pfail { logmsg(LL_VERBOSE, "Power failures: %lld\n", fsm->arg[0]);}
	action longpfail { logmsg(LL_VERBOSE, "Long power failures: %lld\n", fsm->arg[0]);}
	action pfailevent { logmsg(LL_VERBOSE, "Power failure event at %lld, %lld %s\n", TST_to_time(fsm, 0), fsm->arg[7], fsm->strarg[0]);}
	action pfailevents { logmsg(LL_VERBOSE, "Power failure events: %lld\n", fsm->arg[0]);}
	action V_sags_L1 { logmsg(LL_VERBOSE, "Voltage sags L1: %lld\n", fsm->arg[0]);}
	action V_sags_L2 { logmsg(LL_VERBOSE, "Voltage sags L2: %lld\n", fsm->arg[0]);}
	action V_sags_L3 { logmsg(LL_VERBOSE, "Voltage sags L3: %lld\n", fsm->arg[0]);}
	action V_swells_L1 { logmsg(LL_VERBOSE, "Voltage swells L1: %lld\n", fsm->arg[0]);}
	action V_swells_L2 { logmsg(LL_VERBOSE, "Voltage swells L2: %lld\n", fsm->arg[0]);}
	action V_swells_L3 { logmsg(LL_VERBOSE, "Voltage swells L3: %lld\n", fsm->arg[0]);}
	action textmsgcodes { logmsg(LL_VERBOSE, "Text message codes: %s\n", fsm->strarg[0]);}
	action textmsg { logmsg(LL_VERBOSE, "Text message: %s\n", fsm->strarg[0]); }
	action dev_type { logmsg(LL_VERBOSE, "Device %lld type: %lld\n", fsm->arg[0], fsm->arg[1]);}
	action dev_id { logmsg(LL_VERBOSE, "Device %lld ID: %s\n", fsm->arg[0], fsm->strarg[0]);}
	action dev_counter { logmsg(LL_VERBOSE, "Device %lld counter at %lld: %f %s\n", fsm->arg[0], TST_to_time(fsm, 1), (double)fsm->arg[8] / (double)fsm->arg[9], fsm->strarg[0]); }
	action dev_valve { logmsg(LL_VERBOSE, "Device %lld valve position: %lld\n", fsm->arg[0], fsm->arg[1]);}
	action dev_timeseries_head { logmsg(LL_VERBOSE, "Device %lld timeseries, starting time %lld, status %lld, period %lld, values %lld:\n", fsm->arg[0], TST_to_time(fsm, 1), fsm->arg[7], fsm->arg[8], fsm->arg[9]); }
	action dev_timeseries_counter_head { logmsg(LL_VERBOSE, "counter values, unit %s\n", fsm->strarg[0]); }
	action dev_timeseries_counter_cold_head { logmsg(LL_VERBOSE, "cold counter values, unit %s\n", fsm->strarg[0]); }
	action dev_timeseries_counterval { logmsg(LL_VERBOSE, "counter value: %f\n", (double)fsm->arg[0] / (double)fsm->arg[1]); }
	action gas_id_old { logmsg(LL_VERBOSE, "Gas meter ID: %s\n", fsm->strarg[0]);}
	action gas_count_old { logmsg(LL_VERBOSE, "Gas meter counter: %f %s\n", (double)fsm->arg[0] / (double)fsm->arg[1], fsm->strarg[0]); }
	action gas_valve_old { logmsg(LL_VERBOSE, "Gas meter valve position: %lld\n", fsm->arg[0]);}
	action error {logmsg(LL_VERBOSE, "Error while parsing\n"); fsm->parse_errors++ ; fhold ; fgoto rest_of_line; }

	action unknown { logmsg(LL_VERBOSE, "Unknown: %s\n", fsm->strarg[0]); fsm->strargc = 0 ; fsm->buflen = 0; }

	# Helpers that collect arguments

	digitpair = (digit @add_digit){2}  >cleararg %addarg;	# Parse and store two digits
	dst = ([SW] >cleararg @addchar);	# Parse and store daylight saving time character

	mbusid = ( [1234] @add_digit ) >cleararg %addarg;	# We can have 4 additional MBUS devices: gas meter, water meter, thermal meter, slave meter

	# Definitions of statements and parts of statements

	crlf = '\r\n';	# Lines are terminated by carriage return + line feed
	#crlf = [\r\n]{2};	# Lines are terminated by carriage return + line feed, but we'll also match some converted line ends

	fixedpoint = fpval;			# Fixed point value, stored as an integer value and an integer divider

	TST = digitpair{6} dst;
	TST_old = digitpair{6};
	unit = [* ] ([^)]+ >addstr $str_append %str_term);	# Formally only '*' is a valid unit separator, but some meters use a space (and thus put the unit in the value string)
	timeseries_unit = [^)]+ >addstr $str_append %str_term;

	billing_period = ('*' digit+)?;		# The billing period specifier is part of IEC 62056-21, but currently isn't present in DSMR telegrams, so we make it optional

	headerstr = ([^\r^\n]+ >addstr $str_append %str_term);
	#msgstr = ([^)]* >addstr $str_append %str_term);
	msgstr = hexstring;
	idstr = hexstring;

	header = '/' headerstr crlf crlf @header @clearargs;
	end = '!' hexint? crlf @crc @clearargs;		# Telegram end with optional CRC

	fixedpointval = '(' fixedpoint unit ')';	# The value can be either integer or non-integer
	tstval = '(' TST ')';
	tstval_old = '(' TST_old ')';

	# COSEM-objects supported by DSMR

	P1_version = '1-3:0.2.8(' hexint ')' crlf @P1_version;		# P1 version
	timestamp = '0-0:1.0.0' tstval crlf @timestamp;			# Telegram timestamp

	equipment_id_p1 = '0-0:96.1.1' billing_period '(' idstr ')' crlf @equipment_id;	# Equipment ID in P1-meters
	equipment_id_iec = digit '-0:0.0.0' billing_period '(' uinteger ')' crlf @equipment_id;	# Equipment ID in IEC 62056-21 meters

	E_in_t1 = '1-0:1.8.1' billing_period fixedpointval crlf @E_in_t1;	# Electricity delivered to client in tariff 1
	E_in_t2 = '1-0:1.8.2' billing_period fixedpointval crlf @E_in_t2;	# Electricity delivered to client in tariff 2
	E_out_t1 = '1-0:2.8.1' billing_period fixedpointval crlf @E_out_t1;	# Electricity delivered by client in tariff 1
	E_out_t2 = '1-0:2.8.2' billing_period fixedpointval crlf @E_out_t2;	# Electricity delivered by client in tariff 2
	tariff = '0-0:96.14.0(' uinteger ')' crlf @tariff;		# TODO: can be non-integer, in theory?
	switchpos = '0-0:' ('96.3.10' | '24.4.0') '(' uinteger ')' crlf @switchpos;	# Switch position electricity (in/out/enabled), absent from DSMR>=4.0.7

	P_in = '1-0:1.7.0' fixedpointval crlf @P_in;	# Actual power delivered to client
	P_out = '1-0:2.7.0' fixedpointval crlf @P_out;	# Actual power delivered by client
	P_threshold = '0-0:17.0.0' fixedpointval crlf @P_threshold;

	pfail = '0-0:96.7.21(' uinteger ')' crlf @pfail;
	longpfail = '0-0:96.7.9(' uinteger ')' crlf @longpfail;

	pfailevents = '1-0:99.97.0(' uinteger ')' @pfailevents @clearargs;	# Power failure events
	pfailevent = tstval '(' uinteger unit ')' @pfailevent @clearargs;		# Single power failure event
	pfaileventlog = pfailevents '(0-0:96.7.19)'? pfailevent* crlf;	# Power failure event log, with zero or more events

	V_sags_L1 = '1-0:32.32.0(' uinteger ')' crlf @V_sags_L1;
	V_sags_L2 = '1-0:52.32.0(' uinteger ')' crlf @V_sags_L2;
	V_sags_L3 = '1-0:72.32.0(' uinteger ')' crlf @V_sags_L3;

	V_swells_L1 = '1-0:32.36.0(' uinteger ')' crlf @V_swells_L1;
	V_swells_L2 = '1-0:52.36.0(' uinteger ')' crlf @V_swells_L2;
	V_swells_L3 = '1-0:72.36.0(' uinteger ')' crlf @V_swells_L3;

	textmsgcodes = '0-0:96.13.1(' msgstr ')' crlf @textmsgcodes;
	textmsgcodes_empty = '0-0:96.13.1()' crlf;
	textmsg = '0-0:96.13.0(' msgstr ')' crlf @textmsg;
	textmsg_empty = '0-0:96.13.0()' crlf;

	V_L1 = '1-0:32.7.0' fixedpointval crlf @V_L1;
	V_L2 = '1-0:52.7.0' fixedpointval crlf @V_L2;
	V_L3 = '1-0:72.7.0' fixedpointval crlf @V_L3;

	I_L1 = '1-0:31.7.0(' uinteger unit ')' crlf @I_L1;
	I_L2 = '1-0:51.7.0(' uinteger unit ')' crlf @I_L2;
	I_L3 = '1-0:71.7.0(' uinteger unit ')' crlf @I_L3;

	I_threshold = '0-0:17.0.0(' uinteger unit ')' crlf @I_threshold;

	P_in_L1 = '1-0:21.7.0' fixedpointval crlf @P_in_L1;
	P_in_L2 = '1-0:41.7.0' fixedpointval crlf @P_in_L2;
	P_in_L3 = '1-0:61.7.0' fixedpointval crlf @P_in_L3;

	P_out_L1 = '1-0:22.7.0' fixedpointval crlf @P_out_L1;
	P_out_L2 = '1-0:42.7.0' fixedpointval crlf @P_out_L2;
	P_out_L3 = '1-0:62.7.0' fixedpointval crlf @P_out_L3;

	dev_type = '0-' mbusid ':24.1.0(' uinteger ')' crlf @dev_type;
	dev_id = '0-' mbusid ':96.1.0(' idstr ')' crlf @dev_id;
	dev_counter = '0-' mbusid ':24.2.1' tstval fixedpointval crlf @dev_counter;
	dev_valve = '0-' mbusid ':24.4.0(' uinteger ')' crlf @dev_valve;	# Valve position (on/off/released), absent from DSMR>=4.0.7

	# This describes the rather horrible "profile generic dataset" representation used in DSMR 3.x
	dev_timeseries_head = '0-' mbusid ':24.3.0' tstval_old '(' uinteger ')' '(' uinteger ')' '(' uinteger ')' @dev_timeseries_head @clearargs;
	dev_timeseries_counter_head = '(0-' digit+ ':24.2.1)(' timeseries_unit ')' @dev_timeseries_counter_head @clearargs;
	dev_timeseries_counter_cold_head = '(0-' digit+ ':24.3.1)(' timeseries_unit ')'  @dev_timeseries_counter_cold_head @clearargs;
	dev_timeseries_counterval = '(' fixedpoint ')' @dev_timeseries_counterval @clearargs;
	dev_counter_timeseries = dev_timeseries_head dev_timeseries_counter_head dev_timeseries_counterval+ crlf;
	dev_counter_cold_timeseries = dev_timeseries_head dev_timeseries_counter_cold_head dev_timeseries_counterval+ crlf;

	gas_id_old = '7-0:0.0.0(' idstr ')' crlf @gas_id_old;
	gas_count_old = '7-0:23.1.0' tstval fixedpointval crlf @gas_count_old;
	#gas_count_Tcomp_old = '7-0:23.2.0' tstval fixedpointval crlf @gas_count_Tcomp_old;
	#gas_valve_old = '7-0:24.4.0(' uinteger ')' crlf @gas_valve_old;
	#heat_id_old = '5-0:0.0.0(' idstr ')' crlf @heat_id_old;
	#cold_id_old = '6-0:0.0.0(' idstr ')' crlf @cold_id_old;
	#water_id_old = '8-0:0.0.0(' idstr ')' crlf @water_id_old;
	#heat_count_old = '5-0:1.0.0' tstval fixedpointval crlf @heat_count_old;
	#cold_count_old = '6-0:1.0.0' tstval fixedpointval crlf @cold_count_old;
	#water_count_old = '8-0:1.0.0' tstval fixedpointval crlf @water_count_old;

	# P1 Telegram components

	equipment_id = equipment_id_p1 | equipment_id_iec;
	metadata_object = P1_version | timestamp;
	emeter_object = equipment_id | tariff | switchpos | E_in_t1 | E_in_t2 | E_out_t1 | E_out_t2;
	power_object = P_in | P_out | P_in_L1 | P_out_L1 | P_in_L2 | P_out_L2 | P_in_L3 | P_out_L3 | P_threshold;
	current_object = I_L1 | I_L2 | I_L3 | I_threshold;
	voltage_object = V_L1 | V_L2 | V_L3;
	power_quality_object = pfail | longpfail | pfaileventlog | V_sags_L1 | V_swells_L1 | V_sags_L2 | V_swells_L2 | V_sags_L3 | V_swells_L3;
	mbusdev_object = dev_type | dev_id | dev_counter | dev_valve | dev_counter_timeseries | dev_counter_cold_timeseries;
	slavedev_legacy_object = gas_id_old | gas_count_old;
	message_object = textmsgcodes | textmsg | textmsgcodes_empty | textmsg_empty;

	object = 	metadata_object | emeter_object | power_object | current_object | voltage_object | power_quality_object |
				message_object | mbusdev_object | slavedev_legacy_object;

	line = object $err(error) @clearargs;	# Clear argument stacks at the end of each line, handle parsing errors

	telegram = header? line* end;	# Make header optional, so we can recover from errors in the middle of a telegram

	main := telegram* $err(error);		# Parse zero or more telegrams

}%%

%% write data;

void parser_init( struct parser *fsm )
{
	int arg;

	fsm->buflen = 0;
	fsm->argc = 0;
	for (arg = 0 ; arg < PARSER_MAXARGS ; arg++)
		fsm->arg[arg] = 0;
	fsm->multiplier = 1;
	fsm->bitcount = 0;
	fsm->strargc = 0;
	for (arg = 0 ; arg < PARSER_MAXARGS ; arg++)
		fsm->strarg[arg] = NULL;
	fsm->parse_errors = 0;

	%% write init;
}

void parser_execute(struct parser *fsm, const char *data, int len, int eofflag)
{
	const char *p = data;
	const char *pe = data + len;
	const char *eof = 0;

	if (eofflag)
		eof = pe;

	%% write exec;

	fsm->pe = pe;
}

int parser_finish(struct parser *fsm)
{
	if ( fsm->cs == parser_error )			// Machine failed before matching
		return -1;
	if ( fsm->cs >= parser_first_final )	// Final state reached
		return 1;
	return 0;								// Final state not reached
}


* fix a bug (eval_appmacro)

* fix a bug (eval_appmacro)

(%
 % rowl - generation 1
 % Copyright (C) 2010 nineties
 %
 % $Id: eval.rl 2010-05-26 01:10:04 nineties $
 %);

include(stddef,code);
export(init_evaluator, assign, deref, check_arity, eval_sexp);

symbol_map: NULL; (% (symbol name, scope id) -> symbol object %);

scopeid: 0;
scopeid_stack: NULL;

(% p0: (symbol name, scope id) %);
symbol_hash: (p0) {
    return strhash(p0[0])*3 + p0[1];
};

symbol_equal: (p0, p1) {
    if (p0[1] != p1[1]) { return FALSE; };
    return streq(p0[0], p1[0]);
};

scope_push: () {
    scopeid = scopeid + 1;
    vec_pushback(scopeid_stack, scopeid);
};

scope_pop: () {
    vec_popback(scopeid_stack);
};

(% p0: symbol name, p1: value %);
assign: (p0, p1) {
    allocate(1);
    x0 = vec_at(scopeid_stack, vec_size(scopeid_stack)-1);
    map_add(symbol_map, mktup2(p0, x0), p1);
};

(% p0: symbol object %);
deref: (p0) {
    allocate(4);
    x0 = vec_size(scopeid_stack)-1;
    x3 = sym_name(p0);
    while (x0 >= 0) {
        x1 = vec_at(scopeid_stack, x0); (% scopeid-id %);
        x2 = map_find(symbol_map, mktup2(x3, x1));
        if (x2 != NULL) { return x2; };
        x0 = x0 - 1;
    };
    return p0;
};

check_arity: (p0, p1, p2) {
    if (length(p0) != p1) {
        fputs(stderr, "ERROR '");
        fputs(stderr, p2);
        fputs(stderr, "': required ");
        fputi(stderr, p1);
        fputs(stderr, " arguments");
        exit(1);
    }
};

eval_args: (p0) {
    if (p0 == nil_sym) { return nil_sym; };
    return mkcons(eval_sexp(car(p0)), eval_args(cdr(p0)));
};

eval_cons: (p0) {
    allocate(2);
    x0 = car(p0);
    if (sym_p(x0) == nil_sym) { goto &eval_cons_error; };
    x1 = eval_sexp(x0);
    if (x1 == var_sym)    { return eval_var(cdr(p0)); };
    if (x1 == set_sym)    { return eval_set(cdr(p0)); };
    if (x1 == quote_sym)  { return eval_quote(cdr(p0)); };
    if (x1 == if_sym)     { return eval_if(cdr(p0)); };
    if (x1 == cond_sym)   { return eval_cond(cdr(p0)); };
    if (x1 == while_sym)  { return eval_while(cdr(p0)); };
    if (x1 == do_sym)     { return eval_do(cdr(p0)); };
    if (x1 == lambda_sym) { return eval_lambda(cdr(p0)); };
    if (x1 == macro_sym)  { return eval_macro(cdr(p0)); };
    if (prim_p(x1) != nil_sym)   { return (prim_funptr(x1))(eval_args(cdr(p0))); };
    if (lambda_p(x1) != nil_sym) { return eval_applambda(x1, eval_args(cdr(p0))); };
    if (macro_p(x1) != nil_sym)  { return eval_appmacro(x1, cdr(p0)); };
label eval_cons_error;
    fputs(stderr, "ERROR: invalid application of '");
    pp_sexp(stderr, car(p0));
    fputs(stderr, "'\n");
    exit(1);
};

(% (var <id> <value>) %);
eval_var: (p0) {
    allocate(2);
    check_arity(p0, 2, "var");
    x0 = car(p0);
    x1 = eval_sexp(cadr(p0));
    sym_set(x0, x1);
    assign(sym_name(x0), x0);
    return x0;
};

eval_set: (p0) {
    allocate(2);
    check_arity(p0, 2, "set");
    x0 = deref(car(p0));
    x1 = eval_sexp(cadr(p0));
    if (sym_value(x0) == NULL) {
        fputs(stderr, "ERROR 'eval_set': undefined variable '");
        fputs(stderr, sym_name(x0));
        fputs(stderr, "'\n");
        exit(1);
    };
    sym_set(x0, x1);
    return x0;
};

eval_quote: (p0) {
    check_arity(p0, 1, "quote");
    return car(p0);
};

(% p0 : (cond ifthen ifelse) %);
eval_if: (p0) {
    allocate(2);
    check_arity(p0, 3, "if");
    scope_push();
    x0 = eval_sexp(car(p0));
    p0 = cdr(p0);
    if (x0 != nil_sym)  {
        x1 = eval_sexp(car(p0));
        scope_pop();
        return x1;
    };
    x1 = eval_sexp(cadr(p0));
    scope_pop();
    return x1;
};

eval_cond: (p0) {
    allocate(1);
    scope_push();
    while (p0 != nil_sym) {
        x0 = car(p0);
        check_arity(x0, 2, "cond");
        if (eval_sexp(car(x0)) != nil_sym) {
            scope_pop();
            return eval_sexp(cadr(x0));
        };
        p0 = cdr(p0);
    };
    fputs(stderr, "ERROR 'eval_cond': any condition was not met\n");
    exit(1);
};

(% p0: (cond body) %);
eval_while: (p0) {
    allocate(3);
    check_arity(p0, 2, "while");
    scope_push();
    x0 = car(p0); (% condition %);
    x1 = cadr(p0); (% body %);
    x2 = eval_sexp(x0);
    while (x2 != nil_sym) {
        eval_sexp(x1);
        x2 = eval_sexp(x0);
    };
    scope_pop();
    if (x2 != nil_sym) {
        fputs(stderr, "ERROR 'while': conditional expression could not evaluated to true/false\n");
        exit(1);
    };
    return nil_sym;
};

eval_do: (p0) {
    allocate(1);
    scope_push();
    while (p0 != nil_sym) {
        x0 = eval_sexp(car(p0));
        p0 = cdr(p0);
    };
    scope_pop();
    return x0;
};

(% (lambda (params) body) %);
eval_lambda: (p0) {
    allocate(2);
    check_arity(p0, 2, "lambda");
    x0 = car(p0); (% params %);
    x1 = cadr(p0); (% body %);
    return mklambda(x0, x1);
};

(% (macro (params) body) %);
eval_macro: (p0) {
    allocate(2);
    check_arity(p0, 2, "macro");
    x0 = car(p0); (% params %);
    x1 = cadr(p0); (% body %);
    return mkmacro(x0, x1);
};

(% p0: lambda, p1: params %);
eval_applambda: (p0, p1) {
    allocate(3);
    x0 = lambda_params(p0);
    if (length(x0) != length(p1)) {
        fputs(stderr, "ERROR: invalid number of argument (must be ");
        fputi(stderr, length(x0));
        fputi(stderr, ")\n");
        exit(1);
    };
    p1 = eval_args(p1);

    scope_push();
    while (x0 != nil_sym) {
        x1 = mksym(sym_name(car(x0)));
        sym_set(x1, car(p1));
        assign(sym_name(x1), x1);
        x0 = cdr(x0);
        p1 = cdr(p1);
    };
    x2 = eval_sexp(lambda_body(p0));
    scope_pop();
    return x2;
};

(% p0: macro, p1: params %);
eval_appmacro: (p0, p1) {
    allocate(3);
    x0 = macro_params(p0);
    if (length(x0) != length(p1)) {
        fputs(stderr, "ERROR: invalid number of argument (must be ");
        fputi(stderr, length(x0));
        fputi(stderr, ")\n");
        exit(1);
    };
    p1 = eval_args(p1);

    scope_push();
    while (x0 != nil_sym) {
        x1 = mksym(sym_name(car(x0)));
        sym_set(x1, car(p1));
        assign(sym_name(x1), x1);
        x0 = cdr(x0);
        p1 = cdr(p1);
    };
    x2 = eval_sexp(macro_body(p0));
    scope_pop();
    return eval_sexp(x2);
};

eval_sexp: (p0) {
    allocate(2);
    x0 = p0[0]; (% node code %);
    if (x0 == NODE_CONS)   { return eval_cons(p0); };
    if (x0 == NODE_SYMBOL) {
        x1 = deref(p0);
        if (sym_value(x1) != NULL) {
            return eval_sexp(sym_value(x1));
        };
        return x1;
    };
    return p0;
};

init_evaluator: () {
    symbol_map = mkmap(&symbol_hash, &symbol_equal, 100);
    scopeid_stack = mkvec(0);
    scope_push(); (% global scope %);
};

(%
 % rowl - generation 1
 % Copyright (C) 2010 nineties
 %
 % $Id: eval.rl 2010-05-25 22:10:05 nineties $
 %);

include(stddef,code);
export(init_evaluator, assign, deref, check_arity, eval_sexp);

symbol_map: NULL; (% (symbol name, scope id) -> symbol object %);

scopeid: 0;
scopeid_stack: NULL;

(% p0: (symbol name, scope id) %);
symbol_hash: (p0) {
    return strhash(p0[0])*3 + p0[1];
};

symbol_equal: (p0, p1) {
    if (p0[1] != p1[1]) { return FALSE; };
    return streq(p0[0], p1[0]);
};

scope_push: () {
    scopeid = scopeid + 1;
    vec_pushback(scopeid_stack, scopeid);
};

scope_pop: () {
    vec_popback(scopeid_stack);
};

(% p0: symbol name, p1: value %);
assign: (p0, p1) {
    allocate(1);
    x0 = vec_at(scopeid_stack, vec_size(scopeid_stack)-1);
    map_add(symbol_map, mktup2(p0, x0), p1);
};

(% p0: symbol object %);
deref: (p0) {
    allocate(4);
    x0 = vec_size(scopeid_stack)-1;
    x3 = sym_name(p0);
    while (x0 >= 0) {
        x1 = vec_at(scopeid_stack, x0); (% scopeid-id %);
        x2 = map_find(symbol_map, mktup2(x3, x1));
        if (x2 != NULL) { return x2; };
        x0 = x0 - 1;
    };
    return p0;
};

check_arity: (p0, p1, p2) {
    if (length(p0) != p1) {
        fputs(stderr, "ERROR '");
        fputs(stderr, p2);
        fputs(stderr, "': required ");
        fputi(stderr, p1);
        fputs(stderr, " arguments");
        exit(1);
    }
};

eval_args: (p0) {
    if (p0 == nil_sym) { return nil_sym; };
    return mkcons(eval_sexp(car(p0)), eval_args(cdr(p0)));
};

eval_cons: (p0) {
    allocate(2);
    x0 = car(p0);
    if (sym_p(x0) == nil_sym) { goto &eval_cons_error; };
    x1 = eval_sexp(x0);
    if (x1 == var_sym)    { return eval_var(cdr(p0)); };
    if (x1 == set_sym)    { return eval_set(cdr(p0)); };
    if (x1 == quote_sym)  { return eval_quote(cdr(p0)); };
    if (x1 == if_sym)     { return eval_if(cdr(p0)); };
    if (x1 == cond_sym)   { return eval_cond(cdr(p0)); };
    if (x1 == while_sym)  { return eval_while(cdr(p0)); };
    if (x1 == do_sym)     { return eval_do(cdr(p0)); };
    if (x1 == lambda_sym) { return eval_lambda(cdr(p0)); };
    if (x1 == macro_sym)  { return eval_macro(cdr(p0)); };
    if (prim_p(x1) != nil_sym)   { return (prim_funptr(x1))(eval_args(cdr(p0))); };
    if (lambda_p(x1) != nil_sym) { return eval_applambda(x1, eval_args(cdr(p0))); };
    if (macro_p(x1) != nil_sym)  { return eval_appmacro(x1, cdr(p0)); };
label eval_cons_error;
    fputs(stderr, "ERROR: invalid application of '");
    pp_sexp(stderr, car(p0));
    fputs(stderr, "'\n");
    exit(1);
};

(% (var <id> <value>) %);
eval_var: (p0) {
    allocate(2);
    check_arity(p0, 2, "var");
    x0 = car(p0);
    x1 = eval_sexp(cadr(p0));
    sym_set(x0, x1);
    assign(sym_name(x0), x0);
    return x0;
};

eval_set: (p0) {
    allocate(2);
    check_arity(p0, 2, "set");
    x0 = deref(car(p0));
    x1 = eval_sexp(cadr(p0));
    if (sym_value(x0) == NULL) {
        fputs(stderr, "ERROR 'eval_set': undefined variable '");
        fputs(stderr, sym_name(x0));
        fputs(stderr, "'\n");
        exit(1);
    };
    sym_set(x0, x1);
    return x0;
};

eval_quote: (p0) {
    check_arity(p0, 1, "quote");
    return car(p0);
};

(% p0 : (cond ifthen ifelse) %);
eval_if: (p0) {
    allocate(2);
    check_arity(p0, 3, "if");
    scope_push();
    x0 = eval_sexp(car(p0));
    p0 = cdr(p0);
    if (x0 != nil_sym)  {
        x1 = eval_sexp(car(p0));
        scope_pop();
        return x1;
    };
    x1 = eval_sexp(cadr(p0));
    scope_pop();
    return x1;
};

eval_cond: (p0) {
    allocate(1);
    scope_push();
    while (p0 != nil_sym) {
        x0 = car(p0);
        check_arity(x0, 2, "cond");
        if (eval_sexp(car(x0)) != nil_sym) {
            scope_pop();
            return eval_sexp(cadr(x0));
        };
        p0 = cdr(p0);
    };
    fputs(stderr, "ERROR 'eval_cond': any condition was not met\n");
    exit(1);
};

(% p0: (cond body) %);
eval_while: (p0) {
    allocate(3);
    check_arity(p0, 2, "while");
    scope_push();
    x0 = car(p0); (% condition %);
    x1 = cadr(p0); (% body %);
    x2 = eval_sexp(x0);
    while (x2 != nil_sym) {
        eval_sexp(x1);
        x2 = eval_sexp(x0);
    };
    scope_pop();
    if (x2 != nil_sym) {
        fputs(stderr, "ERROR 'while': conditional expression could not evaluated to true/false\n");
        exit(1);
    };
    return nil_sym;
};

eval_do: (p0) {
    allocate(1);
    scope_push();
    while (p0 != nil_sym) {
        x0 = eval_sexp(car(p0));
        p0 = cdr(p0);
    };
    scope_pop();
    return x0;
};

(% (lambda (params) body) %);
eval_lambda: (p0) {
    allocate(2);
    check_arity(p0, 2, "lambda");
    x0 = car(p0); (% params %);
    x1 = cadr(p0); (% body %);
    return mklambda(x0, x1);
};

(% (macro (params) body) %);
eval_macro: (p0) {
    allocate(2);
    check_arity(p0, 2, "macro");
    x0 = car(p0); (% params %);
    x1 = cadr(p0); (% body %);
    return mkmacro(x0, x1);
};

(% p0: lambda, p1: params %);
eval_applambda: (p0, p1) {
    allocate(3);
    x0 = lambda_params(p0);
    if (length(x0) != length(p1)) {
        fputs(stderr, "ERROR: invalid number of argument (must be ");
        fputi(stderr, length(x0));
        fputi(stderr, ")\n");
        exit(1);
    };
    p1 = eval_args(p1);

    scope_push();
    while (x0 != nil_sym) {
        x1 = mksym(sym_name(car(x0)));
        sym_set(x1, car(p1));
        assign(sym_name(x1), x1);
        x0 = cdr(x0);
        p1 = cdr(p1);
    };
    x2 = eval_sexp(lambda_body(p0));
    scope_pop();
    return x2;
};

(% p0: macro, p1: params %);
eval_appmacro: (p0, p1) {
    allocate(3);
    x0 = macro_params(p0);
    if (length(x0) != length(p1)) {
        fputs(stderr, "ERROR: invalid number of argument (must be ");
        fputi(stderr, length(x0));
        fputi(stderr, ")\n");
        exit(1);
    };
    p1 = eval_args(p1);

    scope_push();
    while (x0 != nil_sym) {
        x1 = mksym(sym_name(car(x0)));
        sym_set(x1, car(p1));
        assign(sym_name(x1), x1);
        x0 = cdr(x0);
        p1 = cdr(p1);
    };
    x2 = eval_sexp(macro_body(p0));
    scope_pop();
    return x2;
};

eval_sexp: (p0) {
    allocate(2);
    x0 = p0[0]; (% node code %);
    if (x0 == NODE_CONS)   { return eval_cons(p0); };
    if (x0 == NODE_SYMBOL) {
        x1 = deref(p0);
        if (sym_value(x1) != NULL) {
            return eval_sexp(sym_value(x1));
        };
        return x1;
    };
    return p0;
};

init_evaluator: () {
    symbol_map = mkmap(&symbol_hash, &symbol_equal, 100);
    scopeid_stack = mkvec(0);
    scope_push(); (% global scope %);
};


Raise exceptions on parse errors in JavaParser

package stompede.stomp;

import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.RubyClass;
import org.jruby.RubyObject;
import org.jruby.RubyFixnum;
import org.jruby.RubyString;
import org.jruby.RubyNumeric;
import org.jruby.RubyException;
import org.jruby.exceptions.RaiseException;

import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.Block;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;

%%{
  machine message;

  action mark {
    mark = p;
  }

  action mark_message {
    mark_message = context.runtime.getClassFromPath("Stompede::Stomp::Message").callMethod("new", context.nil, context.nil);
    /*mark_message_size = 0;*/
  }

  action write_command {
    mark_message.callMethod(context, "write_command", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action mark_key {
    mark_key = RubyString.newString(context.runtime, data, mark, p - mark);
    mark = -1;
  }

  action write_header {
    IRubyObject args[] = { mark_key, RubyString.newString(context.runtime, data, mark, p - mark) };
    mark_message.callMethod(context, "write_header", args);
    mark_key = null;
    mark = -1;
  }

  action finish_headers {
    IRubyObject content_length = mark_message.callMethod(context, "content_length");

    if ( ! content_length.isNil()) {
      mark_content_length = RubyNumeric.num2int(content_length);
    } else {
      mark_content_length = -1;
    }
  }

  action write_body {
    mark_message.callMethod(context, "write_body", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action consume_null {
    ((mark_content_length != -1) && ((p - mark) < mark_content_length))
  }

  action consume_octet {
    ((mark_content_length == -1) || ((p - mark) < mark_content_length))
  }

  action check_message_size {
    /*mark_message_size += 1;*/
    /*if (mark_message_size > max_message_size) {*/
    /*  rb_raise(eMessageSizeExceeded, "");*/
    /*}*/
  }

  action finish_message {
    block.yield(context, mark_message);
    mark_message = null;
  }

  include message_common "parser_common.rl";
}%%

@JRubyClass(name="JavaParser", parent="Object")
public class JavaParser extends RubyObject {
  %% write data noprefix;

  private class State {
    public int cs = JavaParser.start;
    public byte[] chunk;
    public int mark = -1;
    public RubyString mark_key;
    public IRubyObject mark_message;
    public int mark_message_size = -1;
    public int mark_content_length = -1;
  }

  private RaiseException exception;
  private long maxMessageSize;
  private State state;

  public JavaParser(Ruby runtime, RubyClass klass) {
    super(runtime, klass);
    state = new State();
  }

  @JRubyMethod
  public IRubyObject initialize(ThreadContext context) {
    RubyModule mStomp = context.runtime.getClassFromPath("Stompede::Stomp");
    return initialize(context, mStomp.callMethod("max_message_size"));
  }

  @JRubyMethod(argTypes = {RubyFixnum.class})
  public IRubyObject initialize(ThreadContext context, IRubyObject maxMessageSize) {
    this.maxMessageSize = ((RubyFixnum) maxMessageSize).getLongValue();
    return context.nil;
  }

  @JRubyMethod(argTypes = {RubyString.class})
  public IRubyObject parse(ThreadContext context, IRubyObject chunk, Block block) {
    byte data[] = ((RubyString) chunk).getBytes();

    int p = 0;
    int pe = data.length;

    int cs = state.cs;
    int mark = state.mark;
    RubyString mark_key = state.mark_key;
    IRubyObject mark_message = state.mark_message;
    int mark_message_size = state.mark_message_size;
    int mark_content_length = state.mark_content_length;

    %% write exec;

    state.cs = cs;
    state.mark = mark;
    state.mark_key = mark_key;
    state.mark_message = mark_message;
    state.mark_message_size = mark_message_size;
    state.mark_content_length = mark_content_length;

    if (cs == error) {
      IRubyObject args[] = { RubyString.newString(context.runtime, data), RubyFixnum.newFixnum(context.runtime, (long) p) };
      IRubyObject error = context.runtime.getClassFromPath("Stompede::Stomp").callMethod(context, "build_parse_error", args);
      throw new RaiseException((RubyException) error);
    }

    return context.nil;
  }
}

package stompede.stomp;

import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.RubyClass;
import org.jruby.RubyObject;
import org.jruby.RubyFixnum;
import org.jruby.RubyString;
import org.jruby.RubyNumeric;
import org.jruby.exceptions.RaiseException;

import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.Block;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;

%%{
  machine message;

  action mark {
    mark = p;
  }

  action mark_message {
    mark_message = context.runtime.getClassFromPath("Stompede::Stomp::Message").callMethod("new", context.nil, context.nil);
    /*mark_message_size = 0;*/
  }

  action write_command {
    mark_message.callMethod(context, "write_command", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action mark_key {
    mark_key = RubyString.newString(context.runtime, data, mark, p - mark);
    mark = -1;
  }

  action write_header {
    IRubyObject args[] = { mark_key, RubyString.newString(context.runtime, data, mark, p - mark) };
    mark_message.callMethod(context, "write_header", args);
    mark_key = null;
    mark = -1;
  }

  action finish_headers {
    IRubyObject content_length = mark_message.callMethod(context, "content_length");

    if ( ! content_length.isNil()) {
      mark_content_length = RubyNumeric.num2int(content_length);
    } else {
      mark_content_length = -1;
    }
  }

  action write_body {
    mark_message.callMethod(context, "write_body", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action consume_null {
    ((mark_content_length != -1) && ((p - mark) < mark_content_length))
  }

  action consume_octet {
    ((mark_content_length == -1) || ((p - mark) < mark_content_length))
  }

  action check_message_size {
    /*mark_message_size += 1;*/
    /*if (mark_message_size > max_message_size) {*/
    /*  rb_raise(eMessageSizeExceeded, "");*/
    /*}*/
  }

  action finish_message {
    block.yield(context, mark_message);
    mark_message = null;
  }

  include message_common "parser_common.rl";
}%%

@JRubyClass(name="JavaParser", parent="Object")
public class JavaParser extends RubyObject {
  %% write data noprefix;

  private class State {
    public int cs = JavaParser.start;
    public byte[] chunk;
    public int mark = -1;
    public RubyString mark_key;
    public IRubyObject mark_message;
    public int mark_message_size = -1;
    public int mark_content_length = -1;
  }

  private RaiseException exception;
  private long maxMessageSize;
  private State state;

  public JavaParser(Ruby runtime, RubyClass klass) {
    super(runtime, klass);
    state = new State();
  }

  @JRubyMethod
  public IRubyObject initialize(ThreadContext context) {
    RubyModule mStomp = context.runtime.getClassFromPath("Stompede::Stomp");
    return initialize(context, mStomp.callMethod("max_message_size"));
  }

  @JRubyMethod(argTypes = {RubyFixnum.class})
  public IRubyObject initialize(ThreadContext context, IRubyObject maxMessageSize) {
    this.maxMessageSize = ((RubyFixnum) maxMessageSize).getLongValue();
    return context.nil;
  }

  @JRubyMethod(argTypes = {RubyString.class})
  public IRubyObject parse(ThreadContext context, IRubyObject chunk, Block block) {
    byte data[] = ((RubyString) chunk).getBytes();

    int p = 0;
    int pe = data.length;

    int cs = state.cs;
    int mark = state.mark;
    RubyString mark_key = state.mark_key;
    IRubyObject mark_message = state.mark_message;
    int mark_message_size = state.mark_message_size;
    int mark_content_length = state.mark_content_length;

    %% write exec;

    state.cs = cs;
    state.mark = mark;
    state.mark_key = mark_key;
    state.mark_message = mark_message;
    state.mark_message_size = mark_message_size;
    state.mark_content_length = mark_content_length;

    if (cs == error) {
      // RaiseException error = context.runtime.getClassFromPath("Stompede::Stomp").callMethod("build_parse_error");
      // throw error;
    }

    return context.nil;
  }
}


rewrite set machines

rewrite set machines

# begin: ragel
=begin
%%{
  machine bel;

  include 'common.rl';
  include 'identifier.rl';
  include 'string.rl';
  include 'list.rl';

  action add_key {
    $stderr.puts "SET add_key"
    key = @buffers.delete(:ident)
    @buffers[:set_name] = key
  }

  action add_ident_value {
    $stderr.puts "SET add_ident_value"
    ident = @buffers.delete(:ident)
    @buffers[:set_value] = ident
  }

  action add_string_value {
    $stderr.puts "SET add_string_value"
    string = @buffers.delete(:string)
    @buffers[:set_value] = string
  }

  action add_list_value {
    $stderr.puts "SET add_list_value"
    list = @buffers.delete(:list)
    @buffers[:set_value] = list
  }

  action set_end {
    $stderr.puts "SET set_end"
    $stderr.puts @buffers.inspect
    name = @buffers.delete(:set_name)
    value = @buffers.delete(:set_value)
    set = set(name, value, complete: name.complete && value.complete)
    @buffers[:set] = set
  }

  action yield_set {
    $stderr.puts "SET yield_set"
    yield @buffers[:set]
  }

  SET_KW =
    [sS]
    [eE]
    [tT]
    ;

  key =
    an_ident
    %add_key
    ;

  ident_value =
    an_ident
    %add_ident_value
    ;

  string_value =
    a_string
    %add_string_value
    ;

  list_value =
    a_list
    %add_list_value
    ;

  value =
    ident_value |
    string_value |
    list_value
    ;

  set_node :=
    SET_KW
    %{ $stderr.puts '% SET_KW' }
    SP+
    an_ident
    %add_key
    SP+
    EQL
    %{ $stderr.puts '% EQL' }
    SP+
    value
    %set_end
    %yield_set
    ;
}%%
=end
# end: ragel

require_relative '../ast/node'
require_relative '../mixin/buffer'
require_relative '../nonblocking_io_wrapper'

module BELParser
  module Parsers
    module BELScript
      module Set

        class << self

          MAX_LENGTH = 1024 * 128 # 128K

          def parse(content)
            return nil unless content

            Parser.new(content).each do |obj|
              yield obj
            end
          end
        end

        private

        class Parser
          include Enumerable
          include BELParser::Parsers::Buffer
          include BELParser::Parsers::AST::Sexp

          def initialize(content)
            @content = content
            $stderr.puts "\n---\ncontent: '" + @content + "'"
      # begin: ragel
            %% write data;
      # end: ragel
          end

          def each
            @buffers    = {}
            @incomplete = {}
            @accum_set  = false
            @have_set   = false
            @have_name  = false
            @have_value = false
            data        = @content.unpack('C*')
            p           = 0
            pe          = data.length
            eof         = data.length

      # begin: ragel
            %% write init;
            %% write exec;
      # end: ragel
          end
        end
      end
    end
  end
end

if __FILE__ == $0
  $stdin.each_line do |line|
    BELParser::Parsers::BELScript::Set.parse(line) { |obj|
      puts obj.inspect
    }
  end
end

# vim: ft=ruby ts=2 sw=2:
# encoding: utf-8

# begin: ragel
=begin
%%{
  machine bel;

  include 'identifier.rl';
  include 'string.rl';
  include 'list.rl';

  action start_set_keyword {
    #$stderr.puts 'start_set_keyword'
    @incomplete[:set_keyword] = []
  }

  action accum_set_keyword {
    #$stderr.puts 'accum_set_keyword "' + fc.chr + '"'
    @incomplete[:set_keyword] << fc
    @accum_set = true
  }

  action start_value {
    @incomplete[:set_value] = []
  }

  action accum_value {
    @incomplete[:set_value] << fc
  }

  action eof_value {
    $stderr.puts 'handle eof - what is incomplete?'
    $stderr.puts @incomplete.inspect
  }

  action end_set_keyword {
    #$stderr.puts 'end_set_keyword'
    # have we accumulated part of the set keyword?
    unless @accum_set
      fbreak;
    end
    buf = @incomplete.delete(:set_keyword)
    keyword = ord_to_str(buf)
    if keyword == 'SET'
      @have_set = true
    end
  }

  action eof_set_keyword {
    #$stderr.puts 'eof_set_keyword'
  }

  action eof_set {
    #$stderr.puts 'eof_set'
  }

  action set_name_end {
    #$stderr.puts "set_name_end"
    name = @buffers.delete(:ident)
    unless ident.nil?
      @have_name = true
      @buffers[:set_name] = name
    end
  }

  action set_value_end {
    $stderr.puts 'set_value_end'
    $stderr.puts @buffers.inspect
    if @buffers.key?(:ident)
      @have_value = true
      ident = @buffers.delete(:ident)
      @buffers[:set_value] = ident
    elsif @buffers.key?(:list)
      $stderr.puts "I got a list!"
      @have_value = true
      list = @buffers.delete(:list)
      @buffers[:set_value] = list
    elsif @buffers.key?(:string)
      @have_value = true
      string = @buffers.delete(:string)
      @buffers[:set_value] = string
    end
  }

  action set_end {
    #$stderr.puts "set_end"
    children = []
    if @have_name
      children << @buffers.delete(:set_name)
    end
    if @have_value
      children << @buffers.delete(:set_value)
    end
    completed = @have_set && @have_name && @have_value
    ast_node = set(*children, complete: completed)
    @buffers[:set] = ast_node
    yield @buffers[:set]
  }

  SET_KW = [sS][eE][tT];

  action set_keyword {
    @buffers[:set] = set()
  }

  action name {
    @buffers[:set] = @buffers[:set] << name(@buffers[:ident])
  }

  action string_value {
    @buffers[:set] = @buffers[:set] << value(@buffers[:string])
  }

  action ident_value {
    @buffers[:set] = @buffers[:set] << value(@buffers[:ident])
  }

  action list_value {
    @buffers[:set] = @buffers[:set] << value(@buffers[:list])
  }

  action yield_set {
    yield @buffers[:set]
  }

  SET =
    [sS]
    [eE]?
    [tT]?
    ;

  set =
    (SET | alnum+)
    >start_set_keyword
    $accum_set_keyword
    %end_set_keyword
    $eof(eof_set_keyword)
    ;

  name =
    an_ident
    %set_name_end
    ;

  value =
    (
      a_string |
      a_list |
      an_ident
    )
    >start_value
    $accum_value
    $eof(eof_value)
    %set_value_end
    ;

  set_ast :=
    (
      set? SP+ name? SP+ EQL? SP+ value?
    )
    NL?
    $set_end
    $eof(eof_set)
    ;
}%%
=end
# end: ragel

require_relative '../ast/node'
require_relative '../mixin/buffer'
require_relative '../nonblocking_io_wrapper'

module BELParser
  module Parsers
    module BELScript
      module Set

        class << self

          MAX_LENGTH = 1024 * 128 # 128K

          def parse(content)
            return nil unless content

            Parser.new(content).each do |obj|
              yield obj
            end
          end
        end

        private

        class Parser
          include Enumerable
          include BELParser::Parsers::Buffer
          include BELParser::Parsers::AST::Sexp

          def initialize(content)
            @content = content
      # begin: ragel
            %% write data;
      # end: ragel
          end

          def each
            @buffers    = {}
            @incomplete = {}
            @accum_set  = false
            @have_set   = false
            @have_name  = false
            @have_value = false
            data        = @content.unpack('C*')
            p           = 0
            pe          = data.length
            eof         = data.length

      # begin: ragel
            %% write init;
            %% write exec;
      # end: ragel
          end
        end
      end
    end
  end
end

if __FILE__ == $0
  $stdin.each_line do |line|
    BELParser::Parsers::BELScript::Set.parse(line) { |obj|
      puts obj.inspect
    }
  end
end

# vim: ft=ruby ts=2 sw=2:
# encoding: utf-8