/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Jip J. Dekker */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ %define api.pure %define api.header.include {} %{ #include #include #include //Anonymous struct for when yyparse is exported typedef struct MZAContext MZAContext; #include using namespace MiniZinc; typedef struct yy_buffer_state *YY_BUFFER_STATE; YY_BUFFER_STATE mza_yy_scan_string ( const char* yy_str ); extern int yylex(YYSTYPE*, YYLTYPE*); extern FILE* yyin; struct ProcPatch { int code; BytecodeProc::Mode mode; std::vector>> patch; ProcPatch(int code0, BytecodeProc::Mode mode0, std::vector>> patch0) : code(code0), mode(mode0), patch(std::move(patch0)) {} }; typedef struct MZAContext { std::vector& procs; std::unordered_map& proc_map; int& max_glob; std::vector to_patch; BytecodeStream proc_body; std::unordered_map labels; std::vector> patch_labels; std::vector>> patch_procs; } MZAContext; void yyerror(YYLTYPE* location, MZAContext& ctx, const char* s); %} %union { int iValue; char* sValue; MiniZinc::BytecodeStream::Instr bValue; std::list* liValue; std::list* sliValue; } %parse-param {MZAContext& ctx} %locations %define parse.error verbose %token MZA_INT %token MZA_REG %token MZA_MODE %token MZA_CTX %token MZA_ID %token MZA_COLON ":" %token MZA_DELAY "D" %token MZA_GLOBAL "GLOBAL" %token MZA_ADDI %token MZA_SUBI %token MZA_MULI %token MZA_DIVI %token MZA_MODI %token MZA_INCI %token MZA_DECI %token MZA_IMMI %token MZA_CLEAR %token MZA_LOAD_GLOBAL %token MZA_STORE_GLOBAL %token MZA_MOV %token MZA_JMP %token MZA_JMPIF %token MZA_JMPIFNOT %token MZA_EQI %token MZA_LTI %token MZA_LEI %token MZA_AND %token MZA_OR %token MZA_NOT %token MZA_XOR %token MZA_ISPAR %token MZA_ISEMPTY %token MZA_LENGTH %token MZA_GET_VEC %token MZA_GET_ARRAY %token MZA_LB %token MZA_UB %token MZA_DOM %token MZA_MAKE_SET %token MZA_DIFF %token MZA_INTERSECTION %token MZA_UNION %token MZA_INTERSECT_DOMAIN %token MZA_OPEN_AGGREGATION %token MZA_CLOSE_AGGREGATION %token MZA_SIMPLIFY_LIN %token MZA_PUSH %token MZA_POP %token MZA_POST %token MZA_RET %token MZA_CALL %token MZA_BUILTIN %token MZA_TCALL %token MZA_ITER_ARRAY %token MZA_ITER_VEC %token MZA_ITER_RANGE %token MZA_ITER_NEXT %token MZA_ITER_BREAK %token MZA_TRACE %token MZA_ABORT %type delay instruction mode %type instr instrI instrIR instrR instrRR instrRS instrRRR instrRRS instrRRIRRR instrS %type label %type registers %type labels %start procedures %% procedures: /* empty */ | procedures procedure procedure: ":" MZA_ID ":" mode MZA_INT delay instructions { // Patch jumps with recorded labels for (auto& cl : ctx.patch_labels) { if (ctx.labels.find(cl.second) == ctx.labels.end()) { throw Error("Error: label " + cl.second + " not found\n"); } ctx.proc_body.patchAddress(cl.first, ctx.labels[cl.second]); } ctx.labels.clear(); ctx.patch_labels.clear(); ctx.proc_body.setNArgs($5); // Add ABORT instruction for error management if (ctx.proc_body.size() > 0) { ctx.proc_body.addInstr(BytecodeStream::ABORT); } // Store procedure in the correct place auto mode = static_cast($4); auto it = ctx.proc_map.find($2); if (it != ctx.proc_map.end()) { BytecodeProc& bcp = ctx.procs[it->second]; if (bcp.mode[mode].size() > 0) { throw Error("Error: procedure " + std::string($2) + " already defined before with the same mode\n"); } if (bcp.nargs != $5 ) { throw Error("Error: procedure " + std::string($2) + " already defined before with different number of arguments\n"); } bcp.mode[$4] = ctx.proc_body; ctx.to_patch.emplace_back(it->second, mode, ctx.patch_procs); } else { BytecodeProc bcp; bcp.name = $2; bcp.mode[mode] = ctx.proc_body; bcp.nargs = $5; bcp.delay = $6; ctx.proc_map.emplace($2, ctx.procs.size()); ctx.to_patch.emplace_back(ctx.procs.size(), mode, ctx.patch_procs); ctx.procs.push_back(bcp); } ctx.proc_body = BytecodeStream(); ctx.patch_procs.clear(); } mode: /* empty */ { $$ = BytecodeProc::FUN; } | MZA_MODE delay: /* empty */ { $$ = 0; } | MZA_DELAY { $$ = 1; } instructions: /* empty */ | instructions labeled_instr labeled_instr: labels instruction { for (auto l : *$1) { ctx.labels.emplace(l, $2); } delete $1; } labels: /* empty */ { $$ = new std::list(); } | labels label { $1->push_back($2); $$ = $1; } label: MZA_ID ":" instruction: instr { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); } | instrI MZA_INT { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addIntVal($2); } | instrIR MZA_INT MZA_REG { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addIntVal($2); ctx.proc_body.addReg($3); } | instrR MZA_REG { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2); } | instrRR MZA_REG MZA_REG { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2); ctx.proc_body.addReg($3); } | instrRS MZA_REG MZA_ID { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2); ctx.patch_labels.emplace_back(ctx.proc_body.size(), $3); ctx.proc_body.addSmallInt(0); } | instrRRS MZA_REG MZA_REG MZA_ID { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2); ctx.proc_body.addReg($3); ctx.patch_labels.emplace_back(ctx.proc_body.size(), $4); ctx.proc_body.addSmallInt(0); } | instrRRR MZA_REG MZA_REG MZA_REG { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2); ctx.proc_body.addReg($3); ctx.proc_body.addReg($4); } | instrRRIRRR MZA_REG MZA_REG MZA_INT MZA_REG MZA_REG MZA_REG { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2); ctx.proc_body.addReg($3); ctx.proc_body.addIntVal($4); ctx.proc_body.addReg($5); ctx.proc_body.addReg($6); ctx.proc_body.addReg($7); } | instrS MZA_ID { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.patch_labels.emplace_back(ctx.proc_body.size(), $2); ctx.proc_body.addSmallInt(0); } | MZA_CALL MZA_MODE MZA_ID MZA_INT registers { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addCharVal($2); ctx.patch_procs.emplace_back(ctx.proc_body.size(), std::make_pair($3, $5->size())); ctx.proc_body.addSmallInt(0); ctx.proc_body.addCharVal($4); for (auto arg : *$5) { ctx.proc_body.addReg(arg); } delete $5; } | MZA_BUILTIN MZA_ID registers { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.patch_procs.emplace_back(ctx.proc_body.size(), std::make_pair($2, $3->size())); ctx.proc_body.addSmallInt(0); for (auto arg : *$3) { ctx.proc_body.addReg(arg); } delete $3; } | MZA_TCALL MZA_MODE MZA_ID MZA_INT { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addCharVal($2); ctx.patch_procs.emplace_back(ctx.proc_body.size(), std::make_pair($3, -1)); ctx.proc_body.addSmallInt(0); ctx.proc_body.addCharVal($4); } | MZA_OPEN_AGGREGATION MZA_CTX { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addCharVal($2); } // Special cases: Using aggregation context with the same name as instructions | MZA_OPEN_AGGREGATION MZA_AND { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addCharVal(AggregationCtx::VCTX_AND); } | MZA_OPEN_AGGREGATION MZA_OR { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addCharVal(AggregationCtx::VCTX_OR); } // Special cases: Read INT, but adds Reg value on the Stream | MZA_LOAD_GLOBAL MZA_INT MZA_REG { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2, true); ctx.max_glob = std::max(ctx.max_glob, $2); ctx.proc_body.addReg($3); } | MZA_STORE_GLOBAL MZA_REG MZA_INT { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addReg($2); ctx.proc_body.addReg($3, true); ctx.max_glob = std::max(ctx.max_glob, $3); } | MZA_GET_ARRAY MZA_INT registers { $$ = ctx.proc_body.size(); ctx.proc_body.addInstr($1); ctx.proc_body.addIntVal($2); for (auto arg : *$3) { ctx.proc_body.addReg(arg); } delete $3; } instrIR: MZA_IMMI instrI: MZA_ITER_BREAK instrR: MZA_INCI | MZA_DECI | MZA_PUSH | MZA_POP | MZA_POST | MZA_ITER_NEXT | MZA_TRACE instrRR: MZA_MOV | MZA_NOT | MZA_ISPAR | MZA_ISEMPTY | MZA_LENGTH | MZA_MAKE_SET | MZA_UB | MZA_LB | MZA_DOM | MZA_CLEAR instrRS: MZA_JMPIF | MZA_JMPIFNOT | MZA_ITER_ARRAY | MZA_ITER_VEC instrRRR: MZA_ADDI | MZA_SUBI | MZA_MULI | MZA_DIVI | MZA_MODI | MZA_EQI | MZA_LTI | MZA_LEI | MZA_AND | MZA_OR | MZA_XOR | MZA_GET_VEC | MZA_DIFF | MZA_INTERSECTION | MZA_UNION | MZA_INTERSECT_DOMAIN instrRRS: MZA_ITER_RANGE instrRRIRRR: MZA_SIMPLIFY_LIN instrS: MZA_JMP instr: MZA_RET | MZA_CLOSE_AGGREGATION | MZA_ABORT registers: /* empty */ { $$ = new std::list(); } | registers MZA_REG { $1->push_back($2); $$ = $1; } %% #include #include void yyerror(YYLTYPE* location, MZAContext& ctx, const char* s) { std::ostringstream oss; oss << "Cannot parse MiniZinc assembly in line " << location->first_line << ": " << std::string(s); throw Error(oss.str()); } std::pair> parse_mza(const std::string& assembly_str) { std::vector procs; std::unordered_map proc_map; int max_glob; // Initialise first slots with for (PrimitiveMap::Primitive* p : primitiveMap()) { BytecodeProc bcp; bcp.name = p->name(); bcp.nargs = p->n_args(); bcp.delay = false; procs.push_back(bcp); proc_map.emplace(bcp.name, p->ident()); } mza_yy_scan_string(assembly_str.c_str()); MZAContext ctx = MZAContext{procs, proc_map, max_glob}; int err = yyparse(ctx); if (err != 0) { throw std::runtime_error("Cannot parse MiniZinc assembly: " + std::to_string(err)); } for (auto& p : ctx.to_patch) { int code = p.code; BytecodeProc::Mode mode = p.mode; for (auto& patch : p.patch) { int& nargs = patch.second.second; std::string& name = patch.second.first; if (nargs >= 0 && nargs != procs[proc_map[name]].nargs) { throw Error("Error: number of arguments in call to " + patch.second.first + " at position " + std::to_string(patch.second.second) + " has an invalid number of arguments.\n"); } procs[code].mode[mode].patchAddress(patch.first, proc_map[patch.second.first]); } } return {max_glob, procs}; }