From 7764d0ba1dcf064ae487ee985c43083a0909e7f4 Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sat, 5 Jan 2013 11:13:26 +0100 Subject: initial import --- frontends/ilang/Makefile.inc | 16 ++ frontends/ilang/ilang_frontend.cc | 49 +++++ frontends/ilang/ilang_frontend.h | 45 +++++ frontends/ilang/lexer.l | 122 +++++++++++ frontends/ilang/parser.y | 416 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 648 insertions(+) create mode 100644 frontends/ilang/Makefile.inc create mode 100644 frontends/ilang/ilang_frontend.cc create mode 100644 frontends/ilang/ilang_frontend.h create mode 100644 frontends/ilang/lexer.l create mode 100644 frontends/ilang/parser.y (limited to 'frontends/ilang') diff --git a/frontends/ilang/Makefile.inc b/frontends/ilang/Makefile.inc new file mode 100644 index 00000000..07ebf085 --- /dev/null +++ b/frontends/ilang/Makefile.inc @@ -0,0 +1,16 @@ + +GENFILES += frontends/ilang/parser.tab.cc +GENFILES += frontends/ilang/parser.tab.h +GENFILES += frontends/ilang/parser.output +GENFILES += frontends/ilang/lexer.cc + +frontends/ilang/parser.tab.cc frontends/ilang/parser.tab.h: frontends/ilang/parser.y + bison -d -r all -b frontends/ilang/parser frontends/ilang/parser.y + mv frontends/ilang/parser.tab.c frontends/ilang/parser.tab.cc + +frontends/ilang/lexer.cc: frontends/ilang/lexer.l + flex -o frontends/ilang/lexer.cc frontends/ilang/lexer.l + +OBJS += frontends/ilang/parser.tab.o frontends/ilang/lexer.o +OBJS += frontends/ilang/ilang_frontend.o + diff --git a/frontends/ilang/ilang_frontend.cc b/frontends/ilang/ilang_frontend.cc new file mode 100644 index 00000000..f3ad3a19 --- /dev/null +++ b/frontends/ilang/ilang_frontend.cc @@ -0,0 +1,49 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * A very simple and straightforward frontend for the RTLIL text + * representation (as generated by the 'ilang' backend). + * + */ + +#include "ilang_frontend.h" +#include "kernel/register.h" +#include "kernel/log.h" + +void rtlil_frontend_ilang_yyerror(char const *s) +{ + log_error("Parser error in line %d: %s\n", rtlil_frontend_ilang_yyget_lineno(), s); +} + +struct IlangFrontend : public Frontend { + IlangFrontend() : Frontend("ilang") { } + virtual void execute(FILE *&f, std::string filename, std::vector args, RTLIL::Design *design) + { + log_header("Executing ILANG frontend.\n"); + extra_args(f, filename, args, 1); + log("Input filename: %s\n", filename.c_str()); + + ILANG_FRONTEND::current_design = design; + rtlil_frontend_ilang_yydebug = false; + rtlil_frontend_ilang_yyrestart(f); + rtlil_frontend_ilang_yyparse(); + rtlil_frontend_ilang_yylex_destroy(); + } +} IlangFrontend; + diff --git a/frontends/ilang/ilang_frontend.h b/frontends/ilang/ilang_frontend.h new file mode 100644 index 00000000..5e768c3b --- /dev/null +++ b/frontends/ilang/ilang_frontend.h @@ -0,0 +1,45 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * A very simple and straightforward frontend for the RTLIL text + * representation (as generated by the 'ilang' backend). + * + */ + +#ifndef ILANG_FRONTEND_H +#define ILANG_FRONTEND_H + +#include "kernel/rtlil.h" +#include + +namespace ILANG_FRONTEND { + void ilang_frontend(FILE *f, RTLIL::Design *design); + extern RTLIL::Design *current_design; +} + +extern int rtlil_frontend_ilang_yydebug; +int rtlil_frontend_ilang_yylex(void); +void rtlil_frontend_ilang_yyerror(char const *s); +void rtlil_frontend_ilang_yyrestart(FILE *f); +int rtlil_frontend_ilang_yyparse(void); +void rtlil_frontend_ilang_yylex_destroy(void); +int rtlil_frontend_ilang_yyget_lineno(void); + +#endif + diff --git a/frontends/ilang/lexer.l b/frontends/ilang/lexer.l new file mode 100644 index 00000000..e331c267 --- /dev/null +++ b/frontends/ilang/lexer.l @@ -0,0 +1,122 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * A very simple and straightforward frontend for the RTLIL text + * representation (as generated by the 'ilang' backend). + * + */ + +%{ +#include "kernel/rtlil.h" +#include "parser.tab.h" +%} + +%option yylineno +%option noyywrap +%option nounput +%option prefix="rtlil_frontend_ilang_yy" + +%x STRING + +%% + +"module" { return TOK_MODULE; } +"attribute" { return TOK_ATTRIBUTE; } +"parameter" { return TOK_PARAMETER; } +"wire" { return TOK_WIRE; } +"memory" { return TOK_MEMORY; } +"auto" { return TOK_AUTO; } +"width" { return TOK_WIDTH; } +"offset" { return TOK_OFFSET; } +"size" { return TOK_SIZE; } +"input" { return TOK_INPUT; } +"output" { return TOK_OUTPUT; } +"inout" { return TOK_INOUT; } +"cell" { return TOK_CELL; } +"connect" { return TOK_CONNECT; } +"switch" { return TOK_SWITCH; } +"case" { return TOK_CASE; } +"assign" { return TOK_ASSIGN; } +"sync" { return TOK_SYNC; } +"low" { return TOK_LOW; } +"high" { return TOK_HIGH; } +"posedge" { return TOK_POSEDGE; } +"negedge" { return TOK_NEGEDGE; } +"edge" { return TOK_EDGE; } +"always" { return TOK_ALWAYS; } +"update" { return TOK_UPDATE; } +"process" { return TOK_PROCESS; } +"end" { return TOK_END; } + +[a-z]+ { return TOK_INVALID; } + +"\\"[^ \t\r\n]+ { rtlil_frontend_ilang_yylval.string = strdup(yytext); return TOK_ID; } +"$"[^ \t\r\n]+ { rtlil_frontend_ilang_yylval.string = strdup(yytext); return TOK_ID; } +"."[0-9]+ { rtlil_frontend_ilang_yylval.string = strdup(yytext); return TOK_ID; } + +[0-9]+'[01xzm-]+ { rtlil_frontend_ilang_yylval.string = strdup(yytext); return TOK_VALUE; } +[0-9]+ { rtlil_frontend_ilang_yylval.integer = atoi(yytext); return TOK_INT; } + +\" { BEGIN(STRING); } +\\. { yymore(); } +\" { + BEGIN(0); + char *yystr = strdup(yytext); + yystr[strlen(yytext) - 1] = 0; + int i = 0, j = 0; + while (yystr[i]) { + if (yystr[i] == '\\' && yystr[i + 1]) { + i++; + if (yystr[i] == 'n') + yystr[i] = '\n'; + else if (yystr[i] == 't') + yystr[i] = '\t'; + else if ('0' <= yystr[i] && yystr[i] <= '7') { + yystr[i] = yystr[i] - '0'; + if ('0' <= yystr[i + 1] && yystr[i + 1] <= '7') { + yystr[i + 1] = yystr[i] * 8 + yystr[i + 1] - '0'; + i++; + } + if ('0' <= yystr[i + 1] && yystr[i + 1] <= '7') { + yystr[i + 1] = yystr[i] * 8 + yystr[i + 1] - '0'; + i++; + } + } + } + yystr[j++] = yystr[i++]; + } + yystr[j] = 0; + rtlil_frontend_ilang_yylval.string = yystr; + return TOK_STRING; +} +. { yymore(); } + +"#"[^\n]*\n /* ignore comments */ +[ \t] /* ignore non-newline whitespaces */ +[\r\n]+ { return TOK_EOL; } + +. { return *yytext; } + +%% + +// this is a hack to avoid the 'yyinput defined but not used' error msgs +void *rtlil_frontend_ilang_avoid_input_warnings() { + return (void*)&yyinput; +} + diff --git a/frontends/ilang/parser.y b/frontends/ilang/parser.y new file mode 100644 index 00000000..61bac830 --- /dev/null +++ b/frontends/ilang/parser.y @@ -0,0 +1,416 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * A very simple and straightforward frontend for the RTLIL text + * representation (as generated by the 'ilang' backend). + * + */ + +%{ +#include +#include "ilang_frontend.h" +namespace ILANG_FRONTEND { + RTLIL::Design *current_design; + RTLIL::Module *current_module; + RTLIL::Wire *current_wire; + RTLIL::Memory *current_memory; + RTLIL::Cell *current_cell; + RTLIL::Process *current_process; + std::vector*> switch_stack; + std::vector case_stack; + std::map attrbuf; +} +using namespace ILANG_FRONTEND; +%} + +%name-prefix="rtlil_frontend_ilang_yy" + +%union { + char *string; + int integer; + RTLIL::Const *data; + RTLIL::SigSpec *sigspec; +} + +%token TOK_ID TOK_VALUE TOK_STRING +%token TOK_INT +%token TOK_MODULE TOK_WIRE TOK_WIDTH TOK_INPUT TOK_OUTPUT TOK_INOUT +%token TOK_CELL TOK_CONNECT TOK_SWITCH TOK_CASE TOK_ASSIGN TOK_SYNC +%token TOK_LOW TOK_HIGH TOK_POSEDGE TOK_NEGEDGE TOK_EDGE TOK_ALWAYS +%token TOK_UPDATE TOK_PROCESS TOK_END TOK_INVALID TOK_EOL TOK_OFFSET +%token TOK_PARAMETER TOK_ATTRIBUTE TOK_AUTO TOK_MEMORY TOK_SIZE + +%type sigspec sigspec_list +%type sync_type +%type constant + +%expect 0 +%debug + +%% + +input: + optional_eol { + attrbuf.clear(); + } design { + if (attrbuf.size() != 0) + rtlil_frontend_ilang_yyerror("dangling attribute"); + }; + +optional_eol: + optional_eol TOK_EOL | /* empty */; + +design: + design module | + design attr_stmt | + /* empty */; + +module: + TOK_MODULE TOK_ID TOK_EOL { + if (current_design->modules.count($2) != 0) + rtlil_frontend_ilang_yyerror("scope error"); + current_module = new RTLIL::Module; + current_module->name = $2; + current_module->attributes = attrbuf; + current_design->modules[$2] = current_module; + attrbuf.clear(); + free($2); + } module_body TOK_END { + if (attrbuf.size() != 0) + rtlil_frontend_ilang_yyerror("dangling attribute"); + } TOK_EOL; + +module_body: + module_body module_stmt | + /* empty */; + +module_stmt: + attr_stmt | wire_stmt | memory_stmt | cell_stmt | proc_stmt | conn_stmt; + +attr_stmt: + TOK_ATTRIBUTE TOK_ID constant TOK_EOL { + attrbuf[$2] = *$3; + delete $3; + }; + +wire_stmt: + TOK_WIRE { + current_wire = new RTLIL::Wire; + current_wire->attributes = attrbuf; + attrbuf.clear(); + } wire_options TOK_ID TOK_EOL { + if (current_module->wires.count($4) != 0) + rtlil_frontend_ilang_yyerror("scope error"); + current_wire->name = $4; + current_module->wires[$4] = current_wire; + free($4); + }; + +wire_options: + wire_options TOK_AUTO { + current_wire->auto_width = true; + } | + wire_options TOK_WIDTH TOK_INT { + current_wire->width = $3; + } | + wire_options TOK_OFFSET TOK_INT { + current_wire->start_offset = $3; + } | + wire_options TOK_INPUT TOK_INT { + current_wire->port_id = $3; + current_wire->port_input = true; + current_wire->port_output = false; + } | + wire_options TOK_OUTPUT TOK_INT { + current_wire->port_id = $3; + current_wire->port_input = false; + current_wire->port_output = true; + } | + wire_options TOK_INOUT TOK_INT { + current_wire->port_id = $3; + current_wire->port_input = true; + current_wire->port_output = true; + } | + /* empty */; + +memory_stmt: + TOK_MEMORY { + current_memory = new RTLIL::Memory; + current_memory->attributes = attrbuf; + attrbuf.clear(); + } memory_options TOK_ID TOK_EOL { + if (current_module->memories.count($4) != 0) + rtlil_frontend_ilang_yyerror("scope error"); + current_memory->name = $4; + current_module->memories[$4] = current_memory; + free($4); + }; + +memory_options: + memory_options TOK_WIDTH TOK_INT { + current_wire->width = $3; + } | + memory_options TOK_SIZE TOK_INT { + current_memory->size = $3; + } | + /* empty */; + +cell_stmt: + TOK_CELL TOK_ID TOK_ID TOK_EOL { + if (current_module->cells.count($3) != 0) + rtlil_frontend_ilang_yyerror("scope error"); + current_cell = new RTLIL::Cell; + current_cell->type = $2; + current_cell->name = $3; + current_cell->attributes = attrbuf; + current_module->cells[$3] = current_cell; + attrbuf.clear(); + free($2); + free($3); + } cell_body TOK_END TOK_EOL; + +cell_body: + cell_body TOK_PARAMETER TOK_ID constant TOK_EOL { + current_cell->parameters[$3] = *$4; + free($3); + delete $4; + } | + cell_body TOK_CONNECT TOK_ID sigspec TOK_EOL { + if (current_cell->connections.count($3) != 0) + rtlil_frontend_ilang_yyerror("scope error"); + current_cell->connections[$3] = *$4; + delete $4; + free($3); + } | + /* empty */; + +proc_stmt: + TOK_PROCESS TOK_ID TOK_EOL { + if (current_module->processes.count($2) != 0) + rtlil_frontend_ilang_yyerror("scope error"); + current_process = new RTLIL::Process; + current_process->name = $2; + current_process->attributes = attrbuf; + current_module->processes[$2] = current_process; + switch_stack.clear(); + switch_stack.push_back(¤t_process->root_case.switches); + case_stack.clear(); + case_stack.push_back(¤t_process->root_case); + free($2); + } case_body sync_list TOK_END TOK_EOL; + +switch_stmt: + attr_list TOK_SWITCH sigspec TOK_EOL { + RTLIL::SwitchRule *rule = new RTLIL::SwitchRule; + rule->signal = *$3; + rule->attributes = attrbuf; + switch_stack.back()->push_back(rule); + attrbuf.clear(); + delete $3; + } switch_body TOK_END TOK_EOL; + +attr_list: + /* empty */ | + attr_list attr_stmt; + +switch_body: + switch_body TOK_CASE { + RTLIL::CaseRule *rule = new RTLIL::CaseRule; + switch_stack.back()->back()->cases.push_back(rule); + switch_stack.push_back(&rule->switches); + case_stack.push_back(rule); + } compare_list TOK_EOL case_body { + switch_stack.pop_back(); + case_stack.pop_back(); + } | + /* empty */; + +compare_list: + sigspec { + case_stack.back()->compare.push_back(*$1); + delete $1; + } | + compare_list ',' sigspec { + case_stack.back()->compare.push_back(*$3); + delete $3; + } | + /* empty */; + +case_body: + switch_stmt case_body | + assign_stmt case_body | + /* empty */; + +assign_stmt: + TOK_ASSIGN sigspec sigspec TOK_EOL { + case_stack.back()->actions.push_back(RTLIL::SigSig(*$2, *$3)); + delete $2; + delete $3; + }; + +sync_list: + sync_list TOK_SYNC sync_type sigspec TOK_EOL { + RTLIL::SyncRule *rule = new RTLIL::SyncRule; + rule->type = RTLIL::SyncType($3); + rule->signal = *$4; + current_process->syncs.push_back(rule); + delete $4; + } update_list | + sync_list TOK_SYNC TOK_ALWAYS TOK_EOL { + RTLIL::SyncRule *rule = new RTLIL::SyncRule; + rule->type = RTLIL::SyncType::STa; + rule->signal = RTLIL::SigSpec(); + current_process->syncs.push_back(rule); + } update_list | + /* empty */; + +sync_type: + TOK_LOW { $$ = RTLIL::ST0; } | + TOK_HIGH { $$ = RTLIL::ST1; } | + TOK_POSEDGE { $$ = RTLIL::STp; } | + TOK_NEGEDGE { $$ = RTLIL::STn; } | + TOK_EDGE { $$ = RTLIL::STe; }; + +update_list: + update_list TOK_UPDATE sigspec sigspec TOK_EOL { + current_process->syncs.back()->actions.push_back(RTLIL::SigSig(*$3, *$4)); + delete $3; + delete $4; + } | + /* empty */; + +constant: + TOK_VALUE { + char *ep; + int width = strtol($1, &ep, 10); + std::list bits; + while (*(++ep) != 0) { + RTLIL::State bit = RTLIL::Sx; + switch (*ep) { + case '0': bit = RTLIL::S0; break; + case '1': bit = RTLIL::S1; break; + case 'x': bit = RTLIL::Sx; break; + case 'z': bit = RTLIL::Sz; break; + case '-': bit = RTLIL::Sa; break; + case 'm': bit = RTLIL::Sm; break; + } + bits.push_front(bit); + } + if (bits.size() == 0) + bits.push_back(RTLIL::Sx); + while ((int)bits.size() < width) { + RTLIL::State bit = bits.back(); + if (bit == RTLIL::S1) + bit = RTLIL::S0; + bits.push_back(bit); + } + while ((int)bits.size() > width) + bits.pop_back(); + $$ = new RTLIL::Const; + for (auto it = bits.begin(); it != bits.end(); it++) + $$->bits.push_back(*it); + free($1); + } | + TOK_INT { + $$ = new RTLIL::Const($1, 32); + } | + TOK_STRING { + $$ = new RTLIL::Const($1); + free($1); + }; + +sigspec: + constant { + RTLIL::SigChunk chunk; + chunk.wire = NULL; + chunk.width = $1->bits.size(); + chunk.offset = 0; + chunk.data = *$1; + $$ = new RTLIL::SigSpec; + $$->chunks.push_back(chunk); + $$->width = chunk.width; + delete $1; + } | + TOK_ID { + if (current_module->wires.count($1) == 0) + rtlil_frontend_ilang_yyerror("scope error"); + RTLIL::SigChunk chunk; + chunk.wire = current_module->wires[$1]; + chunk.width = current_module->wires[$1]->width; + chunk.offset = 0; + $$ = new RTLIL::SigSpec; + $$->chunks.push_back(chunk); + $$->width = chunk.width; + free($1); + } | + TOK_ID '[' TOK_INT ']' { + if (current_module->wires.count($1) == 0) + rtlil_frontend_ilang_yyerror("scope error"); + RTLIL::SigChunk chunk; + chunk.wire = current_module->wires[$1]; + chunk.offset = $3; + chunk.width = 1; + $$ = new RTLIL::SigSpec; + $$->chunks.push_back(chunk); + $$->width = 1; + free($1); + } | + TOK_ID '[' TOK_INT ':' TOK_INT ']' { + if (current_module->wires.count($1) == 0) + rtlil_frontend_ilang_yyerror("scope error"); + RTLIL::SigChunk chunk; + chunk.wire = current_module->wires[$1]; + chunk.width = $3 - $5 + 1; + chunk.offset = $5; + $$ = new RTLIL::SigSpec; + $$->chunks.push_back(chunk); + $$->width = chunk.width; + free($1); + } | + '{' sigspec_list '}' { + $$ = $2; + }; + +sigspec_list: + sigspec_list sigspec { + $$ = new RTLIL::SigSpec; + for (auto it = $2->chunks.begin(); it != $2->chunks.end(); it++) { + $$->chunks.push_back(*it); + $$->width += it->width; + } + for (auto it = $1->chunks.begin(); it != $1->chunks.end(); it++) { + $$->chunks.push_back(*it); + $$->width += it->width; + } + delete $1; + delete $2; + } | + /* empty */ { + $$ = new RTLIL::SigSpec; + }; + +conn_stmt: + TOK_CONNECT sigspec sigspec TOK_EOL { + if (attrbuf.size() != 0) + rtlil_frontend_ilang_yyerror("dangling attribute"); + current_module->connections.push_back(RTLIL::SigSig(*$2, *$3)); + delete $2; + delete $3; + }; + -- cgit v1.2.3