Hello libart-query
All checks were successful
on-push / build-and-test (push) Successful in 15s

This commit is contained in:
2025-10-18 00:31:14 +02:00
commit 85c90c74e0
56 changed files with 2289 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto

View File

@@ -0,0 +1,31 @@
name: on-push
on:
push:
tags-ignore:
- '*'
branches:
- '**'
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/art/infra/buildenv/x86_64-fedora_42-unified:latest
volumes:
- /build
steps:
- name: Configure repository access
run: |
git config --global http.$GITHUB_SERVER_URL/.extraheader "Authorization: token ${{ secrets.ACT_RUNNER_TOKEN }}"
- name: Configure build directory
run: |
bpkg create -d /build cc config.cxx=clang++ config.cc.coptions="-Wall -Werror -Wno-unknown-pragmas"
- name: Build package
run: |
cd /build
bpkg build --yes --trust-yes $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git##$GITHUB_SHA
- name: Test package
run: |
cd /build
b test

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: helloryan

31
.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
.bdep/
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@@ -0,0 +1,31 @@
Copyright © 2024 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# libart-query
![Build badge](https://code.helloryan.se/art/libart-query/actions/workflows/on-push.yaml/badge.svg)
libart-query implements a simple query language.
## Sponsorship
You can sponsor the development of this project via Patreon. Read more
over at https://patreon.com/helloryan.

9
art/query/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

62
art/query/buildfile Normal file
View File

@@ -0,0 +1,62 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
./: lib{art-query}: libul{art-query}
libul{art-query}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{art-query}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n}
$d/exe{$n}: libul{art-query}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
{
dist = true
clean = ($src_root != $out_root)
}
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
# Export options.
#
lib{art-query}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{art-query}: bin.lib.version = "-$version.project_id"
else
lib{art-query}: bin.lib.version = "-$version.major.$version.minor"
# Install into the art/query/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/art/query/
install.subdirs = true
}

46
art/query/error.cxx Normal file
View File

@@ -0,0 +1,46 @@
#include <art/query/error.hxx>
namespace art::query
{
warning_t::
warning_t(source_location_t origin, string description)
: origin_{std::move(origin)},
description_{std::move(description)}
{}
source_location_t const&
warning_t::
origin() const
{
return origin_;
}
string const&
warning_t::
description() const
{
return description_;
}
error_t::
error_t(source_location_t origin, string description)
: origin_{std::move(origin)},
description_{std::move(description)}
{}
source_location_t const&
error_t::
origin() const
{
return origin_;
}
string const&
error_t::
description() const
{
return description_;
}
} // namespace art::query

46
art/query/error.hxx Normal file
View File

@@ -0,0 +1,46 @@
#ifndef art__query__error_hxx_
#define art__query__error_hxx_
#include <art/query/source-location.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class warning_t
{
public:
warning_t(source_location_t, string);
source_location_t const&
origin() const;
string const&
description() const;
private:
source_location_t origin_;
string description_;
};
class error_t
{
public:
error_t(source_location_t source, string);
source_location_t const&
origin() const;
string const&
description() const;
private:
source_location_t origin_;
string description_;
};
} // namespace art::query
#endif

25
art/query/expression.cxx Normal file
View File

@@ -0,0 +1,25 @@
#include <art/query/expression.hxx>
namespace art::query
{
source_location_t const&
expression_t::
origin() const
{
return container_->origin_();
}
void
accept(expression_t const& e, visitor_t& v)
{
e.container_->accept_(v);
}
string
to_string(expression_t const& e)
{
return e.container_->to_string_();
}
} // namespace art::query

94
art/query/expression.hxx Normal file
View File

@@ -0,0 +1,94 @@
#ifndef art__query__expression_hxx_
#define art__query__expression_hxx_
#include <art/query/error.hxx>
#include <art/query/source-location.hxx>
#include <art/query/types.hxx>
#include <art/query/visitor.hxx>
namespace art::query
{
class expression_t
{
public:
template<typename E>
expression_t(E e)
: container_{make_shared<container_t<E>>(std::move(e))}
{}
source_location_t const&
origin() const;
friend
void
accept(expression_t const&, visitor_t&);
friend
string
to_string(expression_t const&);
private:
struct abstract_t
{
virtual
~abstract_t() noexcept = default;
virtual
source_location_t const&
origin_() const = 0;
virtual
void
accept_(visitor_t& v) const = 0;
virtual
string
to_string_() const = 0;
};
template<typename E>
struct container_t
: abstract_t
{
explicit
container_t(E e)
: e{std::move(e)}
{}
~container_t() noexcept override
{}
source_location_t const&
origin_() const override
{
return e.origin();
}
void
accept_(visitor_t& v) const override
{
if (auto c = dynamic_cast<basic_visitor_t<E>*>(&v); c) {
c->visit(e);
return;
}
v.visit_default();
}
string
to_string_() const override
{
return to_string(e);
}
E e;
};
shared_ptr<abstract_t const> container_;
};
} // namespace art::query
#endif

View File

@@ -0,0 +1,134 @@
#include <art/query/lexical-analyzer.hxx>
#include <cctype>
#include <sstream>
namespace art::query
{
lexical_analyzer_t::
~lexical_analyzer_t() noexcept = default;
token_t
lexical_analyzer_t::
peek()
{
if (!current_)
current_ = extract();
return *current_;
}
void
lexical_analyzer_t::
consume()
{
current_ = extract();
}
lexical_analyzer_t::
lexical_analyzer_t() = default;
token_t
lexical_analyzer_t::
extract()
{
char_type c = peek_char();
while (is_whitespace(c)) {
consume_char();
c = peek_char();
}
if (c == 0)
return token_t{token_type_t::end};
if (c == ':') {
consume_char();
return token_t{token_type_t::colon};
}
if (c == '(') {
consume_char();
return token_t{token_type_t::open_parens};
}
if (c == ')') {
consume_char();
return token_t{token_type_t::close_parens};
}
if (c == '"') {
consume_char();
c = peek_char();
std::stringstream str;
while (c != 0 && c != '"') {
str << (char)c; // FIXME: UTF-8 encode.
consume_char();
c = peek_char();
}
consume_char();
return token_t{token_type_t::quoted_term, str.str()};
}
std::stringstream str;
while (c != 0 && !is_whitespace(c) && c != '(' && c != ')' && c != ':') {
consume_char();
str << (char)c; // FIXME: UFT-8 encode.
c = peek_char();
}
auto term = str.str();
auto lower = [](string str)
{
for (auto& j : str) {
j = std::tolower(j);
}
return str;
};
if (lower(term) == "and")
return token_t{token_type_t::logical_and};
if (lower(term) == "or")
return token_t{token_type_t::logical_or};
if (lower(term) == "not")
return token_t{token_type_t::logical_not};
return token_t{token_type_t::simple_term, std::move(term)};
}
lexical_analyzer_t::char_type
lexical_analyzer_t::
peek_char()
{
if (!next_char_)
next_char_ = extract_char();
return *next_char_;
}
void
lexical_analyzer_t::
consume_char()
{
next_char_ = extract_char();
}
bool
lexical_analyzer_t::
is_whitespace(char_type c)
{
return c == ' ' || c == '\t' || c == '\v';
}
} // namespace art::query

View File

@@ -0,0 +1,94 @@
#ifndef art__query__lexical_analyzer_hxx_
#define art__query__lexical_analyzer_hxx_
#include <art/query/token.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class lexical_analyzer_t
{
public:
using char_type = uint32_t;
virtual
~lexical_analyzer_t() noexcept;
token_t
peek();
void
consume();
protected:
lexical_analyzer_t();
lexical_analyzer_t(lexical_analyzer_t const&) = delete;
lexical_analyzer_t(lexical_analyzer_t&&) = delete;
token_t
extract();
char_type
peek_char();
void
consume_char();
virtual
char_type
extract_char() = 0;
lexical_analyzer_t& operator=(lexical_analyzer_t const&) = delete;
lexical_analyzer_t& operator=(lexical_analyzer_t&&) = delete;
static
bool
is_whitespace(char_type);
private:
optional<char_type> next_char_;
optional<token_t> current_;
};
template<typename Iterator, typename EndIterator = Iterator>
class basic_lexical_analyzer_t
: public lexical_analyzer_t
{
public:
using iterator = Iterator;
using end_iterator = EndIterator;
basic_lexical_analyzer_t(iterator, end_iterator);
basic_lexical_analyzer_t(basic_lexical_analyzer_t const&);
basic_lexical_analyzer_t(basic_lexical_analyzer_t&&);
~basic_lexical_analyzer_t() noexcept override;
basic_lexical_analyzer_t&
operator=(basic_lexical_analyzer_t const&) = delete;
basic_lexical_analyzer_t&
operator=(basic_lexical_analyzer_t&&) = delete;
protected:
char_type
extract_char() override;
private:
iterator current_;
end_iterator end_;
};
using string_lexical_analyzer_t = basic_lexical_analyzer_t<string::const_iterator>;
} // namespace art::query
#include <art/query/lexical-analyzer.txx>
#endif

View File

@@ -0,0 +1,40 @@
namespace art::query
{
template<typename I, typename E>
basic_lexical_analyzer_t<I, E>::
basic_lexical_analyzer_t(iterator begin, end_iterator end)
: current_{std::move(begin)},
end_{std::move(end)}
{}
template<typename I, typename E>
basic_lexical_analyzer_t<I, E>::
basic_lexical_analyzer_t(basic_lexical_analyzer_t const& other)
: current_{other.current_},
end_{other.end_}
{}
template<typename I, typename E>
basic_lexical_analyzer_t<I, E>::
basic_lexical_analyzer_t(basic_lexical_analyzer_t&& other)
: current_{std::move(other.current_)},
end_{std::move(other.end_)}
{}
template<typename I, typename E>
basic_lexical_analyzer_t<I, E>::
~basic_lexical_analyzer_t() noexcept = default;
template<typename I, typename E>
basic_lexical_analyzer_t<I, E>::char_type
basic_lexical_analyzer_t<I, E>::
extract_char()
{
if (current_ == end_)
return 0;
return *current_++;
}
} // namespace art::query

56
art/query/logical-and.cxx Normal file
View File

@@ -0,0 +1,56 @@
#include <art/query/logical-and.hxx>
#include <sstream>
namespace art::query
{
logical_and_t::
logical_and_t(expression_t left, expression_t right)
: left_{std::move(left)},
right_{std::move(right)}
{}
logical_and_t::
logical_and_t(source_location_t origin,
expression_t left,
expression_t right)
: origin_{std::move(origin)},
left_{std::move(left)},
right_{std::move(right)}
{}
source_location_t const&
logical_and_t::
origin() const
{
return origin_;
}
expression_t const&
logical_and_t::
left() const
{
return left_;
}
expression_t const&
logical_and_t::
right() const
{
return right_;
}
string
to_string(logical_and_t const& e)
{
std::stringstream str;
str << to_string(e.left());
str << " AND ";
str << to_string(e.right());
return str.str();
}
} // namespace art::query

39
art/query/logical-and.hxx Normal file
View File

@@ -0,0 +1,39 @@
#ifndef art__query__logical_and_hxx_
#define art__query__logical_and_hxx_
#include <art/query/expression.hxx>
#include <art/query/source-location.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class logical_and_t
{
public:
logical_and_t(expression_t, expression_t);
logical_and_t(source_location_t, expression_t, expression_t);
source_location_t const&
origin() const;
expression_t const&
left() const;
expression_t const&
right() const;
private:
source_location_t origin_;
expression_t left_;
expression_t right_;
};
string
to_string(logical_and_t const&);
} // namespace art::query
#endif

43
art/query/logical-not.cxx Normal file
View File

@@ -0,0 +1,43 @@
#include <art/query/logical-not.hxx>
#include <sstream>
namespace art::query
{
logical_not_t::
logical_not_t(expression_t right)
: right_{std::move(right)}
{}
logical_not_t::
logical_not_t(source_location_t origin, expression_t right)
: origin_{std::move(origin)},
right_{std::move(right)}
{}
source_location_t const&
logical_not_t::
origin() const
{
return origin_;
}
expression_t const&
logical_not_t::
right() const
{
return right_;
}
string
to_string(logical_not_t const& e)
{
std::stringstream str;
str << "NOT " << to_string(e.right());
return str.str();
}
} // namespace art::query

34
art/query/logical-not.hxx Normal file
View File

@@ -0,0 +1,34 @@
#ifndef art__query__logical_not_t_hxx_
#define art__query__logical_not_t_hxx_
#include <art/query/expression.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class logical_not_t
{
public:
explicit
logical_not_t(expression_t);
logical_not_t(source_location_t, expression_t);
source_location_t const&
origin() const;
expression_t const&
right() const;
private:
source_location_t origin_;
expression_t right_;
};
string
to_string(logical_not_t const& e);
} // namespace art::query
#endif

56
art/query/logical-or.cxx Normal file
View File

@@ -0,0 +1,56 @@
#include <art/query/logical-or.hxx>
#include <sstream>
namespace art::query
{
logical_or_t::
logical_or_t(expression_t left, expression_t right)
: left_{std::move(left)},
right_{std::move(right)}
{}
logical_or_t::
logical_or_t(source_location_t origin,
expression_t left,
expression_t right)
: origin_{std::move(origin)},
left_{std::move(left)},
right_{std::move(right)}
{}
source_location_t const&
logical_or_t::
origin() const
{
return origin_;
}
expression_t const&
logical_or_t::
left() const
{
return left_;
}
expression_t const&
logical_or_t::
right() const
{
return right_;
}
string
to_string(logical_or_t const& e)
{
std::stringstream str;
str << to_string(e.left());
str << " or ";
str << to_string(e.right());
return str.str();
}
} // namespace art::query

39
art/query/logical-or.hxx Normal file
View File

@@ -0,0 +1,39 @@
#ifndef art__query__logical_or_hxx_
#define art__query__logical_or_hxx_
#include <art/query/types.hxx>
#include <art/query/expression.hxx>
#include <art/query/source-location.hxx>
namespace art::query
{
class logical_or_t
{
public:
logical_or_t(expression_t, expression_t);
logical_or_t(source_location_t, expression_t, expression_t);
source_location_t const&
origin() const;
expression_t const&
left() const;
expression_t const&
right() const;
private:
source_location_t origin_;
expression_t left_;
expression_t right_;
};
string
to_string(logical_or_t const&);
} // namespace art::query
#endif

88
art/query/match.cxx Normal file
View File

@@ -0,0 +1,88 @@
#include <art/query/match.hxx>
namespace art::query
{
bool
match(query_t const& q, predicate_t const& p)
{
class matcher_t
: public visitor_t,
public basic_visitor_t<term_t>,
public basic_visitor_t<tag_t>,
public basic_visitor_t<logical_and_t>,
public basic_visitor_t<logical_not_t>,
public basic_visitor_t<logical_or_t>,
public basic_visitor_t<parenthesized_t>
{
public:
static
bool
match(optional<expression_t> const& e, predicate_t const& p)
{
matcher_t m{p};
if (e) {
accept(*e, m);
}
return m.result;
}
private:
explicit
matcher_t(predicate_t const& p)
: predicate_{p}
{}
void
visit_default() override
{
result = false;
}
void
visit(term_t const& e) override
{
result = predicate_(e);
}
void
visit(tag_t const& e) override
{
result = predicate_(e);
}
void
visit(logical_and_t const& e) override
{
result = match(e.left(), predicate_) && match(e.right(), predicate_);
}
void
visit(logical_or_t const& e) override
{
result = match(e.left(), predicate_) || match(e.right(), predicate_);
}
void
visit(logical_not_t const& e) override
{
result = !match(e.right(), predicate_);
}
void
visit(parenthesized_t const& e) override
{
result = match(e.expr(), predicate_);
}
predicate_t const& predicate_;
bool result{};
};
return matcher_t::match(q.expr(), p);
}
} // namespace art::query

112
art/query/match.hxx Normal file
View File

@@ -0,0 +1,112 @@
#ifndef art__query__match_hxx_
#define art__query__match_hxx_
#include <art/query/types.hxx>
#include <art/query/logical-and.hxx>
#include <art/query/logical-not.hxx>
#include <art/query/logical-or.hxx>
#include <art/query/parenthesized.hxx>
#include <art/query/query.hxx>
#include <art/query/tag.hxx>
#include <art/query/term.hxx>
namespace art::query
{
using predicate_t = function<
bool(variant<term_t, tag_t> const&)
>;
bool
match(query_t const&, predicate_t const&);
template<typename Q, typename P>
Q
transform(optional<query_t> const& q, P const& predicate)
{
class matcher_t
: public visitor_t,
public basic_visitor_t<term_t>,
public basic_visitor_t<tag_t>,
public basic_visitor_t<logical_and_t>,
public basic_visitor_t<logical_not_t>,
public basic_visitor_t<logical_or_t>,
public basic_visitor_t<parenthesized_t>
{
public:
P const& predicate;
static
Q
transform(optional<expression_t> const& e, P const& predicate)
{
matcher_t m{predicate};
if (e) {
accept(*e, m);
}
return m.result;
}
private:
explicit
matcher_t(P const& predicate)
: predicate{predicate}
{}
void
visit_default() override
{}
void
visit(term_t const& term) override
{
result = predicate(term);
}
void
visit(tag_t const& tag) override
{
result = predicate(tag);
}
void
visit(logical_and_t const& logical_and) override
{
result = transform(logical_and.left(), predicate) && transform(logical_and.right(), predicate);
}
void
visit(logical_or_t const& logical_or) override
{
result = transform(logical_or.left(), predicate) || transform(logical_or.right(), predicate);
}
void
visit(logical_not_t const& logical_not) override
{
result = !transform(logical_not.right(), predicate);
}
void
visit(parenthesized_t const& parenthesized) override
{
result = transform(parenthesized.expr(), predicate);
}
Q result{};
};
if (q) {
return matcher_t::transform(q->expr(), predicate);
}
return Q{};
}
} // namespace art::query
#endif

View File

@@ -0,0 +1,43 @@
#include <art/query/parenthesized.hxx>
#include <sstream>
namespace art::query
{
parenthesized_t::
parenthesized_t(expression_t expr)
: expr_{std::move(expr)}
{}
parenthesized_t::
parenthesized_t(source_location_t origin, expression_t expr)
: origin_{std::move(origin)},
expr_{std::move(expr)}
{}
source_location_t const&
parenthesized_t::
origin() const
{
return origin_;
}
expression_t const&
parenthesized_t::
expr() const
{
return expr_;
}
string
to_string(parenthesized_t const& e)
{
std::stringstream str;
str << '(' << to_string(e.expr()) << ')';
return str.str();
}
} // namespace art::query

View File

@@ -0,0 +1,35 @@
#ifndef art__query__parenthesized_hxx_
#define art__query__parenthesized_hxx_
#include <art/query/expression.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class parenthesized_t
{
public:
explicit
parenthesized_t(expression_t);
parenthesized_t(source_location_t, expression_t);
source_location_t const&
origin() const;
expression_t const&
expr() const;
private:
source_location_t origin_;
expression_t expr_;
};
string
to_string(parenthesized_t const& e);
} // namespace art::query
#endif

View File

@@ -0,0 +1,38 @@
#include <art/query/parse-context.hxx>
namespace art::query
{
parse_context_t::
parse_context_t()
{}
vector<warning_t> const&
parse_context_t::
warnings() const
{
return warnings_;
}
vector<error_t> const&
parse_context_t::
errors() const
{
return errors_;
}
void
parse_context_t::
report_warning(warning_t w)
{
warnings_.emplace_back(std::move(w));
}
void
parse_context_t::
report_error(error_t e)
{
errors_.emplace_back(std::move(e));
}
} // namespace art::query

View File

@@ -0,0 +1,36 @@
#ifndef art__query__parse_context_hxx_
#define art__query__parse_context_hxx_
#include <art/query/error.hxx>
#include <art/query/source-location.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class parse_context_t
{
public:
parse_context_t();
vector<warning_t> const&
warnings() const;
vector<error_t> const&
errors() const;
void
report_warning(warning_t);
void
report_error(error_t);
private:
vector<warning_t> warnings_;
vector<error_t> errors_;
};
} // namespace art::query
#endif

16
art/query/parse.cxx Normal file
View File

@@ -0,0 +1,16 @@
#include <art/query/parse.hxx>
#include <art/query/lexical-analyzer.hxx>
#include <art/query/syntactical-analyzer.hxx>
namespace art::query
{
query_t
try_parse(string const& q, parse_context_t& context)
{
string_lexical_analyzer_t lexer{q.begin(), q.end()};
return syntactical_analyzer_t{lexer, context}.try_parse();
}
} // namespace art::query

16
art/query/parse.hxx Normal file
View File

@@ -0,0 +1,16 @@
#ifndef art__query__parse_hxx_
#define art__query__parse_hxx_
#include <art/query/parse-context.hxx>
#include <art/query/query.hxx>
#include <art/query/types.hxx>
namespace art::query
{
query_t
try_parse(string const&, parse_context_t&);
} // namespace art::query
#endif

54
art/query/query.cxx Normal file
View File

@@ -0,0 +1,54 @@
#include <art/query/query.hxx>
namespace art::query
{
query_t::
query_t(optional<expression_t> e,
vector<warning_t> warnings,
vector<error_t> errors)
: expr_{std::move(e)},
warnings_{std::move(warnings)},
errors_{std::move(errors)}
{}
optional<expression_t> const&
query_t::
expr() const
{
return expr_;
}
vector<warning_t> const&
query_t::
warnings() const
{
return warnings_;
}
vector<error_t> const&
query_t::
errors() const
{
return errors_;
}
void
accept(query_t const& q, visitor_t& v)
{
if (q.expr()) {
accept(*q.expr(), v);
}
}
string
to_string(query_t const& q)
{
if (q.expr()) {
return to_string(*q.expr());
}
return string{};
}
} // namespace art::query

43
art/query/query.hxx Normal file
View File

@@ -0,0 +1,43 @@
#ifndef art__query__query_hxx_
#define art__query__query_hxx_
#include <art/query/error.hxx>
#include <art/query/expression.hxx>
#include <art/query/types.hxx>
#include <art/query/visitor.hxx>
namespace art::query
{
class query_t
{
public:
query_t(optional<expression_t> e,
vector<warning_t> warnings,
vector<error_t> errors);
optional<expression_t> const&
expr() const;
vector<warning_t> const&
warnings() const;
vector<error_t> const&
errors() const;
private:
optional<expression_t> expr_;
vector<warning_t> warnings_;
vector<error_t> errors_;
};
void
accept(query_t const& q, visitor_t& v);
string
to_string(query_t const& q);
} // namespace art::query
#endif

View File

@@ -0,0 +1,19 @@
#ifndef art__query__source_location_hxx_
#define art__query__source_location_hxx_
#include <art/query/types.hxx>
namespace art::query
{
struct source_location_t
{
string name;
uint32_t row{};
uint32_t column{};
};
} // namespace art::query
#endif

View File

@@ -0,0 +1,283 @@
#include <art/query/logical-and.hxx>
#include <art/query/logical-not.hxx>
#include <art/query/logical-or.hxx>
#include <art/query/parenthesized.hxx>
#include <art/query/syntactical-analyzer.hxx>
#include <art/query/tag.hxx>
#include <art/query/term.hxx>
namespace art::query
{
syntactical_analyzer_t::
syntactical_analyzer_t(lexical_analyzer_t& lexer,
parse_context_t& context)
: lexer_{lexer},
context_{context}
{}
lexical_analyzer_t&
syntactical_analyzer_t::
lexer()
{
return lexer_;
}
parse_context_t&
syntactical_analyzer_t::
context()
{
return context_;
}
query_t
syntactical_analyzer_t::
try_parse()
{
auto expr = try_parse_expression();
if (expr) {
auto last = lexer().peek();
if (last.type() != token_type_t::end) {
context().report_warning({{}, "trailing token at end of query"});
}
}
return query_t{
std::move(expr),
context().warnings(),
context().errors()
};
}
optional<expression_t>
syntactical_analyzer_t::
try_parse_primary_expression()
{
optional<expression_t> expr;
for (;;) {
auto t = lexer().peek();
if (t.type() == token_type_t::end) {
break;
}
else if (t.type() == token_type_t::logical_not) {
lexer().consume();
auto rhs = try_parse_primary_expression();
if (!rhs) {
context().report_warning({{}, "expected expression after NOT"});
break;
}
if (expr) {
expr = logical_and_t{
{}, *expr, logical_not_t{{}, *rhs}
};
}
else {
expr = logical_not_t{{}, *rhs};
}
}
else if (t.type() == token_type_t::simple_term) {
lexer().consume();
// If the next token is a ':' then this is a tag.
//
if (lexer().peek().type() == token_type_t::colon) {
lexer().consume();
// consume trailing colons as well.
//
while (lexer().peek().type() == token_type_t::colon) {
lexer().consume();
}
auto identifier = *t.value();
// Next expect a term or quoted term.
//
t = lexer().peek();
if (t.type() != token_type_t::simple_term &&
t.type() != token_type_t::quoted_term) {
context().report_warning({{}, "expected simple-term or quoted-term after tag" });
lexer().consume();
continue;
}
lexer().consume();
// Construct the term
//
term_t term{
{},
t.type() == token_type_t::simple_term ? term_t::simple : term_t::quoted,
*t.value()
};
if (expr) {
expr = logical_or_t{
{}, *expr, tag_t{{}, std::move(identifier), std::move(term)}
};
}
else {
expr = tag_t{{}, std::move(identifier), std::move(term)};
}
}
else if (expr) {
expr = logical_or_t{
{}, *expr, term_t{{}, term_t::simple, *t.value()}
};
}
else {
expr = term_t{{}, term_t::simple, *t.value()};
}
}
else if (t.type() == token_type_t::quoted_term) {
if (expr) {
expr = logical_or_t{
{}, *expr, term_t{{}, term_t::quoted, *t.value()}
};
}
else {
expr = term_t{{}, term_t::quoted, *t.value()};
}
lexer().consume();
}
else if (t.type() == token_type_t::open_parens) {
lexer().consume();
t = lexer().peek();
if (t.type() == token_type_t::close_parens) {
lexer().consume();
continue;
}
auto next_expr = try_parse_expression();
if (!next_expr) {
context().report_warning({{}, "expected expression inside parenthesis"});
lexer().consume();
continue;
}
next_expr = parenthesized_t{{}, *next_expr};
if (expr) {
expr = logical_or_t{{}, *expr, *next_expr};
}
else {
expr = next_expr;
}
t = lexer().peek();
if (t.type() != token_type_t::close_parens) {
context().report_warning({{}, "expected end parenthesis"});
lexer().consume();
continue;
}
lexer().consume();
}
else {
break;
}
}
return expr;
}
optional<expression_t>
syntactical_analyzer_t::
try_parse_logical_and()
{
auto lhs = try_parse_primary_expression();
if (!lhs) {
return nullopt;
}
while (lexer().peek().type() == token_type_t::logical_and) {
lexer().consume();
auto rhs = try_parse_primary_expression();
if (!rhs) {
context().report_warning({{}, "expected expression after AND"});
return lhs;
}
lhs = logical_and_t{{}, *lhs, *rhs};
}
return lhs;
}
optional<expression_t>
syntactical_analyzer_t::
try_parse_logical_or()
{
auto lhs = try_parse_logical_and();
if (!lhs)
return nullopt;
while (lexer().peek().type() == token_type_t::logical_or) {
lexer().consume();
auto rhs = try_parse_logical_and();
if (!rhs)
return nullopt;
lhs = logical_or_t{{}, *lhs, *rhs};
}
return lhs;
}
optional<expression_t>
syntactical_analyzer_t::
try_parse_expression()
{
auto invalid = [](token_t const& token)
{
if (token.type() == token_type_t::end) {
return false;
}
if (token.type() == token_type_t::simple_term) {
return false;
}
if (token.type() == token_type_t::quoted_term) {
return false;
}
if (token.type() == token_type_t::open_parens) {
return false;
}
return true;
};
// filter leading invalid tokens.
//
while (invalid(lexer().peek())) {
context().report_warning({{}, "invalid leading token"});
lexer().consume();
}
return try_parse_logical_or();
}
} // namespace art::query

View File

@@ -0,0 +1,48 @@
#ifndef art__query__syntactical_analyzer_hxx_
#define art__query__syntactical_analyzer_hxx_
#include <art/query/expression.hxx>
#include <art/query/lexical-analyzer.hxx>
#include <art/query/parse-context.hxx>
#include <art/query/query.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class syntactical_analyzer_t
{
public:
syntactical_analyzer_t(lexical_analyzer_t&, parse_context_t&);
lexical_analyzer_t&
lexer();
parse_context_t&
context();
query_t
try_parse();
private:
optional<expression_t>
try_parse_primary_expression();
optional<expression_t>
try_parse_logical_and();
optional<expression_t>
try_parse_logical_or();
optional<expression_t>
try_parse_expression();
private:
lexical_analyzer_t& lexer_;
parse_context_t& context_;
};
} // namespace art::query
#endif

48
art/query/tag.cxx Normal file
View File

@@ -0,0 +1,48 @@
#include <art/query/tag.hxx>
namespace art::query
{
tag_t::
tag_t(string identifier, term_t value)
: identifier_{std::move(identifier)},
value_{std::move(value)}
{}
tag_t::
tag_t(source_location_t origin,
string identifier,
term_t value)
: origin_{std::move(origin)},
identifier_{std::move(identifier)},
value_{std::move(value)}
{}
source_location_t const&
tag_t::
origin() const
{
return origin_;
}
string const&
tag_t::
identifier() const
{
return identifier_;
}
term_t const&
tag_t::
value() const
{
return value_;
}
string
to_string(tag_t const& e)
{
return e.identifier() + ":" + to_string(e.value());
}
} // namespace art::query

40
art/query/tag.hxx Normal file
View File

@@ -0,0 +1,40 @@
#ifndef art__query__tag_hxx_
#define art__query__tag_hxx_
#include <art/query/expression.hxx>
#include <art/query/source-location.hxx>
#include <art/query/term.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class tag_t
{
public:
tag_t(string, term_t);
tag_t(source_location_t, string, term_t);
source_location_t const&
origin() const;
string const&
identifier() const;
term_t const&
value() const;
private:
source_location_t origin_;
string identifier_;
term_t value_;
};
string
to_string(tag_t const& e);
} // namespace imperium::contract::query
#endif

53
art/query/term.cxx Normal file
View File

@@ -0,0 +1,53 @@
#include <art/query/term.hxx>
namespace art::query
{
term_t::
term_t(term_type_t type, string value)
: type_{type},
value_{std::move(value)}
{}
term_t::
term_t(source_location_t origin,
term_type_t type,
string value)
: origin_{std::move(origin)},
type_{type},
value_{std::move(value)}
{}
source_location_t const&
term_t::
origin() const
{
return origin_;
}
term_t::term_type_t
term_t::
type() const
{
return type_;
}
string const&
term_t::
value() const
{
return value_;
}
string
to_string(term_t const& term)
{
if (term.type() == term_t::simple)
return term.value();
// fixme: escape term.value()?
//
return "\"" + term.value() + "\"";
}
} // namespace art::query

45
art/query/term.hxx Normal file
View File

@@ -0,0 +1,45 @@
#ifndef art__query__term_hxx_
#define art__query__term_hxx_
#include <art/query/expression.hxx>
#include <art/query/source-location.hxx>
#include <art/query/types.hxx>
namespace art::query
{
class term_t
{
public:
enum term_type_t
{
simple,
quoted
};
term_t(term_type_t, string);
term_t(source_location_t, term_type_t, string);
source_location_t const&
origin() const;
term_type_t
type() const;
string const&
value() const;
private:
source_location_t origin_;
term_type_t type_;
string value_;
};
string
to_string(term_t const&);
} // namespace art::query
#endif

78
art/query/token.cxx Normal file
View File

@@ -0,0 +1,78 @@
#include <art/query/token.hxx>
namespace art::query
{
token_t::
token_t(token_type_t type, optional<string> value)
: type_{type},
value_{std::move(value)}
{}
token_type_t
token_t::
type() const
{
return type_;
}
optional<string> const&
token_t::
value() const
{
return value_;
}
std::ostream&
operator<<(std::ostream& o, token_t const& token)
{
switch (token.type()) {
case token_type_t::end:
o << "end";
break;
case token_type_t::simple_term:
o << "simple-term";
break;
case token_type_t::quoted_term:
o << "quoted-term";
break;
case token_type_t::colon:
o << "colon";
break;
case token_type_t::logical_and:
o << "logical-and";
break;
case token_type_t::logical_not:
o << "logical-not";
break;
case token_type_t::logical_or:
o << "logical-or";
break;
case token_type_t::open_parens:
o << "open-parens";
break;
case token_type_t::close_parens:
o << "close-parens";
break;
}
if (auto v = token.value(); v) {
o << ": " << *v;
}
o << '\n';
return o;
}
} // namespace art::query

52
art/query/token.hxx Normal file
View File

@@ -0,0 +1,52 @@
#ifndef art__query__token_hxx_
#define art__query__token_hxx_
#include <art/query/types.hxx>
#include <iostream>
namespace art::query
{
enum class token_type_t
{
end,
simple_term,
quoted_term,
colon,
logical_and,
logical_not,
logical_or,
open_parens,
close_parens
};
class token_t
{
public:
explicit
token_t(token_type_t, optional<string> = nullopt);
token_type_t
type() const;
optional<string> const&
value() const;
private:
token_type_t type_;
optional<string> value_;
};
std::ostream&
operator<<(std::ostream&, token_t const&);
} // namespace art::query
#endif

42
art/query/types.hxx Normal file
View File

@@ -0,0 +1,42 @@
#ifndef art__query__types_hxx_
#define art__query__types_hxx_
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
namespace art::query
{
using std::int8_t;
using std::int16_t;
using std::int32_t;
using std::int64_t;
using std::uint8_t;
using std::uint16_t;
using std::uint32_t;
using std::uint64_t;
using std::make_shared;
using std::shared_ptr;
using std::weak_ptr;
using std::optional;
using std::nullopt;
using std::vector;
using std::variant;
using std::string;
using strings = vector<string>;
using std::function;
} // namespace art::query
#endif

37
art/query/version.hxx.in Normal file
View File

@@ -0,0 +1,37 @@
#ifndef art__query__version_hxx_
#define art__query__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - code / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBART_QUERY_VERSION $libart_query.version.project_number$ULL
#define LIBART_QUERY_VERSION_STR "$libart_query.version.project$"
#define LIBART_QUERY_VERSION_ID "$libart_query.version.project_id$"
#define LIBART_QUERY_VERSION_FULL "$libart_query.version$"
#define LIBART_QUERY_VERSION_MAJOR $libart_query.version.major$
#define LIBART_QUERY_VERSION_MINOR $libart_query.version.minor$
#define LIBART_QUERY_VERSION_PATCH $libart_query.version.patch$
#define LIBART_QUERY_PRE_RELEASE $libart_query.version.pre_release$
#define LIBART_QUERY_SNAPSHOT_SN $libart_query.version.snapshot_sn$ULL
#define LIBART_QUERY_SNAPSHOT_ID "$libart_query.version.snapshot_id$"
#endif

39
art/query/visitor.hxx Normal file
View File

@@ -0,0 +1,39 @@
#ifndef art__query__visitor_hxx_
#define art__query__visitor_hxx_
namespace art::query
{
class visitor_t
{
public:
virtual
~visitor_t() noexcept = default;
virtual
void
visit_default() = 0;
protected:
visitor_t() = default;
};
template<typename V>
class basic_visitor_t
{
public:
virtual
void
visit(V const&) = 0;
protected:
basic_visitor_t() = default;
~basic_visitor_t() noexcept = default;
};
} // namespace art::query
#endif

4
build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@@ -0,0 +1,7 @@
project = libart-query
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@@ -0,0 +1,6 @@
$out_root/
{
include art/query/
}
export $out_root/art/query/$import.target

16
build/root.build Normal file
View File

@@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

5
buildfile Normal file
View File

@@ -0,0 +1,5 @@
./: {art/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

11
manifest Normal file
View File

@@ -0,0 +1,11 @@
: 1
name: libart-query
version: 0.1.0-a.0.z
language: c++
summary: libart-query C++ library
license: BSD-4-Clause
description-file: README.md
url: https://art.helloryan.se/
email: art@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0

2
repositories.manifest Normal file
View File

@@ -0,0 +1,2 @@
: 1
summary: libart-query project repository

8
tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@@ -0,0 +1 @@
./: {*/ -build/}