Skip to content

Commit

Permalink
Support for requires, conflicts, and provides
Browse files Browse the repository at this point in the history
This commit enables pc file properties of these forms to be parsed:
libfoo >= 1.0.0, libbar < 4.0.1

Note that CPS does not fully replicate what pc files support because
only a single version field exist for requirements, which indicates the
minimum version, i.e. a >= in pc files. Support for this has not yet
been implemented in cps-config so this implementation only parses
those values successfully without using them in any way.
  • Loading branch information
lunacd committed Aug 16, 2024
1 parent 0dc1aec commit 408f6e1
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 77 deletions.
41 changes: 30 additions & 11 deletions src/cps/pc_compat/pc.l
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,37 @@ str [^ \t\r\n#:=${}]+
blank [ \t\r]+
comment #[^\n]*

/* "Name" { return yy::parser::make_NAME(); }
"Version" { return yy::parser::make_VERSION(); }
"Description" { return yy::parser::make_DESCRIPTION(); }
"URL" { return yy::parser::make_URL(); }
"Cflags" { return yy::parser::make_CFLAGS(); }
"Cflags.private" { return yy::parser::make_CFLAGS_P(); }
"Libs" { return yy::parser::make_LIBS(); }
"Libs.private" { return yy::parser::make_LIBS_P(); } */

%%
":" { return yy::parser::make_COLON(); }
"=" { return yy::parser::make_EQ(); }
"\n" { return yy::parser::make_LF(); }
"$" { return yy::parser::make_DOLLAR(); }
"{" { return yy::parser::make_LBRACE(); }
"}" { return yy::parser::make_RBRACE(); }

{comment} {}
{blank} { return yy::parser::make_BLANK(yytext); }
{str} { return yy::parser::make_STR(yytext); }
<<EOF>> { return yy::parser::make_YYEOF(); }
":" { return yy::parser::make_COLON(); }
"=" { return yy::parser::make_EQ(); }
"<" { return yy::parser::make_LT(); }
"<=" { return yy::parser::make_LE(); }
"!=" { return yy::parser::make_NE(); }
">=" { return yy::parser::make_GE(); }
">" { return yy::parser::make_GT(); }
"\n" { return yy::parser::make_LF(); }
"$" { return yy::parser::make_DOLLAR(); }
"{" { return yy::parser::make_LBRACE(); }
"}" { return yy::parser::make_RBRACE(); }
"," { return yy::parser::make_COMMA(); }
"Requires" { return yy::parser::make_REQUIRES(); }
"Requires.private" { return yy::parser::make_REQUIRES_P(); }
"Conflicts" { return yy::parser::make_CONFLICTS(); }
"Provides" { return yy::parser::make_PROVIDES(); }

{comment} {}
{blank} { return yy::parser::make_BLANK(yytext); }
{str} { return yy::parser::make_STR(yytext); }
<<EOF>> { return yy::parser::make_YYEOF(); }
%%

namespace cps::pc_compat {
Expand Down
111 changes: 87 additions & 24 deletions src/cps/pc_compat/pc.y
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
%define parse.assert

%code requires {
#include <string>
#include <istream>
#include "cps/utils.hpp"
namespace cps::pc_compat {
class PcLoader;
}
#include "cps/pc_compat/pc_base.hpp"
}

// The parsing context.
Expand All @@ -26,25 +22,39 @@
%define parse.lac full

%code {
#include "cps/pc_compat/pc_loader.hpp"
#include "cps/pc_compat/pc_loader.hpp"
}

%define api.token.prefix {TOK_}
%token
COLON ":"
LF "\n"
EQ "="
DOLLAR "$"
LBRACE "{"
RBRACE "}"
COLON ":"
LF "\n"
EQ "="
LT "<"
LE "<="
NE "!="
GT ">"
GE ">="
DOLLAR "$"
LBRACE "{"
RBRACE "}"
COMMA ","
REQUIRES "Requires"
REQUIRES_P "Requires.private"
CONFLICTS "Conflicts"
PROVIDES "Provides"
;

%token <std::string> STR "str"
%token <std::string> BLANK "blank"
%nterm <std::string> literal
%nterm <std::string> variable
%nterm <std::string> literal_value
%nterm <std::string> name
%nterm <cps::pc_compat::VersionOperation> version_op_token
%nterm <cps::pc_compat::VersionOperation> version_op
%nterm <cps::pc_compat::PackageRequirement> package_requirement
%nterm <std::vector<cps::pc_compat::PackageRequirement>> package_requirements
%nterm <std::variant<std::string, std::vector<cps::pc_compat::PackageRequirement>>> literal_property

%printer { yyo << $$; } <*>;

Expand All @@ -60,9 +70,12 @@ lines:
%empty
| lines line;

// In this grammar, leading whitespace is handled by this rule.
// Everything else only takes care of trailing whitespace.
line:
"\n"
| statement "\n";
| statement "\n"
| "blank" statement "\n";

// Statement is a meaningful line, not including line feed
statement:
Expand All @@ -71,23 +84,65 @@ statement:

// property is a line that sets a property to a value
property:
name ":" literal_value { loader.properties.emplace($1, cps::utils::trim($3)); };
"Requires" colon package_requirements { loader.properties.emplace("Requires", $3); }
| "Requires.private" colon package_requirements { loader.properties.emplace("Requires.private", $3); }
| "Conflicts" colon package_requirements { loader.properties.emplace("Conflicts", $3); }
| "Provides" colon package_requirements { loader.properties.emplace("Provides", $3); }
| name colon literal_property { loader.properties.emplace($1, $3); }

// version_op_token captures all the valid tokens for version comparison
version_op_token:
"<" { $$ = cps::pc_compat::VersionOperation::LT; }
| "<=" { $$ = cps::pc_compat::VersionOperation::LE; }
| "=" { $$ = cps::pc_compat::VersionOperation::EQ; }
| "!=" { $$ = cps::pc_compat::VersionOperation::NE; }
| ">" { $$ = cps::pc_compat::VersionOperation::GT; }
| ">=" { $$ = cps::pc_compat::VersionOperation::GE; };

// version_op handles trailing space for version comparisons
version_op:
version_op_token
| version_op_token "blank" { $$ = $1; };

// package_requirement parses a package name, optionally followed by some version requirement
package_requirement:
name version_op "str" {
$$ = cps::pc_compat::PackageRequirement {
.package = $1,
.operation = $2,
.version = $3,
};
}
| package_requirement "blank" { $$ = $1; };

// package_requirements is a comma separated list of package_requirement
package_requirements:
package_requirement { $$ = std::vector{$1}; }
| package_requirements comma package_requirement {
$1.emplace_back($3);
$$ = $1;
}
| package_requirements comma "str" {
$1.emplace_back(cps::pc_compat::PackageRequirement {
.package = $3,
.operation = std::nullopt,
.version = std::nullopt,
});
$$ = $1;
};

// assignment is a line that sets a variable to a value
assignment:
name "=" literal_value { loader.variables.emplace($1, cps::utils::trim($3)); };
name "=" literal { loader.variables.emplace($1, cps::utils::trim($3)); };

// name handles surrounding spaces for a variable or property name
name:
"str"
| "blank" "str" { $$ = $2; }
| "str" "blank" { $$ = $1; }
| "blank" "str" "blank" { $$ = $2; };
| "str" "blank" { $$ = $1; };

// literal_value handles leading spaces.
literal_value:
literal
| "blank" literal { $$ = $2; };
// literal_property constructs a variant with the trimmed literal value
literal_property:
literal { $$ = cps::pc_compat::PcPropertyValue{std::in_place_type<std::string>, cps::utils::trim($1)}; };

// Literal is a literal string. This could contain trailing whitespace so trim the result before using.
literal:
Expand All @@ -97,10 +152,18 @@ literal:
| literal ":" { $$ = $1 + ":"; }
| literal "str" { $$ = $1 + $2; }
| literal variable { $$ = $1 + $2; }
| literal "blank" { $$ = $1 + " "; };
| literal "blank" { $$ = $1 + $2; };

variable:
"$" "{" "str" "}" { $$ = loader.variables[$3]; }

// colon and comma handles trailing whitespace
colon:
":"
| ":" "blank";
comma:
","
| "," "blank";
%%

void
Expand Down
37 changes: 37 additions & 0 deletions src/cps/pc_compat/pc_base.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
// Copyright © 2024 Haowen Liu

// This header is created to avoid writing non-trivial C++ code in pc.y.
// This header contains types and declarations that are needed by both loader and parser.

#pragma once

#include <optional>
#include <ostream>
#include <string>
#include <variant>
#include <vector>

namespace cps::pc_compat {
class PcLoader;

// The following types needs to be declared in the parser because
// PackageRequirement is the type of a non-terminal. bison needs
// to do a sizeof on this type and cannot do so with a forward
// declaration.
enum class VersionOperation { LT, LE, NE, EQ, GT, GE };
struct PackageRequirement {
std::string package;
std::optional<VersionOperation> operation;
std::optional<std::string> version;

// For parser debug output
friend std::ostream & operator<<(std::ostream & ost, const PackageRequirement & package_requirement);
};

// For parser debug output
std::ostream & operator<<(std::ostream & ost, const std::optional<VersionOperation> & version_operation);
std::ostream & operator<<(std::ostream & ost, const std::vector<PackageRequirement> & package_requirements);
std::ostream & operator<<(std::ostream & ost,
const std::variant<std::string, std::vector<PackageRequirement>> & property_value);
} // namespace cps::pc_compat
Loading

0 comments on commit 408f6e1

Please sign in to comment.