Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
/*
|
|
|
|
|
|
PARSE.hpp - helpers for parsing textual specifications
|
|
|
|
|
|
|
|
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2024, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
|
|
|
|
|
|
|
|
**Lumiera** is free software; you can redistribute it and/or modify it
|
|
|
|
|
|
under the terms of the GNU General Public License as published by the
|
|
|
|
|
|
Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
|
|
option) any later version. See the file COPYING for further details.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @file parse.hpp
|
|
|
|
|
|
** Convenience wrappers and definitions for parsing structured definitions.
|
|
|
|
|
|
** Whenever a specification syntax entails nested structures, extracting contents
|
|
|
|
|
|
** with regular expressions alone becomes tricky. Without much sophistication, a
|
|
|
|
|
|
** directly implemented simple recursive descent parser is often less brittle and
|
|
|
|
|
|
** easier to understand and maintain. With some helper abbreviations, notably
|
|
|
|
|
|
** a combinator scheme to work from building blocks, a hand-written solution
|
|
|
|
|
|
** can benefit from taking short-cuts, especially related to result bindings.
|
Library: investigate how to approach recursion
Allowing free recursion in grammars is the key enabling feature,
which allows to accept arbitrary complex structures (like numeric expressions).
It is however also the element which makes the task of parsing a challenging endeavour;
after weighting the arguments, I decided ''not to place the focus on advanced usage,''
yet to open a pathway towards representation of such grammars.
Essentially, I consider it acceptable to require some additional work by the user,
if arbitrary recursive grammars are desired; because this design relies on explicitly
given parse functions, we need to introduce some kind of indirection interface,
to allow ''declaring'' a recursive rule first and later to ''supply the definition,''
which obviously then will involve other rules (or itself) recursively.
This leads to a very ''nifty approach'' towards recursion: we require the user
to provide an ''explicit model type'' beforehand, which implies that this is a
simple type, that can be spelled out (no λ) — and so the user is also
''forced to augment the actual rule with a model-binding,'' thereby reducing
the structured return types from the parse into something simple and uniform.
The user ''has to do the hard work,'' but can ''exploit additional knowledge''
related to the specific use case.
All this framework needs to do then is to supply a `std::function`, using the
explicit return type given; everything else will still work as implemented,
since a `std::function` can always stand-in for any arbitrary λ.
2025-01-25 02:48:11 +01:00
|
|
|
|
**
|
|
|
|
|
|
** So what is provided here is _not a parser library_ — yet aims at »making
|
|
|
|
|
|
** simple things simple« and let you implement the complicated ones yourselves.
|
|
|
|
|
|
** Several decisions were taken accordingly, like only supporting std::string_view
|
|
|
|
|
|
** and automatically consuming any leading whitespace. And notably the focus was
|
|
|
|
|
|
** _not placed_ on the challenging aspects of parsing — while still allowing a
|
|
|
|
|
|
** pathway towards definition of arbitrarily recursive grammars, if so desired.
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef LIB_PARSE_H
|
|
|
|
|
|
#define LIB_PARSE_H
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-22 22:31:25 +01:00
|
|
|
|
#include "lib/error.hpp"
|
2025-01-20 23:55:42 +01:00
|
|
|
|
#include "lib/branch-case.hpp"
|
2025-01-22 22:31:25 +01:00
|
|
|
|
#include "lib/format-string.hpp"
|
2025-01-01 22:02:08 +01:00
|
|
|
|
#include "lib/meta/variadic-rebind.hpp"
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
#include "lib/meta/function.hpp"
|
|
|
|
|
|
#include "lib/meta/trait.hpp"
|
|
|
|
|
|
#include "lib/regex.hpp"
|
2025-01-25 17:00:51 +01:00
|
|
|
|
#include "lib/test/diagnostic-output.hpp"/////////TODO
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
|
|
|
|
|
#include <optional>
|
|
|
|
|
|
#include <utility>
|
2025-01-19 23:11:25 +01:00
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
#include <tuple>
|
|
|
|
|
|
#include <array>
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
|
|
|
|
|
namespace util {
|
|
|
|
|
|
namespace parse {
|
2025-01-22 22:31:25 +01:00
|
|
|
|
namespace err = lumiera::error;
|
2025-01-20 20:22:16 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
using std::move;
|
|
|
|
|
|
using std::forward;
|
|
|
|
|
|
using std::optional;
|
|
|
|
|
|
using lib::meta::_Fun;
|
|
|
|
|
|
using lib::meta::has_Sig;
|
|
|
|
|
|
using lib::meta::NullType;
|
2025-01-01 22:02:08 +01:00
|
|
|
|
using lib::meta::_Vari;
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
using std::decay_t;
|
|
|
|
|
|
using std::tuple;
|
2025-01-19 23:11:25 +01:00
|
|
|
|
using std::array;
|
2025-01-22 22:31:25 +01:00
|
|
|
|
using util::_Fmt;
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
|
|
|
|
|
using StrView = std::string_view;
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
template<class PAR>
|
|
|
|
|
|
class Syntax;
|
|
|
|
|
|
|
2025-01-26 23:54:38 +01:00
|
|
|
|
template<class CON>
|
|
|
|
|
|
class Parser;
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
2025-01-19 23:11:25 +01:00
|
|
|
|
/**
|
2025-01-22 22:31:25 +01:00
|
|
|
|
* Parse evaluation result
|
|
|
|
|
|
* @tparam RES model type to bind
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
*/
|
|
|
|
|
|
template<class RES>
|
|
|
|
|
|
struct Eval
|
|
|
|
|
|
{
|
|
|
|
|
|
using Result = RES;
|
|
|
|
|
|
optional<RES> result;
|
2025-01-18 00:20:24 +01:00
|
|
|
|
size_t consumed{0};
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Building block: parser function
|
|
|
|
|
|
* definition and connection element.
|
|
|
|
|
|
*/
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<class FUN>
|
|
|
|
|
|
struct Connex
|
|
|
|
|
|
{
|
|
|
|
|
|
using PFun = FUN;
|
|
|
|
|
|
PFun parse;
|
|
|
|
|
|
|
|
|
|
|
|
using Result = typename _Fun<PFun>::Ret::Result;
|
|
|
|
|
|
|
Library: lay out foundation for recursive clauses
In accordance to the plan drafted yesterday, I will try to integrate
this essential capability into the framework established thus far by a trick,
requiring only minimal adjustment, but some work by the user.
Since the parse function is defined as a (unqualified) template argument,
it is possible to emplace either a `std::function`, or a reference thereto.
For this to work, the user is required to pre-define the expected result type,
and, furthermore, must later on assign a fully specified clause, which
also has a model transformation binding attached to yield this predeclared
result type
2025-01-26 15:55:01 +01:00
|
|
|
|
Connex (FUN pFun)
|
2025-01-26 23:54:38 +01:00
|
|
|
|
: parse{pFun}
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
{ }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-01-26 23:54:38 +01:00
|
|
|
|
/** special setup to be pre-declared and then used recursively */
|
|
|
|
|
|
template<class RES>
|
|
|
|
|
|
using OpaqueConnex = Connex<std::function<Eval<RES>(StrView)>>;
|
|
|
|
|
|
|
|
|
|
|
|
template<class RES>
|
|
|
|
|
|
using ForwardConnex = Connex<std::function<Eval<RES>(StrView)>&>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
|
|
|
|
|
/** »Null-Connex« which always successfully accepts the empty sequence */
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
auto
|
|
|
|
|
|
buildConnex(NullType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Connex{[](StrView) -> Eval<NullType>
|
|
|
|
|
|
{
|
|
|
|
|
|
return {NullType{}};
|
|
|
|
|
|
}};
|
|
|
|
|
|
}
|
|
|
|
|
|
using NulP = decltype(buildConnex (NullType()));
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Foundation: build a \ref Connex to accept a _terminal symbol._
|
|
|
|
|
|
* the actual parsing is delegated to a Regular Expression,
|
|
|
|
|
|
* which must match against the _beginning_ of the input sequence,
|
|
|
|
|
|
* possibly after skipping some whitespace. The defined parser
|
|
|
|
|
|
* returns an \ref Eval context, to hold a _Result Model_ and
|
|
|
|
|
|
* the number of characters matched by this terminal symbol.
|
|
|
|
|
|
*/
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
auto
|
|
|
|
|
|
buildConnex (regex rex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Connex{[regEx = move(rex)]
|
|
|
|
|
|
(StrView toParse) -> Eval<smatch>
|
2025-01-18 22:18:44 +01:00
|
|
|
|
{ // skip leading whitespace...
|
|
|
|
|
|
size_t pre = leadingWhitespace (toParse);
|
|
|
|
|
|
toParse = toParse.substr(pre);
|
2025-01-18 00:20:24 +01:00
|
|
|
|
auto result{matchAtStart (toParse,regEx)};
|
2025-01-18 22:18:44 +01:00
|
|
|
|
size_t consumed = result? pre+result->length() : 0;
|
2025-01-18 00:20:24 +01:00
|
|
|
|
return {move(result), consumed};
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
}};
|
|
|
|
|
|
}
|
|
|
|
|
|
using Term = decltype(buildConnex (std::declval<regex>()));
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/** build from a string with Regular-Epression spec */
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
Term
|
|
|
|
|
|
buildConnex (string const& rexDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return buildConnex (regex{rexDef});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/** copy-builder from an existing parser function */
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<class FUN>
|
|
|
|
|
|
auto
|
|
|
|
|
|
buildConnex (Connex<FUN> const& anchor)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Connex{anchor};
|
|
|
|
|
|
}
|
|
|
|
|
|
template<class FUN>
|
|
|
|
|
|
auto
|
|
|
|
|
|
buildConnex (Connex<FUN> && anchor)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Connex{move(anchor)};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
template<class PAR>
|
|
|
|
|
|
auto
|
|
|
|
|
|
buildConnex (Syntax<PAR> const& anchor)
|
|
|
|
|
|
{
|
|
|
|
|
|
using Con = typename Syntax<PAR>::Connex;
|
|
|
|
|
|
return Con{anchor};
|
|
|
|
|
|
}
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
2025-01-26 23:54:38 +01:00
|
|
|
|
/** special setup to attach to a pre-defined clause for recursive syntax
|
|
|
|
|
|
* @note works in concert with the Parser deduction guide, so that the
|
|
|
|
|
|
* resulting ForwardConnex holds a _reference_ to std::function,
|
|
|
|
|
|
* and thus gets to see the full definition reassigned later. */
|
|
|
|
|
|
template<class RES>
|
|
|
|
|
|
auto
|
|
|
|
|
|
buildConnex (Syntax<Parser<OpaqueConnex<RES>>> & refClause)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpaqueConnex<RES>& refConnex = refClause;
|
|
|
|
|
|
return ForwardConnex<RES>{refConnex.parse};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
Library: investigate how to approach recursion
Allowing free recursion in grammars is the key enabling feature,
which allows to accept arbitrary complex structures (like numeric expressions).
It is however also the element which makes the task of parsing a challenging endeavour;
after weighting the arguments, I decided ''not to place the focus on advanced usage,''
yet to open a pathway towards representation of such grammars.
Essentially, I consider it acceptable to require some additional work by the user,
if arbitrary recursive grammars are desired; because this design relies on explicitly
given parse functions, we need to introduce some kind of indirection interface,
to allow ''declaring'' a recursive rule first and later to ''supply the definition,''
which obviously then will involve other rules (or itself) recursively.
This leads to a very ''nifty approach'' towards recursion: we require the user
to provide an ''explicit model type'' beforehand, which implies that this is a
simple type, that can be spelled out (no λ) — and so the user is also
''forced to augment the actual rule with a model-binding,'' thereby reducing
the structured return types from the parse into something simple and uniform.
The user ''has to do the hard work,'' but can ''exploit additional knowledge''
related to the specific use case.
All this framework needs to do then is to supply a `std::function`, using the
explicit return type given; everything else will still work as implemented,
since a `std::function` can always stand-in for any arbitrary λ.
2025-01-25 02:48:11 +01:00
|
|
|
|
|
2025-01-25 03:40:41 +01:00
|
|
|
|
namespace {
|
|
|
|
|
|
/** helper to detect return type of a possibly generic λ */
|
|
|
|
|
|
template<typename ARG, typename FUN, typename SEL =void>
|
|
|
|
|
|
struct _ProbeFunReturn
|
|
|
|
|
|
{
|
|
|
|
|
|
static_assert (!sizeof(ARG),
|
|
|
|
|
|
"Model binding must accept preceding model result.");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template<typename ARG, typename FUN>
|
|
|
|
|
|
struct _ProbeFunReturn<ARG,FUN, std::void_t<decltype(std::declval<FUN>() (std::declval<ARG>()))>>
|
|
|
|
|
|
{ // probe the λ with ARG to force template instantiation
|
|
|
|
|
|
using Ret = decltype(std::declval<FUN>() (std::declval<ARG>()));
|
|
|
|
|
|
};
|
2025-01-26 23:54:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template<class FUN>
|
|
|
|
|
|
inline bool
|
|
|
|
|
|
_boundFun(FUN const& fun)
|
|
|
|
|
|
{
|
|
|
|
|
|
if constexpr (std::is_constructible<bool, FUN const&>())
|
|
|
|
|
|
return bool(fun);
|
|
|
|
|
|
else
|
|
|
|
|
|
return std::is_invocable<FUN, StrView>();
|
|
|
|
|
|
}
|
2025-01-25 03:40:41 +01:00
|
|
|
|
}
|
Library: investigate how to approach recursion
Allowing free recursion in grammars is the key enabling feature,
which allows to accept arbitrary complex structures (like numeric expressions).
It is however also the element which makes the task of parsing a challenging endeavour;
after weighting the arguments, I decided ''not to place the focus on advanced usage,''
yet to open a pathway towards representation of such grammars.
Essentially, I consider it acceptable to require some additional work by the user,
if arbitrary recursive grammars are desired; because this design relies on explicitly
given parse functions, we need to introduce some kind of indirection interface,
to allow ''declaring'' a recursive rule first and later to ''supply the definition,''
which obviously then will involve other rules (or itself) recursively.
This leads to a very ''nifty approach'' towards recursion: we require the user
to provide an ''explicit model type'' beforehand, which implies that this is a
simple type, that can be spelled out (no λ) — and so the user is also
''forced to augment the actual rule with a model-binding,'' thereby reducing
the structured return types from the parse into something simple and uniform.
The user ''has to do the hard work,'' but can ''exploit additional knowledge''
related to the specific use case.
All this framework needs to do then is to supply a `std::function`, using the
explicit return type given; everything else will still work as implemented,
since a `std::function` can always stand-in for any arbitrary λ.
2025-01-25 02:48:11 +01:00
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Adapt by applying a result-transforming function after a successful parse.
|
|
|
|
|
|
* @remark the purpose is to extract a custom data model immediately from the
|
|
|
|
|
|
* result; binding functors can be applied at any level of a Syntax,
|
|
|
|
|
|
* and thus the parse can be configured to produce custom result data.
|
|
|
|
|
|
*/
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<class CON, class BIND>
|
|
|
|
|
|
auto
|
|
|
|
|
|
adaptConnex (CON&& connex, BIND&& modelBinding)
|
|
|
|
|
|
{
|
2025-01-18 00:20:24 +01:00
|
|
|
|
using RX = typename CON::Result;
|
2025-01-25 03:40:41 +01:00
|
|
|
|
using Arg = std::add_rvalue_reference_t<RX>;
|
|
|
|
|
|
using AdaptedRes = typename _ProbeFunReturn<Arg,BIND>::Ret;
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
return Connex{[origConnex = forward<CON>(connex)
|
|
|
|
|
|
,binding = forward<BIND>(modelBinding)
|
|
|
|
|
|
]
|
|
|
|
|
|
(StrView toParse) -> Eval<AdaptedRes>
|
|
|
|
|
|
{
|
|
|
|
|
|
auto eval = origConnex.parse (toParse);
|
|
|
|
|
|
if (eval.result)
|
2025-01-25 17:00:51 +01:00
|
|
|
|
return {binding (move (*eval.result))
|
|
|
|
|
|
,eval.consumed};
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
else
|
|
|
|
|
|
return {std::nullopt};
|
|
|
|
|
|
}};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-25 17:00:51 +01:00
|
|
|
|
template<class CON>
|
|
|
|
|
|
auto
|
|
|
|
|
|
toStringConnex (CON&& connex, uint part)
|
|
|
|
|
|
{
|
|
|
|
|
|
using Result = typename CON::Result;
|
|
|
|
|
|
return Connex([baseConnex = forward<CON>(connex)
|
|
|
|
|
|
,part
|
|
|
|
|
|
]
|
|
|
|
|
|
(StrView toParse) -> Eval<string>
|
|
|
|
|
|
{
|
2025-01-26 01:24:10 +01:00
|
|
|
|
auto eval = baseConnex.parse (toParse);
|
|
|
|
|
|
if (eval.result)
|
|
|
|
|
|
if constexpr (lib::meta::is_basically<Result,smatch>())
|
2025-01-25 17:00:51 +01:00
|
|
|
|
return {eval.result->str(part)
|
|
|
|
|
|
,eval.consumed
|
|
|
|
|
|
};
|
2025-01-26 01:24:10 +01:00
|
|
|
|
else
|
|
|
|
|
|
{ // defensive fall-back: ignore model, return accepted input part
|
|
|
|
|
|
size_t pre = leadingWhitespace (toParse);
|
|
|
|
|
|
return {string{toParse.substr (pre, eval.consumed)}
|
|
|
|
|
|
,eval.consumed
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-01-25 17:00:51 +01:00
|
|
|
|
else
|
2025-01-26 01:24:10 +01:00
|
|
|
|
return {std::nullopt};
|
2025-01-25 17:00:51 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
2025-01-25 03:40:41 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
/* ===== building structured models ===== */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Product Model : results from a conjunction of parsing clauses,
|
|
|
|
|
|
* which are to be accepted in sequence, one after the other.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<typename...RESULTS>
|
|
|
|
|
|
struct SeqModel
|
|
|
|
|
|
: tuple<RESULTS...>
|
|
|
|
|
|
{
|
2025-01-22 16:42:28 +01:00
|
|
|
|
static constexpr size_t N = sizeof...(RESULTS);
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
using Seq = lib::meta::TySeq<RESULTS...>;
|
|
|
|
|
|
using Tup = std::tuple<RESULTS...>;
|
|
|
|
|
|
|
2025-01-18 00:20:24 +01:00
|
|
|
|
SeqModel() = default;
|
|
|
|
|
|
|
|
|
|
|
|
template<typename...XS, typename XX>
|
|
|
|
|
|
SeqModel (SeqModel<XS...>&& seq, XX&& extraElm)
|
|
|
|
|
|
: Tup{std::tuple_cat (seq.extractTuple()
|
2025-01-28 20:23:28 +01:00
|
|
|
|
,std::make_tuple (forward<XX> (extraElm)) )}
|
2025-01-18 00:20:24 +01:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
template<typename X1, typename X2>
|
|
|
|
|
|
SeqModel (X1&& res1, X2&& res2)
|
|
|
|
|
|
: Tup{move(res1), move(res2)}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
Tup&& extractTuple() { return move(*this); }
|
2025-01-22 16:42:28 +01:00
|
|
|
|
|
|
|
|
|
|
template<size_t i>
|
|
|
|
|
|
auto get() { return std::get<i> (*this); }
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-01-01 22:02:08 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Sum Model : results from a disjunction of parsing clauses,
|
|
|
|
|
|
* which are are tested and accepted as alternatives, one at least.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<typename...CASES>
|
|
|
|
|
|
struct AltModel
|
2025-01-21 03:53:29 +01:00
|
|
|
|
: lib::BranchCase<CASES...>
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
{
|
2025-01-01 22:02:08 +01:00
|
|
|
|
using Alt = lib::BranchCase<CASES...>;
|
2025-01-26 01:24:10 +01:00
|
|
|
|
static constexpr size_t N = sizeof...(CASES);
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
template<typename EXTRA>
|
|
|
|
|
|
using Additionally = AltModel<CASES...,EXTRA>;
|
2025-01-01 22:02:08 +01:00
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
template<typename EXTRA>
|
|
|
|
|
|
Additionally<EXTRA>
|
|
|
|
|
|
addBranch() ///< mark-up existing model to add a further branch-case
|
|
|
|
|
|
{
|
|
|
|
|
|
Additionally<EXTRA>& upFaked = reinterpret_cast<Additionally<EXTRA>&> (*this);
|
|
|
|
|
|
return {move (upFaked)};
|
|
|
|
|
|
} // this trick works due to similar storage layout
|
2025-01-01 22:02:08 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
/* === Builder functions to mark which side of the combinator to pick === */
|
2025-01-01 22:02:08 +01:00
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
using SubSeq = typename _Vari<AltModel, CASES...>::Prefix; ///< a nested sub-model to extend
|
|
|
|
|
|
using Penult = typename _Vari<AltModel, CASES...>::Penult; ///< plain value expected for left-branch
|
|
|
|
|
|
using Ultima = typename _Vari<AltModel, CASES...>::Ultima; ///< plain value expected for right-branch
|
2025-01-21 03:53:29 +01:00
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
static AltModel
|
|
|
|
|
|
mark_left (SubSeq&& leftCases)
|
2025-01-20 23:55:42 +01:00
|
|
|
|
{
|
2025-01-22 01:11:05 +01:00
|
|
|
|
return {leftCases.template addBranch<Ultima>()};
|
2025-01-20 23:55:42 +01:00
|
|
|
|
}
|
2025-01-01 22:02:08 +01:00
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
static AltModel
|
|
|
|
|
|
mark_left (Penult&& leftCase)
|
|
|
|
|
|
{
|
|
|
|
|
|
return {Alt::TOP-1, move(leftCase)};
|
|
|
|
|
|
}
|
2025-01-01 22:02:08 +01:00
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
static AltModel
|
|
|
|
|
|
mark_right (Ultima&& rightCase)
|
|
|
|
|
|
{
|
|
|
|
|
|
return {Alt::TOP, move(rightCase)};
|
|
|
|
|
|
}
|
2025-01-01 22:02:08 +01:00
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
private:
|
|
|
|
|
|
template<typename INIT>
|
|
|
|
|
|
AltModel (size_t branchID, INIT&& init)
|
|
|
|
|
|
: Alt{branchID, forward<INIT> (init)}
|
2025-01-01 22:02:08 +01:00
|
|
|
|
{ }
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Special case Product Model to represent iterative sequence */
|
|
|
|
|
|
template<typename RES>
|
|
|
|
|
|
struct IterModel
|
2025-01-22 22:31:25 +01:00
|
|
|
|
: std::vector<RES>
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
{
|
2025-01-22 22:31:25 +01:00
|
|
|
|
RES& get (size_t i) { return this->at(i); }
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Marker-Tag for the result from a sub-expression, not to be joined */
|
|
|
|
|
|
template<typename RES>
|
|
|
|
|
|
struct SubModel
|
|
|
|
|
|
{
|
2025-01-22 22:31:25 +01:00
|
|
|
|
RES model;
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
/** Standard case : combinator of two model branches */
|
|
|
|
|
|
template<template<class...> class TAG, class R1, class R2 =void>
|
|
|
|
|
|
struct _Join
|
|
|
|
|
|
{
|
|
|
|
|
|
using Result = TAG<R1,R2>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-01-22 01:11:05 +01:00
|
|
|
|
/** Generic case : extend a structured model by further branch */
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<template<class...> class TAG, class...RS, class R2>
|
|
|
|
|
|
struct _Join<TAG,TAG<RS...>,R2>
|
|
|
|
|
|
{
|
|
|
|
|
|
using Result = TAG<RS...,R2>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Special Case : absorb sub-expression without flattening */
|
|
|
|
|
|
template<template<class...> class TAG, class R1, class R2>
|
|
|
|
|
|
struct _Join<TAG,SubModel<R1>,R2>
|
|
|
|
|
|
{
|
|
|
|
|
|
using Result = TAG<R1,R2>;
|
|
|
|
|
|
};
|
|
|
|
|
|
template<template<class...> class TAG, class R1, class R2>
|
|
|
|
|
|
struct _Join<TAG,R1, SubModel<R2>>
|
|
|
|
|
|
{
|
|
|
|
|
|
using Result = TAG<R1,R2>;
|
|
|
|
|
|
};
|
|
|
|
|
|
template<template<class...> class TAG, class R1, class R2>
|
|
|
|
|
|
struct _Join<TAG,SubModel<R1>,SubModel<R2>>
|
|
|
|
|
|
{
|
|
|
|
|
|
using Result = TAG<R1,R2>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-18 22:18:44 +01:00
|
|
|
|
/** accept sequence of two parse functions */
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<class C1, class C2>
|
|
|
|
|
|
auto
|
|
|
|
|
|
sequenceConnex (C1&& connex1, C2&& connex2)
|
|
|
|
|
|
{
|
|
|
|
|
|
using R1 = typename decay_t<C1>::Result;
|
|
|
|
|
|
using R2 = typename decay_t<C2>::Result;
|
|
|
|
|
|
using ProductResult = typename _Join<SeqModel, R1, R2>::Result;
|
|
|
|
|
|
using ProductEval = Eval<ProductResult>;
|
|
|
|
|
|
return Connex{[conL = forward<C1>(connex1)
|
|
|
|
|
|
,conR = forward<C2>(connex2)
|
|
|
|
|
|
]
|
|
|
|
|
|
(StrView toParse) -> ProductEval
|
|
|
|
|
|
{
|
|
|
|
|
|
auto eval1 = conL.parse (toParse);
|
|
|
|
|
|
if (eval1.result)
|
|
|
|
|
|
{
|
2025-01-18 00:20:24 +01:00
|
|
|
|
size_t posAfter1 = eval1.consumed;
|
|
|
|
|
|
StrView restInput = toParse.substr(posAfter1);
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
auto eval2 = conR.parse (restInput);
|
|
|
|
|
|
if (eval2.result)
|
|
|
|
|
|
{
|
2025-01-18 00:20:24 +01:00
|
|
|
|
uint consumedOverall = posAfter1 + eval2.consumed;
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
return ProductEval{ProductResult{move(*eval1.result)
|
2025-01-18 00:20:24 +01:00
|
|
|
|
,move(*eval2.result)}
|
|
|
|
|
|
,consumedOverall
|
|
|
|
|
|
};
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ProductEval{std::nullopt};
|
|
|
|
|
|
}};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/** accept either one of two alternative parse functions */
|
2025-01-22 02:21:39 +01:00
|
|
|
|
template<class C1, class C2>
|
|
|
|
|
|
auto
|
|
|
|
|
|
branchedConnex (C1&& connex1, C2&& connex2)
|
|
|
|
|
|
{
|
|
|
|
|
|
using R1 = typename decay_t<C1>::Result;
|
|
|
|
|
|
using R2 = typename decay_t<C2>::Result;
|
|
|
|
|
|
using SumResult = typename _Join<AltModel, R1, R2>::Result;
|
|
|
|
|
|
using SumEval = Eval<SumResult>;
|
|
|
|
|
|
return Connex{[conL = forward<C1>(connex1)
|
|
|
|
|
|
,conR = forward<C2>(connex2)
|
|
|
|
|
|
]
|
|
|
|
|
|
(StrView toParse) -> SumEval
|
|
|
|
|
|
{
|
|
|
|
|
|
auto eval1 = conL.parse (toParse);
|
|
|
|
|
|
if (eval1.result)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint endBranch1 = eval1.consumed;
|
|
|
|
|
|
return SumEval{SumResult::mark_left (move(*eval1.result))
|
|
|
|
|
|
,endBranch1
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
auto eval2 = conR.parse (toParse);
|
|
|
|
|
|
if (eval2.result)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint endBranch2 = eval2.consumed;
|
|
|
|
|
|
return SumEval{SumResult::mark_right (move(*eval2.result))
|
|
|
|
|
|
,endBranch2
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return SumEval{std::nullopt};
|
|
|
|
|
|
}};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-22 22:31:25 +01:00
|
|
|
|
/** repeatedly accept parse-function, optionally delimited. */
|
|
|
|
|
|
template<class C1, class C2>
|
|
|
|
|
|
auto
|
|
|
|
|
|
repeatedConnex (uint min, uint max
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
,C1&& delimConnex
|
|
|
|
|
|
,C2&& bodyConnex)
|
2025-01-22 22:31:25 +01:00
|
|
|
|
{
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
using Res = typename decay_t<C2>::Result;
|
2025-01-22 22:31:25 +01:00
|
|
|
|
using IterResult = IterModel<Res>;
|
|
|
|
|
|
using IterEval = Eval<IterResult>;
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
return Connex{[sep = forward<C1>(delimConnex)
|
|
|
|
|
|
,body = forward<C2>(bodyConnex)
|
2025-01-22 22:31:25 +01:00
|
|
|
|
,min,max
|
|
|
|
|
|
]
|
|
|
|
|
|
(StrView toParse) -> IterEval
|
|
|
|
|
|
{
|
2025-01-24 01:41:55 +01:00
|
|
|
|
size_t consumed{0};
|
2025-01-22 22:31:25 +01:00
|
|
|
|
IterResult results;
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
|
|
|
|
|
uint offset{0};
|
|
|
|
|
|
if (not results.empty())
|
|
|
|
|
|
{ // look for delimiter within sequence
|
|
|
|
|
|
auto delim = sep.parse (toParse);
|
|
|
|
|
|
if (not delim.result)
|
|
|
|
|
|
break;
|
|
|
|
|
|
offset += delim.consumed;
|
|
|
|
|
|
}
|
|
|
|
|
|
auto eval = body.parse (toParse.substr(offset));
|
|
|
|
|
|
if (not eval.result)
|
|
|
|
|
|
break;
|
|
|
|
|
|
offset += eval.consumed;
|
|
|
|
|
|
results.emplace_back (move(*eval.result));
|
|
|
|
|
|
toParse = toParse.substr(offset);
|
|
|
|
|
|
consumed += offset;
|
|
|
|
|
|
}
|
|
|
|
|
|
while (results.size() < max);
|
|
|
|
|
|
return results.size() >= min? IterEval{move(results), consumed}
|
|
|
|
|
|
: IterEval{std::nullopt};
|
|
|
|
|
|
}};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/** try to accept parse-function, backtracking if not successful. */
|
|
|
|
|
|
template<class CNX>
|
|
|
|
|
|
auto
|
|
|
|
|
|
optionalConnex (CNX&& connex)
|
|
|
|
|
|
{
|
|
|
|
|
|
using Res = typename decay_t<CNX>::Result;
|
|
|
|
|
|
using OptResult = optional<Res>;
|
|
|
|
|
|
using OptEval = Eval<OptResult>;
|
|
|
|
|
|
return Connex{[body = forward<CNX>(connex)
|
|
|
|
|
|
]
|
|
|
|
|
|
(StrView toParse) -> OptEval
|
|
|
|
|
|
{
|
|
|
|
|
|
auto eval = body.parse (toParse);
|
|
|
|
|
|
size_t consumed{eval.result? eval.consumed : 0};
|
|
|
|
|
|
return OptEval{OptResult{eval.result? move(eval.result) : std::nullopt}
|
|
|
|
|
|
,consumed
|
|
|
|
|
|
};
|
|
|
|
|
|
}};
|
|
|
|
|
|
}
|
2025-01-22 22:31:25 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
2025-01-24 01:41:55 +01:00
|
|
|
|
/** accept some structure enclosed into a bracketing construct.
|
|
|
|
|
|
* \param isOptional if the bracketing can be omitted */
|
|
|
|
|
|
template<class C1, class C2, class C3>
|
|
|
|
|
|
auto
|
|
|
|
|
|
bracketedConnex (C1&& openingConnex
|
|
|
|
|
|
,C2&& closingConnex
|
|
|
|
|
|
,C3&& bodyConnex
|
|
|
|
|
|
,bool isOptional)
|
|
|
|
|
|
{
|
|
|
|
|
|
using Res = typename decay_t<C3>::Result;
|
|
|
|
|
|
return Connex{[opening = forward<C1>(openingConnex)
|
|
|
|
|
|
,closing = forward<C2>(closingConnex)
|
|
|
|
|
|
,body = forward<C3>(bodyConnex)
|
|
|
|
|
|
,isOptional
|
|
|
|
|
|
]
|
|
|
|
|
|
(StrView toParse) -> Eval<Res>
|
|
|
|
|
|
{
|
|
|
|
|
|
auto bracket = opening.parse (toParse);
|
|
|
|
|
|
if (bracket.result or isOptional)
|
|
|
|
|
|
{
|
|
|
|
|
|
size_t consumed = bracket.consumed;
|
|
|
|
|
|
bool expectClose{bracket.result};
|
|
|
|
|
|
auto eval = body.parse (toParse.substr(consumed));
|
|
|
|
|
|
if (eval.result)
|
|
|
|
|
|
{
|
|
|
|
|
|
consumed += eval.consumed;
|
|
|
|
|
|
if (expectClose)
|
|
|
|
|
|
bracket = closing.parse (toParse.substr(consumed));
|
|
|
|
|
|
if (bracket.result or not expectClose)
|
|
|
|
|
|
{
|
|
|
|
|
|
consumed += bracket.consumed;
|
|
|
|
|
|
return Eval<Res>{move (*eval.result)
|
|
|
|
|
|
,consumed
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return Eval<Res>{std::nullopt};
|
|
|
|
|
|
}};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* A Parser function to match and accept some syntax.
|
|
|
|
|
|
* This is a typing- and interface-adapter, wrapping a Connex.
|
|
|
|
|
|
*/
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<class CON>
|
|
|
|
|
|
class Parser
|
|
|
|
|
|
: public CON
|
|
|
|
|
|
{
|
|
|
|
|
|
using PFun = typename CON::PFun;
|
|
|
|
|
|
static_assert (_Fun<PFun>(), "Connex must define a parse-function");
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
using Connex = CON;
|
|
|
|
|
|
using Result = typename CON::Result;
|
|
|
|
|
|
|
|
|
|
|
|
static_assert (has_Sig<PFun, Eval<Result>(StrView)>()
|
|
|
|
|
|
,"Signature of the parse-function not suitable");
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Parse-Function operator: test input and yield Eval record
|
|
|
|
|
|
*/
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
Eval<Result>
|
|
|
|
|
|
operator() (StrView toParse)
|
|
|
|
|
|
{
|
2025-01-26 23:54:38 +01:00
|
|
|
|
REQUIRE (_boundFun (CON::parse), "unbound recursive syntax");
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
return CON::parse (toParse);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
Parser (SPEC&& spec)
|
|
|
|
|
|
: CON{buildConnex (forward<SPEC> (spec))}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/* === Deduction guide : how to construct a Parser === */
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
Parser(NullType) -> Parser<NulP>;
|
|
|
|
|
|
Parser(regex &&) -> Parser<Term>;
|
|
|
|
|
|
Parser(regex const&) -> Parser<Term>;
|
|
|
|
|
|
Parser(string const&) -> Parser<Term>;
|
|
|
|
|
|
|
|
|
|
|
|
template<class FUN>
|
2025-01-26 23:54:38 +01:00
|
|
|
|
Parser(Connex<FUN>) -> Parser<Connex<FUN>>;
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
2025-01-26 23:54:38 +01:00
|
|
|
|
Parser(Syntax<PAR>) -> Parser<typename PAR::Connex>;
|
|
|
|
|
|
|
|
|
|
|
|
template<class RES>
|
|
|
|
|
|
Parser(Syntax<Parser<OpaqueConnex<RES>>>) -> Parser<ForwardConnex<RES>>;
|
|
|
|
|
|
// bind to recursive syntax by reference
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/** @internal meta-helper : detect if parser can be built from a given type */
|
|
|
|
|
|
template<typename SPEC, typename SEL =void>
|
|
|
|
|
|
struct is_usableSpec : std::false_type{ };
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
struct is_usableSpec<SPEC, std::void_t<decltype(Parser{std::declval<SPEC>()})>>
|
|
|
|
|
|
: std::true_type
|
|
|
|
|
|
{ };
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
using if_acceptableSpec = lib::meta::enable_if<is_usableSpec<SPEC>>;
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
using if_acceptableSpecs = lib::meta::enable_if<is_usableSpec<SPEC1>
|
|
|
|
|
|
,lib::meta::enable_if<is_usableSpec<SPEC2>>>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************//**
|
|
|
|
|
|
* A Syntax clause with a parser and result state.
|
|
|
|
|
|
* An instance of this class embodies a (possibly complex)
|
|
|
|
|
|
* _expected syntactical structure;_ the [parse function](\ref parse)
|
|
|
|
|
|
* analyses a given input text for compliance with this expected structure.
|
|
|
|
|
|
* After the parse, result state has been set
|
|
|
|
|
|
* - indicating if the parse was successful
|
|
|
|
|
|
* - possibly with an failure message (TODO 1/25)
|
|
|
|
|
|
* - the number of characters covered by this match
|
|
|
|
|
|
* - a _Result Model,_ as a structured term holding
|
|
|
|
|
|
* result components from each part / sub-clause
|
|
|
|
|
|
*/
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<class PAR>
|
|
|
|
|
|
class Syntax
|
|
|
|
|
|
: public Eval<typename PAR::Result>
|
|
|
|
|
|
{
|
|
|
|
|
|
PAR parse_;
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
using Connex = typename PAR::Connex;
|
|
|
|
|
|
using Result = typename PAR::Result;
|
|
|
|
|
|
|
|
|
|
|
|
Syntax()
|
|
|
|
|
|
: parse_{NullType()}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
explicit
|
|
|
|
|
|
Syntax (PAR&& parser)
|
|
|
|
|
|
: parse_{move (parser)}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
explicit
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
operator bool() const { return success();}
|
|
|
|
|
|
|
|
|
|
|
|
operator Connex&() { return parse_; }
|
|
|
|
|
|
operator Connex const&() const { return parse_; }
|
|
|
|
|
|
|
2025-01-26 23:54:38 +01:00
|
|
|
|
bool success() const { return bool(Syntax::result); }
|
|
|
|
|
|
bool hasResult() const { return bool(Syntax::result); }
|
|
|
|
|
|
bool canInvoke() const { return _boundFun(parse_.parse);}
|
|
|
|
|
|
size_t consumed() const { return Eval<Result>::consumed; }
|
|
|
|
|
|
Result& getResult() { return * Syntax::result; }
|
|
|
|
|
|
Result&& extractResult() { return move(getResult()); }
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/********************************************//**
|
|
|
|
|
|
* Core API : parse against this syntax clause
|
|
|
|
|
|
*/
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
Syntax&&
|
|
|
|
|
|
parse (StrView toParse)
|
|
|
|
|
|
{
|
|
|
|
|
|
eval() = parse_(toParse);
|
|
|
|
|
|
return move(*this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-26 23:54:38 +01:00
|
|
|
|
template<class PX>
|
|
|
|
|
|
Syntax&
|
|
|
|
|
|
operator= (Syntax<PX> refSyntax)
|
|
|
|
|
|
{
|
|
|
|
|
|
using ConX = typename PX::Connex;
|
|
|
|
|
|
ConX& refConnex = refSyntax;
|
|
|
|
|
|
parse_.parse = move(refConnex.parse);
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-22 22:31:25 +01:00
|
|
|
|
|
|
|
|
|
|
/** ===== Syntax clause builder DSL ===== */
|
|
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<typename SPEC>
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
auto seq (SPEC&& clauseDef);
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
2025-01-22 02:21:39 +01:00
|
|
|
|
template<typename SPEC>
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
auto alt (SPEC&& clauseDef);
|
2025-01-22 22:31:25 +01:00
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
auto opt (SPEC&& clauseDef);
|
2025-01-22 22:31:25 +01:00
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
auto repeat (uint min, uint max, SPEC1&& delimDef, SPEC2&& clauseDef);
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
auto repeat (uint cnt, SPEC1&& delimDef, SPEC2&& clauseDef);
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
auto repeat (SPEC1&& delimDef, SPEC2&& clauseDef);
|
2025-01-22 22:31:25 +01:00
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
auto repeat (SPEC&& clauseDef);
|
2025-01-22 22:31:25 +01:00
|
|
|
|
|
2025-01-24 01:41:55 +01:00
|
|
|
|
template<typename SPEC1, typename SPEC2, typename SPEC3>
|
|
|
|
|
|
auto bracket (SPEC1&& openDef, SPEC2&& closeDef, SPEC3&& bodyDef);
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto bracket (string bracketSpec, SPEC&& bodyDef);
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto bracket (SPEC&& bodyDef);
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto bracketOpt (string bracketSpec, SPEC&& bodyDef);
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto bracketOpt (SPEC&& bodyDef);
|
|
|
|
|
|
|
Library: investigate how to approach recursion
Allowing free recursion in grammars is the key enabling feature,
which allows to accept arbitrary complex structures (like numeric expressions).
It is however also the element which makes the task of parsing a challenging endeavour;
after weighting the arguments, I decided ''not to place the focus on advanced usage,''
yet to open a pathway towards representation of such grammars.
Essentially, I consider it acceptable to require some additional work by the user,
if arbitrary recursive grammars are desired; because this design relies on explicitly
given parse functions, we need to introduce some kind of indirection interface,
to allow ''declaring'' a recursive rule first and later to ''supply the definition,''
which obviously then will involve other rules (or itself) recursively.
This leads to a very ''nifty approach'' towards recursion: we require the user
to provide an ''explicit model type'' beforehand, which implies that this is a
simple type, that can be spelled out (no λ) — and so the user is also
''forced to augment the actual rule with a model-binding,'' thereby reducing
the structured return types from the parse into something simple and uniform.
The user ''has to do the hard work,'' but can ''exploit additional knowledge''
related to the specific use case.
All this framework needs to do then is to supply a `std::function`, using the
explicit return type given; everything else will still work as implemented,
since a `std::function` can always stand-in for any arbitrary λ.
2025-01-25 02:48:11 +01:00
|
|
|
|
template<class FUN>
|
|
|
|
|
|
auto bind (FUN&& modelAdapt);
|
|
|
|
|
|
|
|
|
|
|
|
auto bindMatch (uint group =0);
|
|
|
|
|
|
|
2025-01-22 22:31:25 +01:00
|
|
|
|
private:
|
|
|
|
|
|
Eval<Result>& eval() { return *this;}
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
};
|
2025-01-22 22:31:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** ===== Syntax clause builder DSL ===== */
|
|
|
|
|
|
|
|
|
|
|
|
/** build a Syntax clause from anything usable as parser-spec. */
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept (SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Syntax{Parser{forward<SPEC> (clauseDef)}};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/** empty Syntax clause to start further definition */
|
2025-01-22 22:31:25 +01:00
|
|
|
|
auto accept() { return Syntax<Parser<NulP>>{}; }
|
|
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
/** start Syntax clause with an optional syntax part */
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_opt (SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
optionalConnex (Parser{forward<SPEC> (clauseDef)}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Start Syntax clause with a repeated sub-clause,
|
|
|
|
|
|
* with separator and repetition limit; repetitions ∊ [min..max]
|
|
|
|
|
|
* The separator will be expected _between_ instances of the repeated sub-clause
|
|
|
|
|
|
* and will by itself produce no model. The result model is an instance of \ref IterModel,
|
|
|
|
|
|
* which implies it is a vector (uses heap storage); if min ≡ 0, the model can be empty.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_repeated (uint min, uint max, SPEC1&& delimDef, SPEC2&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (max<min)
|
|
|
|
|
|
throw err::Invalid{_Fmt{"Invalid repeated syntax-spec: min:%d > max:%d"}
|
|
|
|
|
|
% min % max };
|
|
|
|
|
|
if (max == 0)
|
|
|
|
|
|
throw err::Invalid{"Invalid repeat with max ≡ 0 repetitions"};
|
|
|
|
|
|
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
repeatedConnex (min,max
|
|
|
|
|
|
,Parser{forward<SPEC1> (delimDef)}
|
|
|
|
|
|
,Parser{forward<SPEC2> (clauseDef)}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** \param cnt exact number of repetitions expected */
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2, typename =if_acceptableSpecs<SPEC1,SPEC2>>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_repeated (uint cnt, SPEC1&& delimDef, SPEC2&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept_repeated (cnt,cnt, forward<SPEC1>(delimDef), forward<SPEC2>(clauseDef));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** start Syntax with an arbitrarily repeated sub-clause, with separator */
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2, typename =if_acceptableSpecs<SPEC1,SPEC2>>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_repeated (SPEC1&& delimDef, SPEC2&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept_repeated (1,uint(-1), forward<SPEC1>(delimDef), forward<SPEC2>(clauseDef));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_repeated (uint min, uint max, SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept_repeated (min, max, NullType{}, forward<SPEC>(clauseDef));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_repeated (uint cnt, SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept_repeated (cnt, NullType{}, forward<SPEC>(clauseDef));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_repeated (SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept_repeated (NullType{}, forward<SPEC>(clauseDef));
|
|
|
|
|
|
}
|
2025-01-24 01:41:55 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Start Syntax with a sub-clause enclosed into a _bracketing construct._
|
|
|
|
|
|
* The »bracket« is defined as syntax for the _open marker_ and _close marker._
|
|
|
|
|
|
* These are consumed without generating model elements. The parse fails unless
|
|
|
|
|
|
* the full sequence `open body close` can be matched.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2, typename SPEC3>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_bracket (SPEC1&& openDef, SPEC2&& closeDef, SPEC3&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
bracketedConnex (Parser{forward<SPEC1>(openDef) }
|
|
|
|
|
|
,Parser{forward<SPEC2>(closeDef)}
|
|
|
|
|
|
,Parser{forward<SPEC3>(bodyDef) }
|
|
|
|
|
|
,false // bracket mandatory, not optional
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Start Syntax with a bracketed sub-clause, with given single-char delimiters.
|
|
|
|
|
|
* \param bracketSpec a 2-char string, e.g. "{}" to expect curly braces.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_bracket (string bracketSpec, SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (bracketSpec.size() != 2)
|
|
|
|
|
|
throw err::Invalid{"Bracket spec with opening and closing character expected"};
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
bracketedConnex (Parser{"\\"+bracketSpec.substr(0,1)}
|
|
|
|
|
|
,Parser{"\\"+bracketSpec.substr(1,1)}
|
|
|
|
|
|
,Parser{forward<SPEC>(bodyDef) }
|
|
|
|
|
|
,false // bracket mandatory, not optional
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Start Syntax with a sub-clause enclosed in parentheses */
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_bracket (SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept_bracket ("()", forward<SPEC>(bodyDef));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Start Syntax with a sub-clause, _optionally_ enclosed into brackets. */
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_bracketOpt (string bracketSpec, SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (bracketSpec.size() != 2)
|
|
|
|
|
|
throw err::Invalid{"Bracket spec with opening and closing character expected"};
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
bracketedConnex (Parser{"\\"+bracketSpec.substr(0,1)}
|
|
|
|
|
|
,Parser{"\\"+bracketSpec.substr(1,1)}
|
|
|
|
|
|
,Parser{forward<SPEC>(bodyDef) }
|
|
|
|
|
|
,true // bracket optional, can be omitted
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
accept_bracketOpt (SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept_bracketOpt ("()", forward<SPEC>(bodyDef));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-26 23:54:38 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Setup an assignable, recursive Syntax clause, initially empty.
|
|
|
|
|
|
* @remark this provides the foundation for recursive syntax clauses;
|
|
|
|
|
|
* initially, an empty std::function with the pre-declared return
|
|
|
|
|
|
* type is embedded. Together with a special Parser deduction guide,
|
|
|
|
|
|
* later on a full syntax clause can be built, taking a _reference_
|
|
|
|
|
|
* to this function; finally the definition prepared here should be
|
|
|
|
|
|
* _re-assigned_ with the fully defined syntax, which is handled
|
|
|
|
|
|
* by the assignment operator in class Syntax to re-assign a
|
|
|
|
|
|
* working parser function into the std::function holder.
|
|
|
|
|
|
* @tparam RES the result model type to be expected; it is necessary
|
|
|
|
|
|
* to augment the full definition explicitly by a model-binding
|
|
|
|
|
|
* to produce this type — which typically also involves writing
|
|
|
|
|
|
* actual code to deal with the possibly open structure enable
|
|
|
|
|
|
* through a recursive syntax definition
|
|
|
|
|
|
* @see Parse_test::verify_recursiveSyntax()
|
|
|
|
|
|
*/
|
Library: lay out foundation for recursive clauses
In accordance to the plan drafted yesterday, I will try to integrate
this essential capability into the framework established thus far by a trick,
requiring only minimal adjustment, but some work by the user.
Since the parse function is defined as a (unqualified) template argument,
it is possible to emplace either a `std::function`, or a reference thereto.
For this to work, the user is required to pre-define the expected result type,
and, furthermore, must later on assign a fully specified clause, which
also has a model transformation binding attached to yield this predeclared
result type
2025-01-26 15:55:01 +01:00
|
|
|
|
template<typename RES>
|
|
|
|
|
|
auto
|
|
|
|
|
|
expectResult()
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept (Connex{std::function<Eval<RES>(StrView)>{}});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Combinator: extend this Syntax clause by expecting a further sub-clause
|
|
|
|
|
|
* behind the part of the input matched by the already defined part of this Syntax.
|
|
|
|
|
|
* The result model will be a \SeqModel, which essentially is a tuple of the
|
|
|
|
|
|
* result models of all sequenced parts.
|
|
|
|
|
|
* @return Syntax clause instance accepting the extended structure.
|
|
|
|
|
|
* @warning the old syntax is invalidated by moving the parse-function out.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::seq (SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
sequenceConnex (move(parse_)
|
|
|
|
|
|
,Parser{forward<SPEC> (clauseDef)}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Combinator: extend this Syntax by adding an _alternative branch_.
|
|
|
|
|
|
* So either the already defined part of this Syntax matches the input,
|
|
|
|
|
|
* or the alternative clause is probed from the start of the input. At least
|
|
|
|
|
|
* one branch must match for the parse to be successful; however, further
|
2025-01-26 23:54:38 +01:00
|
|
|
|
* branches are not tested after finding a matching branch (short-circuit).
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
* The result model is a _Sum Type,_ implemented as a custom variant record
|
|
|
|
|
|
* of type \ref SubModel. It provides a branch selector field to detect which
|
|
|
|
|
|
* branch of the syntax did match. And it allows to retrieve the result model
|
|
|
|
|
|
* of this successful branch — which however requires that the invoking code
|
|
|
|
|
|
* precisely knows the model type to expect.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::alt (SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
branchedConnex (move(parse_)
|
|
|
|
|
|
,Parser{forward<SPEC> (clauseDef)}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Combinator: extend this Syntax with a further sequenced sub-clause,
|
|
|
|
|
|
* which however is _only optional_ and the match succeed without it.
|
|
|
|
|
|
* The result model is (as always for \ref seq ) a tuple; the result
|
|
|
|
|
|
* from the optional part is packaged into a std::optional.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::opt (SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_opt (forward<SPEC> (clauseDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Combinator: extend this Syntax with a further sequenced sub-clause,
|
|
|
|
|
|
* which in this case accepts a repeated sequence, with delimiter.
|
|
|
|
|
|
* @see accept_sequenced()
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::repeat (uint min, uint max, SPEC1&& delimDef, SPEC2&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_repeated (min,max
|
|
|
|
|
|
,forward<SPEC1>(clauseDef)
|
|
|
|
|
|
,forward<SPEC2>(clauseDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::repeat (uint cnt, SPEC1&& delimDef, SPEC2&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_repeated (cnt
|
|
|
|
|
|
,forward<SPEC1>(clauseDef)
|
|
|
|
|
|
,forward<SPEC2>(clauseDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::repeat (SPEC1&& delimDef, SPEC2&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_repeated (forward<SPEC1>(clauseDef)
|
|
|
|
|
|
,forward<SPEC2>(clauseDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::repeat (SPEC&& clauseDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_repeated (forward<SPEC>(clauseDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-24 01:41:55 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Combinator: extend this Syntax with a further sequenced sub-clause in brackets.
|
|
|
|
|
|
* @see accept_bracket()
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC1, typename SPEC2, typename SPEC3>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::bracket (SPEC1&& openDef, SPEC2&& closeDef, SPEC3&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_bracket (forward<SPEC1>(openDef)
|
|
|
|
|
|
,forward<SPEC2>(closeDef)
|
|
|
|
|
|
,forward<SPEC3>(bodyDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::bracket (string bracketSpec, SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_bracket (move (bracketSpec)
|
|
|
|
|
|
,forward<SPEC>(bodyDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::bracket (SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_bracket (forward<SPEC>(bodyDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::bracketOpt (string bracketSpec, SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_bracketOpt (move (bracketSpec)
|
|
|
|
|
|
,forward<SPEC>(bodyDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<typename SPEC>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::bracketOpt (SPEC&& bodyDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seq (accept_bracketOpt (forward<SPEC>(bodyDef)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Library: investigate how to approach recursion
Allowing free recursion in grammars is the key enabling feature,
which allows to accept arbitrary complex structures (like numeric expressions).
It is however also the element which makes the task of parsing a challenging endeavour;
after weighting the arguments, I decided ''not to place the focus on advanced usage,''
yet to open a pathway towards representation of such grammars.
Essentially, I consider it acceptable to require some additional work by the user,
if arbitrary recursive grammars are desired; because this design relies on explicitly
given parse functions, we need to introduce some kind of indirection interface,
to allow ''declaring'' a recursive rule first and later to ''supply the definition,''
which obviously then will involve other rules (or itself) recursively.
This leads to a very ''nifty approach'' towards recursion: we require the user
to provide an ''explicit model type'' beforehand, which implies that this is a
simple type, that can be spelled out (no λ) — and so the user is also
''forced to augment the actual rule with a model-binding,'' thereby reducing
the structured return types from the parse into something simple and uniform.
The user ''has to do the hard work,'' but can ''exploit additional knowledge''
related to the specific use case.
All this framework needs to do then is to supply a `std::function`, using the
explicit return type given; everything else will still work as implemented,
since a `std::function` can always stand-in for any arbitrary λ.
2025-01-25 02:48:11 +01:00
|
|
|
|
template<class PAR>
|
|
|
|
|
|
template<class FUN>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::bind (FUN&& modelAdapt)
|
|
|
|
|
|
{
|
|
|
|
|
|
return accept(
|
|
|
|
|
|
adaptConnex (move(parse_)
|
|
|
|
|
|
,forward<FUN>(modelAdapt)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class PAR>
|
|
|
|
|
|
auto
|
|
|
|
|
|
Syntax<PAR>::bindMatch (uint group)
|
|
|
|
|
|
{
|
2025-01-25 17:00:51 +01:00
|
|
|
|
return accept(
|
|
|
|
|
|
toStringConnex (move(parse_), group));
|
Library: investigate how to approach recursion
Allowing free recursion in grammars is the key enabling feature,
which allows to accept arbitrary complex structures (like numeric expressions).
It is however also the element which makes the task of parsing a challenging endeavour;
after weighting the arguments, I decided ''not to place the focus on advanced usage,''
yet to open a pathway towards representation of such grammars.
Essentially, I consider it acceptable to require some additional work by the user,
if arbitrary recursive grammars are desired; because this design relies on explicitly
given parse functions, we need to introduce some kind of indirection interface,
to allow ''declaring'' a recursive rule first and later to ''supply the definition,''
which obviously then will involve other rules (or itself) recursively.
This leads to a very ''nifty approach'' towards recursion: we require the user
to provide an ''explicit model type'' beforehand, which implies that this is a
simple type, that can be spelled out (no λ) — and so the user is also
''forced to augment the actual rule with a model-binding,'' thereby reducing
the structured return types from the parse into something simple and uniform.
The user ''has to do the hard work,'' but can ''exploit additional knowledge''
related to the specific use case.
All this framework needs to do then is to supply a `std::function`, using the
explicit return type given; everything else will still work as implemented,
since a `std::function` can always stand-in for any arbitrary λ.
2025-01-25 02:48:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
}// namespace parse
|
|
|
|
|
|
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
using parse::accept;
|
Library: change DSL scheme to handle optional and repeated better
It seemed that using postfix-decorating operators would be a good fit
for the DSL. Exploring this idea further showed however, that such a scheme
is indeed a good fit from the implementation side, but leads to rather confusing
and hard to grasp DSL statements for many non-trivial syntax definition.
The reason is: such a postfix-decorator will by default work on ''everything defined''
up to that point; this is too much in many cases.
The other alternative would be a function-style definition, which has the benefit
to take the sub-clause directly as argument (so the scope is always explicit).
The downside is that argument arrangement is a bit more tricky for the repetition
combinator (there can be mis-matches, since we take the »SPEC« as free-template argument)
And, moreover, with function-style, having more top-level entrance points would
be helpful. Overall, no fundamental roadblock, just more technicalities in the setup
of the DSL functions.
With that re-arrangd structure, an optional combinator could be easily integrated,
and a solution was provided to pick up the parser function from a sub-expression
defined as Syntax object.
2025-01-23 19:48:30 +01:00
|
|
|
|
using parse::accept_opt;
|
|
|
|
|
|
using parse::accept_repeated;
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
|
|
|
|
|
|
}// namespace util
|
2025-01-26 01:24:10 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace std { // Specialisation to support C++ »Tuple Protocol« and structured bindings.
|
|
|
|
|
|
|
|
|
|
|
|
/** determine compile-time fixed size of a SeqModel */
|
|
|
|
|
|
template<typename...ELMS>
|
|
|
|
|
|
struct tuple_size<util::parse::SeqModel<ELMS...> >
|
|
|
|
|
|
: tuple_size<typename util::parse::SeqModel<ELMS...>::Tup >
|
|
|
|
|
|
{ };
|
|
|
|
|
|
|
|
|
|
|
|
/** type of the I-th element of a SeqModel -> based on tuple type */
|
|
|
|
|
|
template<size_t I, typename...ELMS>
|
|
|
|
|
|
struct tuple_element<I, util::parse::SeqModel<ELMS...> >
|
|
|
|
|
|
: tuple_element<I, typename util::parse::SeqModel<ELMS...>::Tup >
|
|
|
|
|
|
{ };
|
|
|
|
|
|
}
|
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
|
|
|
|
#endif/*LIB_PARSE_H*/
|