Splitting declaration and initialization using RASCAL - transformation

I'm new to Rascal and experimenting with its transformation/term rewriting abilities.
I want to write a script that splits declarations like:
int x = 5;
into declaration/initializations like:
int x;
x = 5;
How might I go about this? Let's say the language I'm trying to transform is Java.
Thanks for any help.

Sketch of a solution
Good question. There are several ways to do this and I will show the most simplistic one. Note that your example is not the simplest possible example since it requires a conversion from a single statement to a list of statements (i.e., it is not type preserving).
Here is without further ado a complete example, the explanation follows below.
module Decl
import IO;
import ParseTree;
// Decl, a trivial language, to demo simple program trafo
lexical Id = [a-z][a-z0-9]* !>> [a-z0-9] \ Reserved;
lexical Natural = [0-9]+ ;
lexical String = "\"" ![\"]* "\"";
layout Layout = WhitespaceAndComment* !>> [\ \t\n];
lexical WhitespaceAndComment
= [\ \t\n\r]
;
keyword Reserved = "int" | "str" | "if" | "then" | "else" | "fi" | "while" | "do" | "od";
start syntax Program
= {Statement ";"}* body
;
syntax Type
= "int"
| "str"
;
syntax Statement
= Type tp Id var
| Type tp Id var ":=" Expression exp
| Id var ":=" Expression val
| "if" Expression cond "then" {Statement ";"}* thenPart "else" {Statement ";"}* elsePart "fi"
| "while" Expression cond "do" {Statement ";"}* body "od"
;
syntax Expression
= Id name
| String string
| Natural natcon
| bracket "(" Expression e ")"
> left ( Expression lhs "+" Expression rhs
| Expression lhs "-" Expression rhs
)
;
str trafo1 () {
p = parse(#start[Program], "int x := 1").top;
newBody = "";
for(stat <- p.body){
if((Statement) `<Type tp> <Id var> := <Expression exp>` := stat){
newBody += "<tp> <var>; <var> := <exp>";
} else {
newBody += "<stat>";
}
}
return newBody;
}
The major part is a complete grammar of a simple language. The actual transformation is done by trafo1 which does:
Parse the example.
Introduce and initialize newBody (used to build the result).
Iterate over the statements in the given body.
Test for each statement whether it is of the desired form.
If true, append the transformed statement. Note that string templates are used here to build the transformed statement.
If false, append the original statement.
Return the resulting string.
Discussion
The solution style large depends on your goal. Here we just build a string. If desired, you could return the parsed string as result.
Alternatives:
Transform as concrete parse tree (not so easy since some functionality is still lacking but we aim to make this the preferred solution).
First transform to an abstract syntax tree (AST) and perform the transformation on the AST.
Hope this helps you to get started.

I have some more code examples here, which use concrete syntax matching and substitution to arrive at what you want:
module JavaMatch
import lang::java::\syntax::Java15;
// this just replaces exactly these specific kinds of declarations, as the only statement in a block:
CompilationUnit splitInitializersSimple(CompilationUnit u) = visit(u) {
case (Block) `{ int i = 0; }` => (Block) `{int i; i = 0;}`
};
// the next generalizes over any type, variable name or expression, but still one statement in a block:
CompilationUnit splitInitializersSingle(CompilationUnit u) = visit(u) {
case (Block) `{ <Type t> <Id i> = <Expr e>; }`
=> (Block) `{<Type t> <Id i>; <Id i> = <Expr e>;}`
};
// Now we allow more statements around the declaration, and we simply leave them where they are
CompilationUnit splitInitializersInContext(CompilationUnit u) = visit(u) {
case (Block) `{ <BlockStm* pre>
' <Type t> <Id i> = <Expr e>;
' <BlockStm* post>
'}`
=> (Block) `{ <BlockStm* pre>
' <Type t> <Id i>;
' <Id i> = <Expr e>;
' <BlockStm* post>
'}`
};
// But there could be more initializers in the same decl as well, as in int i, j = 0, k; :
CompilationUnit splitInitializersInContext2(CompilationUnit u) = visit(u) {
case (Block) `{ <BlockStm* pre>
' <Type t> <{VarDec ","}+ a>, <Id i>= <Expr e>, <{VarDec ","}+ b>;
' <BlockStm* post>
'}`
=> (Block) `{ <BlockStm* pre>
' <Type t> <{VarDec ","}+ a>, <Id i>, <{VarDec ","}+ b>;
' <Id i> = <Expr e>;
' <BlockStm* post>
'}`
};
// and now we add `innermost` such that not only the first but all occurrences are replaced:
CompilationUnit splitInitializersInContext2(CompilationUnit u) = innermost visit(u) {
case (Block) `{ <BlockStm* pre>
' <Type t> <{VarDec ","}+ a>, <Id i>= <Expr e>, <{VarDec ","}+ b>;
' <BlockStm* post>
'}`
=> (Block) `{ <BlockStm* pre>
' <Type t> <{VarDec ","}+ a>, <Id i>, <{VarDec ","}+ b>;
' <Id i> = <Expr e>;
' <BlockStm* post>
'}`
};
void doIt(loc file) {
start[CompilationUnit] unit = parse(#start[CompilationUnit], file);
unit.top = splitInitializersInContext(unit.top);
writeFile(file, "<unit>");
}
Final remarks, because this is still not fully general:
taking care of modifiers and array types; this will just add more variables to match and carry to the right hand side
the initializers will now occur in reverse order as statements, and in case of data dependency between them this would break
note that the shape of the rule is dependent very much on the Java grammar, since we use concrete syntax matching here. It helps to browse the grammar while you create such code.
this code preserves comments in many places, but not all, in particular in between declarations which are rewritten and in between vardecs which are rewritten the comments will be lost using this code.

Related

apply 'with<>' to a skipper parser to get context

My first version of this question was rich with misunderstandings. My answer below suits my needs. But I kept at it to understand what could be done with with<>. What I get is that it intended to inject context into a parser. Then the parser is called from with_directive::parse (in x3's with.hpp) In the following code, that is just what happens.
#include <boost/spirit/home/x3.hpp>
using namespace boost::spirit::x3;
struct eol_parser_cnt : parser<eol_parser_cnt>
{
struct context {
int line = 0;
std::string::iterator iter_pos;
};
template <typename Iterator, typename Context, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context const& context, unused_type, Attribute& attr) const
{
//std::cout << context.line;
auto& ctx = context;
return boost::spirit::x3::parse(first, last, lit(' ') | (lit("//") >> *(char_ - eol) >> eol));
}
};
const auto& our_skipper = eol_parser_cnt{};
eol_parser_cnt::context lines;
auto with_skipper = with<eol_parser_cnt::context>(lines)[our_skipper];
int main()
{
std::string str("12 word");
auto first = str.begin();
phrase_parse(first, str.end(), int_ >> *char_("a-z"), with_skipper);
}
Putting a break point in eol_parser_cnt::parse and I see it working. The debugger consistently shows the context is there and that it is the structure of eol_parser_cnt::context. I can change the value of line in the debugger and the next hit shows that value, it is a real object. But, try to uncomment the line std::cout << context.line; and the compiler complains that is it an unused_type. So, I just don't get it.
test.cpp(15,30): error C2039: 'line': is not a member of 'boost::spirit::x3::context<ID,T,Context>'
with
[
ID=eol_parser_cnt::context,
T=eol_parser_cnt::context,
Context=boost::spirit::x3::unused_type
]
F:\cpp\boost_1_76_0\boost\spirit\home\x3\support\context.hpp(18): message : see declaration of 'boost::spirit::x3::context<ID,T,Context>'
with
[
ID=eol_parser_cnt::context,
T=eol_parser_cnt::context,
Context=boost::spirit::x3::unused_type
]
F:\cpp\boost_1_76_0\boost\spirit\home\x3\directive\with.hpp(62): message : see reference to function template instantiation 'bool eol_parser_cnt::parse<Iterator,boost::spirit::x3::context<ID,T,Context>,Attribute>(Iterator &,const Iterator &,const boost::spirit::x3::context<ID,T,Context> &,boost::spirit::x3::unused_type,Attribute &) const' being compiled
with
[
Iterator=std::_String_iterator<std::_String_val<std::_Simple_types<char>>>,
ID=eol_parser_cnt::context,
T=eol_parser_cnt::context,
Context=boost::spirit::x3::unused_type,
Attribute=const boost::spirit::x3::unused_type
]
Well, it took a while but I was going to understand this. I re-read C++ Template Metaprogramming seriously this time. Then looking at the x3 code I finally understood that with<> is just another parser wrapper. It is interesting to debug an optimized build and see just how much code disappears. VS shows all the disappeared stuff on the stack as inlined and what was 9 layers of parser calls becomes 2 into the likes of with_error_handling::on_error. Everything from my call to parse_rhs_main (in rule.hpp, line 232), is gone.
So because with<> is just another parser wrapper and if the skipper is wrapped, the context is only avaliable to the skipper. But no big deal as the skipper is in the context of the main parser. The difference is that in the skipper object a get<skipper_eol_cnt>(context) returns a reference to the skipper_eol_cnt::context{}. Whereas in the main parser we have to us get<skipper_tag>(context) and this returns the with<> parser object. val is a member of that parser, the reference to the skipper_eol_cnt::context{}. So get<skipper_tag>(context).val retrieves the context we are looking for.
So here it is, using with<> applied to a skipper.
#include<iostream>
#include <iomanip>
#include <vector>
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted.hpp>
using namespace boost::spirit::x3;
struct skipper_eol_cnt : parser<skipper_eol_cnt>
{
struct context {
int line = 1;
std::string::iterator iter_pos;
};
template <typename Iterator, typename Context, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context const& context, unused_type, Attribute& attr) const
{
const char* start_cmt = "/*";
const char* end_cmt = "*/";
if (first == last)
return false;
bool matched = false;
//here we are getting from the 'with<>' wrapper
auto& ctx = get<skipper_eol_cnt>(context);
//skip: space | '//comment'
boost::spirit::x3::parse(first, last, lit(' ') | (lit("//") >> *(char_ - eol)));//eol counted below
//skip: '/*comment*/'
if (detail::string_parse(start_cmt, first, last, unused, case_compare<Iterator>())) {
for (; first != last; ++first) {
if (detail::string_parse(end_cmt, first, last, unused, case_compare<Iterator>()))
break;
if (*first == '\n')
++ctx.line, ctx.iter_pos = first;
}
}
Iterator iter = first;
for (; iter != last && (*iter == '\r' || *iter == '\n'); ++iter) {
matched = true;
if (*iter == '\n') // LF
++ctx.line, ctx.iter_pos = iter;
}
//{static int pos = 0; if (pos < ctx.line) { pos = ctx.line; std::cout << pos << std::endl; }}
if (matched) first = iter;
return matched;
}
};
auto const& skip_eol_cnt = skipper_eol_cnt{};
struct with_error_handling {
template<typename It, typename Ctx>
error_handler_result on_error(It f, It l, expectation_failure<It> const& ef, Ctx const& ctx) const {
It erit = f + std::distance(f, ef.where());
//here we are getting the wrapped skipper so need the 'with<>.val'
const auto& sctx = get<skipper_tag>(ctx).val;
It bit = erit;
for (; *bit != '\n'/* && bit != f*/; --bit)
;
It eit = erit;
for (; *eit != '\n' && eit != l; ++eit)
;
int str_pos = erit - bit - 1;
std::ostringstream oss;
oss << "Expecting " << ef.which() << "\n at line: " << sctx.line
<< "\n\t" << std::string(bit + 1, eit)
<< "\n\t" << std::setw(str_pos) << std::setfill('-') << "" << "^";
get<with_error_handling>(ctx).push_back(oss.str());;
return error_handler_result::fail;
}
};
//attr sections
struct section_type {
std::string name;
int line;
std::string::iterator iter_pos;
};
BOOST_FUSION_ADAPT_STRUCT(section_type, name)
using sections_type = std::vector<section_type>;
struct parser_find_sections : parser<parser_find_sections> {
template<typename Iterator, typename Context, typename RContext, typename Attribute>
bool parse(Iterator& first, Iterator const& last, Context const& context, RContext const& rcontext, Attribute& section) const {
const auto& sssctx = get<skipper_eol_cnt>(context); //now here this doesn't work, unused_type, but
const auto& sctx = get<skipper_tag>(context).val;
auto to_line = [&sctx, first](auto& ctx) {
_attr(ctx).line = sctx.line;
//_attr(ctx).iter_pos = first; // this one will get at 'section color(x,x)'
_attr(ctx).iter_pos = _where(ctx).begin(); // this one is '(x,x)'
};
static_assert(BOOST_VERSION / 100 % 1000 >= 77);
////NOTE!!! if you have a boost version of less than 1.77, x3::seek will fail here
////quick fix, copy from: https://github.com/boostorg/spirit/blob/boost-1.78.0/include/boost/spirit/home/x3/directive/seek.hpp
////and paste to your boost file...
return phrase_parse(first, last, *(seek["section"] >> (*alpha)[to_line]), get<skipper_tag>(context), section);
}
};
auto const parse_section = rule<with_error_handling, std::pair<int, int>>("the_sec_parser") = [] {
return '(' > int_ > ',' > int_ > ')';
}();
template<typename T>
std::ostream& operator << (std::ostream& os, std::pair<T, T>& t) {
return os << t.first << ',' << t.second;
}
//errors
std::vector<std::string> errors;
auto with_errors = with<with_error_handling>(errors)[parse_section];
auto test_section = [](auto& content, auto& section) {
//attr
std::pair<int, int> attr;
skipper_eol_cnt::context ctx{ section.line, section.iter_pos };
auto with_skip_cnt = with< skipper_eol_cnt>(ctx)[skip_eol_cnt];
auto first(section.iter_pos);
return std::tuple(phrase_parse(first, content.end(), with_errors, with_skip_cnt, attr), attr);
};
int main() {
std::string str(R"(//line 1
section red(5, 6)
section green( 7, 8) //line 3
section blue(9, 10) //no error
/*comment
bunch of lines of stuff....
*/ section white(11, a 12) //error on line 7
section black( 13,14)
)");
//get the list of sections
auto with_skip_cnt = with<skipper_eol_cnt>(skipper_eol_cnt::context{})[skip_eol_cnt];
sections_type secs;
auto first(str.begin());
phrase_parse(first, str.end(), parser_find_sections(), with_skip_cnt, secs);
for (auto& item : secs)
std::cout << item.name << "\t at line: " << item.line << std::endl;
//section 'blue', at 2, has no error
auto [r, attr] = test_section(str, secs.at(2));
if (r)
std::cout << "\nthe " << secs.at(2).name << " hase vals: " << attr << "\n\n";
//section 'white', at 3, has an error
test_section(str, secs.at(3));
if (errors.size())
std::cout << errors.front() << std::endl;
return 0;
}

Bison parser always prints "syntax error"

I am trying to build a 3 address code generator which would produce:
input:x=a+3*(b/7)
output: t1=b/7
t2=3*t1
t3=a+t2
x=t3
NO matter whatever i give as input the output is "syntax error".
I'm using Windows 10.
Yacc code:
%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define YYDEBUG 1
int yylex(void);
int t_count = 1;
void yyerror(char *s)
{
fprintf(stderr,"%s\n",s);
return;
}
char * generateToken(int i)
{
char* ch=(char*)malloc(sizeof(char)*5);
sprintf(ch,"t%d",i++);
return ch;
}
%}
%union { double dval; char ivar[50]; }
%token <ivar> NUMBER
%token <ivar> NAME
%type <ivar> expr
%type <ivar> term
%left '+' '-'
%left '*' '/'
%left '(' ')'
%right '='
%%
program:
line {
}
| program line {
}
;
line:
expr '\n' {
t_count =1;
}
| NAME '=' expr '\n' {
printf("%s = %s", $3,$1);
t_count=1;
}
;
expr:
expr '+' expr {
strcpy($$,generateToken(t_count));
printf("%s = %s + %s",$$,$1,$3);
}
| expr '-' expr {
strcpy($$,generateToken(t_count));
printf("%s = %s - %s",$$,$1,$3);
}
| expr '*' expr {
strcpy($$,generateToken(t_count));
printf("%s = %s * %s",$$,$1,$3);
}
| expr '/' expr {
strcpy($$,generateToken(t_count));
printf("%s = %s / %s",$$,$1,$3);
}
| term {
strcpy($$, $1);
}
| '(' expr ')' {
strcpy($$,generateToken(t_count));
printf("%s =( %s )" ,$$,$2);
}
;
term:
NAME {
strcpy($$, $1);
}
| NUMBER {
strcpy($$, $1);
}
;
%%
int main(void)
{
if (getenv("YYDEBUG")) yydebug = 1;
yyparse();
return 0;
}
Lex code:
%option noyywrap
%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "threeAdd.tab.h"
void yyerror(char*);
extern YYSTYPE yylval;
%}
NAME [a-zA-Z]
DIGIT [0-9]+
NUMBER [-]?{DIGIT}+(\.{DIGIT}+)?
%%
[ \t]+ { }
{NUMBER}
{
strcpy(yylval.ivar,yytext);
return *yylval.ivar;
}
"+" {
return *yytext;
}
"-" {
return *yytext;
}
"*" {
return *yytext;
}
"/" {
return *yytext;
}
"=" {
return *yytext;
}
"(" {
return *yytext;
}
")" {
return *yytext;
}
{NAME} {
strcpy(yylval.ivar,yytext);
return *yylval.ivar;
}
"\n" {
return *yytext;
}
exit {
return 0;
}
. {
char msg[25];
sprintf(msg," <%s>","invalid character",yytext);
yyerror(msg);
}
%%
Sample build & run:
C:\Users\USER\OneDrive\Desktop\Compiler\ICG>flex file.l
C:\Users\USER\OneDrive\Desktop\Compiler\ICG>bison -d file.y
C:\Users\USER\OneDrive\Desktop\Compiler\ICG>gcc lex.yy.c file.tab.c -o ICG.exe
C:\Users\USER\OneDrive\Desktop\Compiler\ICG>ICG.exe
3+9
syntax error
The basic problem is that you are use double-quote (" -- strings) for tokens in your yacc file (without defining any codes for them, so they're useless), and returning single character tokens in your lex file. As a result, none of the tokens will be recognized in your parser.
Replace all the " characters with ' characters on all the single character tokens in your yacc file (so "+" becomes '+' and "\n" becomes '\n').
Once you fix that, you have another problem: your lex rules for {DIGITS}+ and {NAME} don't return a token, so the token will be ignored (leading to syntax errors)
For debugging parser problems in general, it is often worth compiling with -DYYDEBUG and sticking yydebug = 1; into main before calling yyparse, which will cause the parser to print a trace of tokens seen and states visited. I often put
if (getenv("YYDEBUG")) yydebug = 1;
into main and just leave it there -- that way normally debugging won't be enabled, but if you set the environment variable YYDEBUG=1 before running your program, you'll see the debug trace (no need to recompile)
In order to return a token, your lexer rule needs to return the token. So your lexer rule for NUMBER should be:
{NUMBER} {
strcpy(yylval.ivar,yytext);
return NUMBER;
}
and similar for NAME. Note that the opening { of the code block must be on the same line as the pattern -- if it is on a separate line it will not be associated with the pattern.

Spirit error handling: expectation point does not return the expected "what" value

I'm using the Spirit error handling code from the article "Dispatching on Expectation Point Failures" at http://boost-spirit.com/home/2011/02/28/dispatching-on-expectation-point-failures/comment-page-1/, the last example there. My "diagnostics" and "error_handler_impl" classes are pretty much the same as is found in the article, but I can post them if someone thinks it is necessary.
In the code below, the start rule in question
comma = lit(',');
comma.name(",");
start = lit("begin") >> ident > comma >> ident;
has an expectation point after the "begin" keyword. In accordance with the article, I expected that a missing comma would cause error handler to be passed the qi::_4 value to be ",", which is the name of the comma rule. Instead, it is passing "sequence", causing the error handler to print a default message rather than the informative "Missing comma after begin keyword".
An idea what I am missing?
template <typename Iterator, typename Skipper = ascii::space_type>
class Grammar1 : boost::spirit::qi::grammar<Iterator, Skipper>
{
public:
typedef boost::spirit::qi::rule<Iterator, Skipper> rule_nil_T;
typedef boost::spirit::qi::rule<Iterator, string()> rule_str_T;
// structs from Rob Stewart's above-mentioned article
diagnostics<10> d1;
boost::phoenix::function<error_handler_impl> error_handler;
rule_str_T ident;
rule_nil_T comma;
rule_nil_T start;
Grammar1(void) : Grammar1::base_type(start)
{
ident %= lexeme [ qi::raw [ (qi::alpha | '_') >> *(qi::alnum | '_') ] ];
comma = lit(',');
comma.name(",");
start = lit("begin") >> ident > comma >> ident;
d1.add(",", "Missing comma after begin keyword");
on_error<fail>(start,
error_handler(ref(d1), _1, _2, _3, _4));
}
~Grammar1(void) { };
void parseInputFile(Iterator itr, Iterator itr_end)
{
bool r = phrase_parse(itr, itr_end, start, ascii::space);
if (r && itr == itr_end)
{
std::cout << "Parsing succeeded\n";
} else
{
string rest(itr, itr_end);
std::cout << "stopped at: \": " << rest << "\"\n";
}
}
};

Generate string if boolean attribute is true (karma counterpart to qi::matches)

Imagine we want to parse and generate simple C++ member function declarations with Boost.Spirit.
The Qi grammar might look like this:
function_ %= type_ > id_ > "()" > matches["const"];
That means, whether the function is const is stored in a bool.
How to write the corresponding generator with Karma?
function_ %= type_ << ' ' << id_ << "()" << XXX[" const"];
Here, we want a directive that consumes a boolean attribute, executes the embedded generator if the attribute is true and does nothing otherwise. We want something that makes the following tests succeed.
test_generator_attr("abc", XXX["abc"], true);
test_generator_attr("", XXX["abc"], false);
Is such a directive already available in Boost.Spirit?
The first thing that enters my mind at the moment is
bool const_qualifier = true;
std::cout << karma::format(
karma::omit[ karma::bool_(true) ] << " const" | "",
const_qualifier);
It feels a bit... clumsy. I'll have a look later what I'm forgetting :)
UPDATE Here's a slightly more elegant take using karma::symbols<>:
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;
int main()
{
karma::symbols<bool, const char*> const_;
const_.add(true, "const")(false, "");
for (bool const_qualifier : { true, false })
{
std::cout << karma::format_delimited("void foo()" << const_, ' ', const_qualifier) << "\n";
}
}
Prints:
void foo() const
void foo()

Compound Attribute generation in Boost::Spirit parse rule

I have the following parsing rule:
filter = (input >> (qi::repeat(0,2)[char_(';') >> input]))
input is a rule that returns a std::vector<int>, vector that I will just call vec for short.
The question is: What compound attribute would the filter rule return?
I tried:
fusion::vector <vec,std::vector <fusion::vector <char,vec> > >
But it fails and I don't know why.
The attribute types resulting of the parser expressions are quite well-documented. But that can be disorienting and timeconsuming.
Here's a trick: send in a sentinel to detect the attribute type:
struct Sniffer
{
typedef void result_type;
template <typename T>
void operator()(T const&) const { std::cout << typeid(T).name() << "\n"; }
};
then using the folliing parser expression
(input >> (qi::repeat(0,2)[qi::char_(';') >> input])) [ Sniffer() ]
will dump:
N5boost6fusion7vector2ISt6vectorIsSaIsEES2_INS1_IcS4_EESaIS5_EEEE
which c++filt -1 will tell you represents:
boost::fusion::vector2<
std::vector<short, std::allocator<short> >,
std::vector<boost::fusion::vector2<char, std::vector<short, std::allocator<short> > >,
std::allocator<boost::fusion::vector2<char, std::vector<short, std::allocator<short> > >
> >
>
See it live on Coliru: http://coliru.stacked-crooked.com/view?id=3e767990571f8d0917aae745bccfa520-5c1d29aa57205c65cfb2587775d52d22
boost::fusion::vector2<std::vector<short, std::allocator<short> >, std::vector<std::vector<short, std::allocator<short> >, std::allocator<std::vector<short, std::allocator<short> > > > >
It might be so surprisingly complicated, in part, because char_(";") could have been ';' (or more explicitely lit(';')). Constrast with this (Coliru):
boost::fusion::vector2<
std::vector<short, ... >,
std::vector<std::vector<short, std::allocator<short> >, ... > >
This should answer your question.
Sidenotes: parsing things
Don't underestimate automatic attribute propagation in Spirit. Frequently, you don't have to bother with the exact exposed types of attributes. Instead, rely on the (many) attribute transformations that Spirit uses to assign them to your supplied attribute references.
I trust you know the list-operator (%) in spirit? I'll show you how you can use it without further ado:
vector<vector<short>> data;
qi::parse(f, l, qi::short_ % ',' % ';', data);
Now, if you need to enforce the fact that it may be 1-3 elements, you might employ an eps with a Phoenix action to assert the maximum size:
const string x = "1,2,3;2,3,4;3,4,5";
auto f(begin(x)), l(end(x));
if (qi::parse(f, l,
(qi::eps(phx::size(qi::_val) < 2) > (qi::short_ % ',')) % ';'
, data))
{
cout << karma::format(karma::short_ % ',' % ';', data) << "\n";
}
cout << "remaining unparsed: '" << std::string(f,l) << "'\n";
Prints:
1,2,3;2,3,4
remaining unparsed: ';3,4,5'

Resources