diff --git a/crypto/func/func.cpp b/crypto/func/func.cpp index e8e09961..43bba4ea 100644 --- a/crypto/func/func.cpp +++ b/crypto/func/func.cpp @@ -173,6 +173,7 @@ void usage(const char* progname) { "-R\tInclude operation rewrite comments in the output code\n" "-W\tInclude Fift code to serialize and save generated code into specified BoC file. Enables " "-A and -P.\n" + "\t-s\tOutput semantic version of FunC and exit\n" "\t-V\tShow func build information\n"; std::exit(2); } @@ -182,7 +183,7 @@ std::string output_filename; int main(int argc, char* const argv[]) { int i; bool interactive = false; - while ((i = getopt(argc, argv, "Ahi:Io:O:PRSvW:V")) != -1) { + while ((i = getopt(argc, argv, "Ahi:Io:O:PRsSvW:V")) != -1) { switch (i) { case 'A': funC::asm_preamble = true; @@ -215,8 +216,13 @@ int main(int argc, char* const argv[]) { funC::boc_output_filename = optarg; funC::asm_preamble = funC::program_envelope = true; break; + case 's': + std::cout << funC::func_version << "\n"; + std::exit(0); + break; case 'V': - std::cout << "Func build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::cout << "FunC semantic version: v" << funC::func_version << "\n"; + std::cout << "Build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; std::exit(0); break; case 'h': diff --git a/crypto/func/func.h b/crypto/func/func.h index 1506b974..22935dfe 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -38,6 +38,8 @@ extern bool op_rewrite_comments; constexpr int optimize_depth = 20; +const std::string func_version{"0.1.0"}; + enum Keyword { _Eof = -1, _Ident = 0, @@ -106,7 +108,8 @@ enum Keyword { _Operator, _Infix, _Infixl, - _Infixr + _Infixr, + _PragmaHashtag }; void define_keywords(); diff --git a/crypto/func/keywords.cpp b/crypto/func/keywords.cpp index 47975e7f..785c7765 100644 --- a/crypto/func/keywords.cpp +++ b/crypto/func/keywords.cpp @@ -126,6 +126,8 @@ void define_keywords() { .add_keyword("infix", Kw::_Infix) .add_keyword("infixl", Kw::_Infixl) .add_keyword("infixr", Kw::_Infixr); + + sym::symbols.add_keyword("#pragma", Kw::_PragmaHashtag); } } // namespace funC diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index 3aad90ef..d340a78c 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -1288,13 +1288,142 @@ void parse_func_def(Lexer& lex) { sym::close_scope(lex); } +std::string func_ver_test = func_version; + +void parse_pragma(Lexer& lex) { + auto pragma = lex.cur(); + lex.next(); + if (lex.tp() != _Ident) { + lex.expect(_Ident, "pragma name expected"); + } + auto pragma_name = lex.cur().str; + lex.next(); + if (!pragma_name.compare("version") || !pragma_name.compare("not-version")) { + bool negate = !pragma_name.compare("not-version"); + char op = '='; bool eq = false; + int sem_ver[3] = {0, 0, 0}; + char segs = 1; + if (lex.tp() == _Number) { + sem_ver[0] = std::stoi(lex.cur().str); + } else if (lex.tp() == _Ident) { + auto id1 = lex.cur().str; + char ch1 = id1[0]; + if ((ch1 == '>') || (ch1 == '<') || (ch1 == '=') || (ch1 == '^')) { + op = ch1; + } else { + lex.cur().error("unexpected comparator operation"); + } + if (id1.length() < 2) { + lex.cur().error("expected number after comparator"); + } + if (id1[1] == '=') { + eq = true; + if (id1.length() < 3) { + lex.cur().error("expected number after comparator"); + } + sem_ver[0] = std::stoi(id1.substr(2)); + } else { + sem_ver[0] = std::stoi(id1.substr(1)); + } + } else { + lex.cur().error("expected semver with optional comparator"); + } + lex.next(); + if (lex.tp() != ';') { + if (lex.tp() != _Ident || lex.cur().str[0] != '.') { + lex.cur().error("invalid semver format"); + } + sem_ver[1] = std::stoi(lex.cur().str.substr(1)); + segs = 2; + lex.next(); + } + if (lex.tp() != ';') { + if (lex.tp() != _Ident || lex.cur().str[0] != '.') { + lex.cur().error("invalid semver format"); + } + sem_ver[2] = std::stoi(lex.cur().str.substr(1)); + segs = 3; + lex.next(); + } + // End reading semver from source code + int func_ver[3] = {0, 0, 0}; + std::istringstream iss(func_ver_test); + std::string s; + for (int idx = 0; idx < 3; idx++) { + std::getline(iss, s, '.'); + func_ver[idx] = std::stoi(s); + } + // End parsing embedded semver + std::string semver_expr; + if (negate) { + semver_expr += '!'; + } + semver_expr += op; + if (eq) { + semver_expr += '='; + } + for (int idx = 0; idx < 3; idx++) { + semver_expr += std::to_string(sem_ver[idx]); + if (idx < 2) + semver_expr += '.'; + } + bool match = true; + switch (op) { + case '=': + if ((func_ver[0] != sem_ver[0]) || + (func_ver[1] != sem_ver[1]) || + (func_ver[2] != sem_ver[2])) { + match = false; + } + break; + case '>': + if ( ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] == sem_ver[2]) && !eq) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] < sem_ver[2])) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] < sem_ver[1])) || + ((func_ver[0] < sem_ver[0])) ) { + match = false; + } + break; + case '<': + if ( ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] == sem_ver[2]) && !eq) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] > sem_ver[2])) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] > sem_ver[1])) || + ((func_ver[0] > sem_ver[0])) ) { + match = false; + } + break; + case '^': + if ( ((segs == 3) && ((func_ver[0] != sem_ver[0]) || (func_ver[1] != sem_ver[1]) || (func_ver[2] < sem_ver[2]))) + || ((segs == 2) && ((func_ver[0] != sem_ver[0]) || (func_ver[1] < sem_ver[1]))) + || ((segs == 1) && ((func_ver[0] < sem_ver[0]))) ) { + match = false; + } + break; + } + if ((match && negate) || (!match && !negate)) { + pragma.error(std::string("FunC version ") + func_ver_test + " does not satisfy condition " + semver_expr); + } + } else if (!pragma_name.compare("test-version-set")) { + if (lex.tp() != _String) { + lex.cur().error("version string expected"); + } + func_ver_test = lex.cur().str; + lex.next(); + } else { + lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`"); + } + lex.expect(';'); +} + std::vector source_fdescr; bool parse_source(std::istream* is, src::FileDescr* fdescr) { src::SourceReader reader{is, fdescr}; Lexer lex{reader, true, ";,()[] ~."}; while (lex.tp() != _Eof) { - if (lex.tp() == _Global) { + if (lex.tp() == _PragmaHashtag) { + parse_pragma(lex); + } else if (lex.tp() == _Global) { parse_global_var_decls(lex); } else { parse_func_def(lex); diff --git a/crypto/func/test/pv.fc b/crypto/func/test/pv.fc new file mode 100644 index 00000000..d9bcd570 --- /dev/null +++ b/crypto/func/test/pv.fc @@ -0,0 +1,53 @@ +#pragma test-version-set "1.2.3"; + +;; Positive tests +#pragma version ^1.2.0; +#pragma version ^1.2.3; +#pragma version >1.2.0; +#pragma version >0.9.9; +#pragma version <1.3.0; +#pragma version <2.0.0; +#pragma version >=1.2.0; +#pragma version <=1.3.0; +#pragma version >=1.2.3; +#pragma version <=1.2.3; +#pragma version ^1.2.3; +#pragma version 1.2.3; +#pragma version =1.2.3; + +;; Negative tests +#pragma not-version ^1.1.0; +#pragma not-version ^1.0.0; +#pragma not-version ^0.2.3; +#pragma not-version ^2.2.3; +#pragma not-version ^1.3.3; +#pragma not-version >1.2.3; +#pragma not-version <1.2.3; +#pragma not-version ^1.2.4; +#pragma not-version >=1.2.4; +#pragma not-version <=1.2.2; +#pragma not-version 3.2.1; +#pragma not-version =3.2.1; + +;; Test incomplete (partial) version +#pragma version ^1.2; +#pragma version >1.2; +#pragma version <1.3; +#pragma version <2; +#pragma version >=1.2; +#pragma version <=1.3; + +;; Advanced ^ behaviour (partials) +#pragma version ^1.2; +#pragma version ^1.0; +#pragma version ^1; +#pragma version ^0; +#pragma not-version ^1.0.0; +#pragma not-version ^0.0.0; +#pragma not-version ^0.0; +#pragma not-version ^1.3; +#pragma not-version ^2; + +(int) main(int a) { + return a; +}