Files
ortools-clone/ortools/flatzinc/parser.yy
2025-11-05 12:05:41 +01:00

780 lines
27 KiB
Plaintext

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Renames all yy to orfz_ in public functions.
%define api.prefix {orfz_}
// List the parameter of the lexer.
%lex-param {void* scanner}
// Explicit list of the parameters of the orfz_parse() method (this also adds
// them to orfz_error(), in which we only need the scanner). Note that the
// parameter of orfz_lex() is defined below (see YYLEX_PARAM).
%parse-param {operations_research::fz::ParserContext* context}
%parse-param {operations_research::fz::Model* model}
%parse-param {bool* ok}
%parse-param {void* scanner}
// Specify a reentrant parser. (or-tools needs the api.pure declaration)
%define api.pure full
// Code to be exported in parser.tab.hh
%code requires {
#if !defined(ORTOOLS_FLATZINC_FLATZINC_TAB_HH_)
#define ORTOOLS_FLATZINC_FLATZINC_TAB_HH_
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "ortools/flatzinc/parser_util.h"
// Tells flex to use the LexerInfo class to communicate with the bison parser.
typedef operations_research::fz::LexerInfo YYSTYPE;
// Defines the parameter to the orfz_lex() call from the orfz_parse() method.
#define YYLEX_PARAM scanner
#endif // ORTOOLS_FLATZINC_FLATZINC_TAB_HH_
} // code requires
// Code in the implementation file.
%code {
#include <cstdint>
#include "absl/log/check.h"
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "ortools/flatzinc/parser_util.cc"
using operations_research::fz::Annotation;
using operations_research::fz::Argument;
using operations_research::fz::Constraint;
using operations_research::fz::ConvertAsIntegerOrDie;
using operations_research::fz::Domain;
using operations_research::fz::Variable;
using operations_research::fz::Lookup;
using operations_research::fz::Model;
using operations_research::fz::SolutionOutputSpecs;
using operations_research::fz::ParserContext;
using operations_research::fz::VarRefOrValue;
} // code
%define parse.error verbose
// Type declarations.
// The lexer, defined in the ./flazinc.lex file, does the low-level parsing
// of string tokens and converts each of them them into a YACC token. A YACC
// token has a type (VAR, IVALUE, const_literal) and optionally a value,
// stored in a token-specific field of a LexerInfo instance dedicated to this
// token. Eg. "26" is converted into an YACC token of type IVALUE, with value
// 26.
// YACC tokens that come from the lexer are called "terminal"; as opposed to
// tokens that are defined within this .yy file as combinations of other tokens.
// Single characters like ';' are also accepted as YACC tokens.
// Multi-characters constants (eg. "::") aren't, which is why we need to
// define them here.
//
// Here are the terminal, valueless tokens. See the .lex file to see where
// they come from.
%token ARRAY TOKEN_BOOL CONSTRAINT TOKEN_FLOAT TOKEN_INT MAXIMIZE MINIMIZE OF
%token PREDICATE SATISFY SET SOLVE VAR DOTDOT COLONCOLON
// Here are the terminal, value-carrying tokens, preceded by the name of the
// LexerInfo field in which their value is stored (eg. the value of a IVALUE
// token is stored in LexerInfo::integer_value).
%token <integer_value> IVALUE
%token <string_value> SVALUE IDENTIFIER
%token <double_value> DVALUE
// Here are the non-terminal, value-carrying rules.
// Again they are preceded by the name of the LexerInfo field in which
// their value is stored. You need the %type declaration to associate a field
// with a non-terminal rule.
%type <integer_value> integer
%type <domain> domain const_literal int_domain float_domain set_domain
%type <domains> const_literals
%type <integers> integers
%type <double_value> float
%type <doubles> floats
%type <args> arguments
%type <arg> argument
%type <var_or_value> optional_var_or_value var_or_value
%type <var_or_value_array> optional_var_or_value_array var_or_value_array
%type <annotation> annotation
%type <annotations> annotations annotation_arguments
%%
//---------------------------------------------------------------------------
// Model top-level
//---------------------------------------------------------------------------
model: predicates variable_or_constant_declarations constraints solve ';'
//---------------------------------------------------------------------------
// Parsing predicates (We just ignore them, but we need to parse them anyway)
//---------------------------------------------------------------------------
predicates:
predicates predicate ';'
// We help the parser terminate in case it fails to parse something,
// by injecting an "error" clause in the first top-level section
// (i.e. predicates). This arcane token does that.
| predicates error ';' { yyerrok; } // Minimal error recovery.
| /* empty */
// TODO(user): Implement better error recovery.
predicate:
PREDICATE IDENTIFIER '(' predicate_arguments ')'
predicate_arguments: // Cannot be empty.
predicate_argument ',' predicate_arguments
| predicate_argument
predicate_argument:
domain ':' IDENTIFIER
| VAR domain ':' IDENTIFIER
| ARRAY '[' predicate_array_argument ']' OF domain ':' IDENTIFIER
| ARRAY '[' predicate_array_argument ']' OF VAR domain ':' IDENTIFIER
predicate_array_argument:
predicate_ints
| IVALUE DOTDOT IVALUE
predicate_ints:
TOKEN_INT ',' predicate_ints
| TOKEN_INT
//---------------------------------------------------------------------------
// Parsing variables or constants (named objects).
//---------------------------------------------------------------------------
variable_or_constant_declarations:
variable_or_constant_declarations variable_or_constant_declaration ';'
| /* empty */
variable_or_constant_declaration:
domain ':' IDENTIFIER annotations '=' const_literal {
// Declaration of a (named) constant: we simply register it in the
// parser's context, and don't store it in the model.
const Domain& domain = $1;
const std::string& identifier = $3;
const Domain& assignment = $6;
std::vector<Annotation>* const annotations = $4;
if (!assignment.HasOneValue()) {
// TODO(user): Check that the assignment is included in the domain.
context->domain_map[identifier] = assignment;
} else {
const int64_t value = assignment.values.front();
CHECK(domain.Contains(value));
context->integer_map[identifier] = value;
}
delete annotations;
}
| ARRAY '[' IVALUE DOTDOT IVALUE ']' OF int_domain ':' IDENTIFIER
annotations '=' '[' integers ']' {
std::vector<Annotation>* const annotations = $11;
// Declaration of a (named) constant array. See rule right above.
CHECK_EQ($3, 1) << "Only [1..n] array are supported here.";
const int64_t num_constants = $5;
const std::string& identifier = $10;
const std::vector<int64_t>* const assignments = $14;
CHECK(assignments != nullptr);
CHECK_EQ(num_constants, assignments->size());
// TODO(user): CHECK all values within domain.
context->integer_array_map[identifier] = *assignments;
delete assignments;
delete annotations;
}
| ARRAY '[' IVALUE DOTDOT IVALUE ']' OF int_domain ':' IDENTIFIER
annotations '=' '[' ']' {
std::vector<Annotation>* const annotations = $11;
// Declaration of a (named) constant array. See rule right above.
CHECK_EQ($3, 1) << "Only [1..n] array are supported here.";
const int64_t num_constants = $5;
CHECK_EQ(num_constants, 0) << "Empty arrays should have a size of 0";
const std::string& identifier = $10;
context->integer_array_map[identifier] = std::vector<int64_t>();
delete annotations;
}
| ARRAY '[' IVALUE DOTDOT IVALUE ']' OF float_domain ':' IDENTIFIER
annotations '=' '[' floats ']' {
std::vector<Annotation>* const annotations = $11;
// Declaration of a (named) constant array. See rule right above.
CHECK_EQ($3, 1) << "Only [1..n] array are supported here.";
const int64_t num_constants = $5;
const std::string& identifier = $10;
const std::vector<double>* const assignments = $14;
CHECK(assignments != nullptr);
CHECK_EQ(num_constants, assignments->size());
// TODO(user): CHECK all values within domain.
context->float_array_map[identifier] = *assignments;
delete assignments;
delete annotations;
}
| ARRAY '[' IVALUE DOTDOT IVALUE ']' OF float_domain ':' IDENTIFIER
annotations '=' '[' ']' {
std::vector<Annotation>* const annotations = $11;
// Declaration of a (named) constant array. See rule right above.
CHECK_EQ($3, 1) << "Only [1..n] array are supported here.";
const int64_t num_constants = $5;
CHECK_EQ(num_constants, 0) << "Empty arrays should have a size of 0";
const std::string& identifier = $10;
context->float_array_map[identifier] = std::vector<double>();
delete annotations;
}
| ARRAY '[' IVALUE DOTDOT IVALUE ']' OF set_domain ':' IDENTIFIER
annotations '=' '[' const_literals ']' {
// Declaration of a (named) set constant array: See rule above.
CHECK_EQ($3, 1) << "Only [1..n] array are supported here.";
const int64_t num_domains = $5;
// const Domain& domain = $8;
const std::string& identifier = $10;
const std::vector<Domain>* const assignments = $14;
const std::vector<Annotation>* const annotations = $11;
CHECK(assignments != nullptr);
CHECK_EQ(num_domains, assignments->size());
context->domain_array_map[identifier] = *assignments;
delete assignments;
delete annotations;
}
| VAR domain ':' IDENTIFIER annotations optional_var_or_value {
// Declaration of a variable. If it's unassigned or assigned to a
// constant, we'll create a new var stored in the model. If it's
// assigned to another variable x then we simply adjust that
// existing variable x according to the current (re-)declaration.
const Domain& domain = $2;
const std::string& identifier = $4;
std::vector<Annotation>* const annotations = $5;
const VarRefOrValue& assignment = $6;
const bool introduced = ContainsId(annotations, "var_is_introduced") ||
absl::StartsWith(identifier, "X_INTRODUCED");
Variable* var = nullptr;
if (!assignment.defined) {
var = model->AddVariable(identifier, domain, introduced);
CHECK_EQ(var->domain.is_a_set, domain.is_a_set);
} else if (assignment.variable == nullptr) { // A constant.
if (assignment.is_float) {
// Assigned to an float constant.
const double value = assignment.float_value;
var = model->AddVariable(
identifier, Domain::FloatValue(value), introduced);
} else if (assignment.is_domain) {
// TODO(user): Check that the assignment is included in the domain.
// We force the set domain because we can have the following code:
// var set of {0,18}: x = {0,18};
// where the second domain is not tagged as a set.
//
// Assigned to a set constant.
var = model->AddVariable(
identifier, assignment.domain, introduced, domain.is_a_set);
} else {
CHECK(domain.Contains(assignment.value));
var = model->AddVariable(
identifier, Domain::IntegerValue(assignment.value), introduced);
}
} else { // a variable.
var = assignment.variable;
var->Merge(identifier, domain, introduced);
}
// We also register the variable in the parser's context, and add some
// output to the model if needed.
context->variable_map[identifier] = var;
if (ContainsId(annotations, "output_var")) {
model->AddOutput(
SolutionOutputSpecs::SingleVariable(identifier, var,
domain.display_as_boolean));
}
delete annotations;
}
| ARRAY '[' IVALUE DOTDOT IVALUE ']' OF VAR domain ':' IDENTIFIER
annotations optional_var_or_value_array {
// Declaration of a "variable array": these is exactly like N simple
// variable declarations, where the identifier for declaration #i is
// IDENTIFIER[i] (1-based index).
CHECK_EQ($3, 1);
const int64_t num_vars = $5;
const Domain& domain = $9;
const std::string& identifier = $11;
std::vector<Annotation>* const annotations = $12;
std::vector<VarRefOrValue>* const assignments = $13;
CHECK(assignments == nullptr || assignments->size() == num_vars);
const bool introduced = ContainsId(annotations, "var_is_introduced") ||
absl::StartsWith(identifier, "X_INTRODUCED");
std::vector<Variable*> vars(num_vars, nullptr);
for (int i = 0; i < num_vars; ++i) {
const std::string var_name = absl::StrFormat("%s[%d]", identifier, i + 1);
if (assignments == nullptr) {
vars[i] = model->AddVariable(var_name, domain, introduced);
} else if ((*assignments)[i].variable == nullptr) { // A constant.
if ((*assignments)[i].is_float) {
// Assigned to an float constant.
const double value = (*assignments)[i].float_value;
vars[i] =
model->AddVariable(var_name, Domain::FloatValue(value), introduced);
} else if ((*assignments)[i].is_domain) {
// TODO(user): Check that the assignment is included in the domain.
// We force the set domain because we can have the following code:
// var set of {0,18}: x = {0,18};
// where the second domain is not tagged as a set.
//
// Assigned to a set constant.
vars[i] = model->AddVariable(
var_name, (*assignments)[i].domain, introduced, domain.is_a_set);
} else {
// Assigned to an integer constant.
const int64_t value = (*assignments)[i].value;
CHECK(domain.Contains(value));
vars[i] = model->AddVariable(
var_name, Domain::IntegerValue(value), introduced);
}
} else {
Variable* const var = (*assignments)[i].variable;
CHECK(var != nullptr);
vars[i] = var;
vars[i]->Merge(var_name, domain, introduced);
}
}
delete assignments;
// Register the variable array on the context.
context->variable_array_map[identifier] = vars;
// We parse the annotations to build an output object if
// needed. It's a bit more convoluted than the simple variable
// output.
if (annotations != nullptr) {
for (int i = 0; i < annotations->size(); ++i) {
const Annotation& ann = (*annotations)[i];
if (ann.IsFunctionCallWithIdentifier("output_array")) {
// We have found an output annotation.
CHECK_EQ(1, ann.annotations.size());
CHECK_EQ(Annotation::ANNOTATION_LIST, ann.annotations.back().type);
const Annotation& list = ann.annotations.back();
// Let's build the vector of bounds.
std::vector<SolutionOutputSpecs::Bounds> bounds;
for (int a = 0; a < list.annotations.size(); ++a) {
const Annotation& bound = list.annotations[a];
CHECK_EQ(Annotation::INTERVAL, bound.type);
bounds.emplace_back(
SolutionOutputSpecs::Bounds(bound.interval_min, bound.interval_max));
}
// We add the output information.
model->AddOutput(
SolutionOutputSpecs::MultiDimensionalArray(identifier, bounds, vars,
domain.display_as_boolean));
}
}
delete annotations;
}
}
optional_var_or_value:
'=' var_or_value { $$ = $2; }
| /*empty*/ { $$ = VarRefOrValue::Undefined(); }
optional_var_or_value_array:
'=' '[' var_or_value_array ']' { $$ = $3; }
| '=' '[' ']' { $$ = nullptr; }
| /*empty*/ { $$ = nullptr; }
var_or_value_array: // Cannot be empty.
var_or_value_array ',' var_or_value {
$$ = $1;
$$->push_back($3);
}
| var_or_value {
$$ = new std::vector<VarRefOrValue>();
$$->push_back($1);
}
var_or_value:
IVALUE { $$ = VarRefOrValue::Value($1); } // An integer value.
| DVALUE { $$ = VarRefOrValue::FloatValue($1); } // A float value.
| IVALUE DOTDOT IVALUE {
$$ = VarRefOrValue::DomainValue(Domain::Interval($1, $3));
}
| '{' integers '}' {
CHECK($2 != nullptr);
$$ = VarRefOrValue::DomainValue(Domain::IntegerList(std::move(*$2)));
delete $2;
}
| IDENTIFIER {
// A reference to an existing integer constant or variable.
const std::string& id = $1;
if (context->integer_map.contains(id)) {
$$ = VarRefOrValue::Value(context->integer_map.at(id));
} else if (context->float_map.contains(id)) {
$$ = VarRefOrValue::FloatValue(context->float_map.at(id));
} else if (context->variable_map.contains(id)) {
$$ = VarRefOrValue::VarRef(context->variable_map.at(id));
} else {
LOG(ERROR) << "Unknown symbol " << id;
$$ = VarRefOrValue::Undefined();
*ok = false;
}
}
| IDENTIFIER '[' IVALUE ']' {
// A given element of an existing constant array or variable array.
const std::string& id = $1;
const int64_t value = $3;
if (context->integer_array_map.contains(id)) {
$$ = VarRefOrValue::Value(
Lookup(context->integer_array_map.at(id), value));
} else if (context->float_array_map.contains(id)) {
$$ = VarRefOrValue::FloatValue(
Lookup(context->float_array_map.at(id), value));
} else if (context->variable_array_map.contains(id)) {
$$ = VarRefOrValue::VarRef(
Lookup(context->variable_array_map.at(id), value));
} else {
LOG(ERROR) << "Unknown symbol " << id;
$$ = VarRefOrValue::Undefined();
*ok = false;
}
}
int_domain:
TOKEN_BOOL { $$ = Domain::Boolean(); }
| TOKEN_INT { $$ = Domain::AllInt64(); }
| IVALUE DOTDOT IVALUE { $$ = Domain::Interval($1, $3); }
| '{' integers '}' {
CHECK($2 != nullptr);
$$ = Domain::IntegerList(std::move(*$2));
delete $2;
}
set_domain:
SET OF TOKEN_BOOL { $$ = Domain::SetOfBoolean(); }
| SET OF TOKEN_INT { $$ = Domain::SetOfAllInt64(); }
| SET OF IVALUE DOTDOT IVALUE { $$ = Domain::SetOfInterval($3, $5); }
| SET OF '{' integers '}' {
CHECK($4 != nullptr);
$$ = Domain::SetOfIntegerList(std::move(*$4));
delete $4;
}
float_domain:
TOKEN_FLOAT { $$ = Domain::AllFloats(); }
| DVALUE DOTDOT DVALUE {
$$ = Domain::FloatInterval($1, $3);
}
domain:
int_domain { $$ = $1; }
| set_domain { $$ = $1; }
| float_domain { $$ = $1; }
integers:
integers ',' integer { $$ = $1; $$->emplace_back($3); }
| integer { $$ = new std::vector<int64_t>(); $$->emplace_back($1); }
integer:
IVALUE { $$ = $1; }
| IDENTIFIER { $$ = context->integer_map.at($1); }
| IDENTIFIER '[' IVALUE ']' {
$$ = Lookup(context->integer_array_map.at($1), $3);
}
floats:
floats ',' float { $$ = $1; $$->emplace_back($3); }
| float { $$ = new std::vector<double>(); $$->emplace_back($1); }
float:
DVALUE { $$ = $1; }
| IDENTIFIER { $$ = context->float_map.at($1); }
| IDENTIFIER '[' IVALUE ']' {
$$ = Lookup(context->float_array_map.at($1), $3);
}
const_literal:
IVALUE { $$ = Domain::IntegerValue($1); }
| IVALUE DOTDOT IVALUE { $$ = Domain::Interval($1, $3); }
| '{' integers '}' {
CHECK($2 != nullptr);
$$ = Domain::IntegerList(std::move(*$2));
delete $2;
}
| '{' '}' { $$ = Domain::EmptyDomain(); }
| DVALUE {
$$ = Domain::FloatValue($1);
}
| IDENTIFIER { $$ = Domain::IntegerValue(context->integer_map.at($1)); }
| IDENTIFIER '[' IVALUE ']' {
$$ = Domain::IntegerValue(
Lookup(context->integer_array_map.at($1), $3));
}
const_literals:
const_literals ',' const_literal {
$$ = $1;
$$->emplace_back($3);
}
| const_literal { $$ = new std::vector<Domain>(); $$->emplace_back($1); }
//---------------------------------------------------------------------------
// Parsing constraints
//---------------------------------------------------------------------------
constraints: constraints constraint ';'
| /* empty */
constraint :
CONSTRAINT IDENTIFIER '(' arguments ')' annotations {
const std::string& identifier = $2;
CHECK($4 != nullptr) << "Missing argument in constraint";
const std::vector<Argument>& arguments = *$4;
std::vector<Annotation>* const annotations = $6;
model->AddConstraint(identifier, arguments, ContainsId(annotations, "domain"),
ContainsId(annotations, "symmetry_breaking"),
ContainsId(annotations, "redundant"));
delete annotations;
delete $4;
}
arguments:
arguments ',' argument { $$ = $1; $$->emplace_back($3); }
| argument { $$ = new std::vector<Argument>(); $$->emplace_back($1); }
argument:
IVALUE { $$ = Argument::IntegerValue($1); }
| DVALUE { $$ = Argument::FloatValue($1); }
| SVALUE { $$ = Argument::VoidArgument(); }
| IVALUE DOTDOT IVALUE { $$ = Argument::Interval($1, $3); }
| '{' integers '}' {
CHECK($2 != nullptr);
$$ = Argument::IntegerList(std::move(*$2));
delete $2;
}
| IDENTIFIER {
const std::string& id = $1;
if (context->integer_map.contains(id)) {
$$ = Argument::IntegerValue(context->integer_map.at(id));
} else if (context->integer_array_map.contains(id)) {
$$ = Argument::IntegerList(context->integer_array_map.at(id));
} else if (context->float_map.contains(id)) {
const double d = context->float_map.at(id);
$$ = Argument::FloatValue(d);
} else if (context->float_array_map.contains(id)) {
const auto& double_values = context->float_array_map.at(id);
$$ = Argument::FloatList(std::move(double_values));
} else if (context->variable_map.contains(id)) {
$$ = Argument::VarRef(context->variable_map.at(id));
} else if (context->variable_array_map.contains(id)) {
$$ = Argument::VarRefArray(context->variable_array_map.at(id));
} else if (context->domain_map.contains(id)) {
const Domain& d = context->domain_map.at(id);
$$ = Argument::FromDomain(d);
} else {
CHECK(context->domain_array_map.contains(id)) << "Unknown identifier: "
<< id;
const std::vector<Domain>& d = context->domain_array_map.at(id);
$$ = Argument::DomainList(d);
}
}
| IDENTIFIER '[' IVALUE ']' {
const std::string& id = $1;
const int64_t index = $3;
if (context->integer_array_map.contains(id)) {
$$ = Argument::IntegerValue(
Lookup(context->integer_array_map.at(id), index));
} else if (context->variable_array_map.contains(id)) {
$$ = Argument::VarRef(
Lookup(context->variable_array_map.at(id), index));
} else {
CHECK(context->domain_array_map.contains(id))
<< "Unknown identifier: " << id;
const Domain& d =
Lookup(context->domain_array_map.at(id), index);
$$ = Argument::FromDomain(d);
}
}
| '[' var_or_value_array ']' {
std::vector<VarRefOrValue>* const arguments = $2;
CHECK(arguments != nullptr);
bool has_variables = false;
bool has_floats = false;
for (int i = 0; i < arguments->size(); ++i) {
if ((*arguments)[i].variable != nullptr) {
has_variables = true;
}
if ((*arguments)[i].is_float) {
has_floats = true;
}
}
if (has_variables) {
std::vector<Variable*> vars;
vars.reserve(arguments->size());
for (int i = 0; i < arguments->size(); ++i) {
const VarRefOrValue data = (*arguments)[i];
if (data.variable != nullptr) {
vars.push_back(data.variable);
} else if (!data.is_float) {
vars.push_back(model->AddConstant(data.value));
} else {
vars.push_back(model->AddFloatConstant(data.float_value));
}
}
$$ = Argument::VarRefArray(std::move(vars));
} else if (has_floats) {
std::vector<double> values;
values.reserve(arguments->size());
for (const VarRefOrValue& data : *arguments) {
if (data.is_float) {
values.push_back(data.float_value);
} else {
values.push_back(data.value);
}
}
$$ = Argument::FloatList(std::move(values));
} else {
std::vector<int64_t> values;
values.reserve(arguments->size());
for (const VarRefOrValue& data : *arguments) {
values.push_back(data.value);
}
$$ = Argument::IntegerList(std::move(values));
}
delete arguments;
}
| '[' ']' {
$$ = Argument::VoidArgument();
}
//---------------------------------------------------------------------------
// Parsing annotations
//---------------------------------------------------------------------------
annotations:
annotations COLONCOLON annotation {
$$ = $1 != nullptr ? $1 : new std::vector<Annotation>();
$$->emplace_back($3);
}
| /* empty */ { $$ = nullptr; }
annotation_arguments: // Cannot be empty.
annotation_arguments ',' annotation { $$ = $1; $$->emplace_back($3); }
| annotation { $$ = new std::vector<Annotation>(); $$->emplace_back($1); }
annotation:
IVALUE DOTDOT IVALUE { $$ = Annotation::Interval($1, $3); }
| '{' integers '}' {
CHECK($2 != nullptr);
$$ = Annotation::IntegerList(std::move(*$2));
delete $2;
}
| IVALUE { $$ = Annotation::IntegerValue($1); }
| SVALUE { $$ = Annotation::String($1); }
| IDENTIFIER {
const std::string& id = $1;
if (context->variable_map.contains(id)) {
$$ = Annotation::VarRef(context->variable_map.at(id));
} else if (context->variable_array_map.contains(id)) {
$$ = Annotation::VarRefArray(context->variable_array_map.at(id));
} else if (context->integer_map.contains(id)) {
$$ = Annotation::IntegerValue(context->integer_map.at(id));
} else if (context->integer_array_map.contains(id)) {
$$ = Annotation::IntegerList(context->integer_array_map.at(id));
} else {
$$ = Annotation::Identifier(id);
}
}
| IDENTIFIER '(' annotation_arguments ')' {
std::vector<Annotation>* const annotations = $3;
if (annotations != nullptr) {
$$ = Annotation::FunctionCallWithArguments($1, std::move(*annotations));
delete annotations;
} else {
$$ = Annotation::FunctionCall($1);
}
}
| IDENTIFIER '[' IVALUE ']' {
CHECK(context->variable_array_map.contains($1))
<< "Unknown identifier: " << $1;
$$ = Annotation::VarRef(
Lookup(context->variable_array_map.at($1), $3));
}
| '[' annotation_arguments ']' {
std::vector<Annotation>* const annotations = $2;
if (annotations != nullptr && !annotations->empty()) {
bool all_integers = true;
bool all_vars = true;
for (const Annotation& ann : *annotations) {
if (ann.type != Annotation::INT_VALUE) all_integers = false;
if (ann.type != Annotation::VAR_REF) all_vars = false;
}
if (all_integers) {
std::vector<int64_t> values;
for (const Annotation& ann : *annotations) {
values.push_back(ann.interval_min);
}
$$ = Annotation::IntegerList(values);
} else if (all_vars) {
std::vector<Variable*> vars;
for (const Annotation& ann : *annotations) {
vars.push_back(ann.variables[0]);
}
$$ = Annotation::VarRefArray(vars);
} else {
$$ = Annotation::AnnotationList(std::move(*annotations));
}
delete annotations;
} else {
$$ = Annotation::Empty();
}
}
| '[' ']' {
$$ = Annotation::Empty();
}
//---------------------------------------------------------------------------
// Parsing solve declaration.
//---------------------------------------------------------------------------
solve:
SOLVE annotations SATISFY {
if ($2 != nullptr) {
model->Satisfy(std::move(*$2));
delete $2;
} else {
model->Satisfy(std::vector<Annotation>());
}
}
| SOLVE annotations MINIMIZE argument {
Variable* obj_var = $4.type == Argument::VAR_REF
? $4.Var()
: model->AddConstant($4.Value());
if ($2 != nullptr) {
model->Minimize(obj_var, std::move(*$2));
delete $2;
} else {
model->Minimize(obj_var, std::vector<Annotation>());
}
}
| SOLVE annotations MAXIMIZE argument {
Variable* obj_var = $4.type == Argument::VAR_REF
? $4.Var()
: model->AddConstant($4.Value());
if ($2 != nullptr) {
model->Maximize(obj_var, std::move(*$2));
delete $2;
} else {
model->Maximize(obj_var, std::vector<Annotation>());
}
}
%%