From 52501bb91457f93a873b532eff2bbdbb3617902f Mon Sep 17 00:00:00 2001 From: Piotr Fusik Date: Tue, 27 Feb 2024 21:17:44 +0100 Subject: [PATCH] [cpp] Accept non-null-terminated std::string_view in TryParse. --- GenCpp.fu | 40 ++++++++++++++++-------------------- libfut.cpp | 50 ++++++++++++++++++++++++--------------------- libfut.cs | 41 +++++++++++++++++-------------------- libfut.hpp | 1 + libfut.js | 40 ++++++++++++++++-------------------- test/Environment.fu | 2 +- 6 files changed, 84 insertions(+), 90 deletions(-) diff --git a/GenCpp.fu b/GenCpp.fu index 0c869674..9c274240 100644 --- a/GenCpp.fu +++ b/GenCpp.fu @@ -22,6 +22,7 @@ public class GenCpp : GenCCpp { bool UsingStringViewLiterals; bool HasEnumFlags; + bool NumberTryParse; bool StringReplace; bool StringToLower; bool StringToUpper; @@ -599,7 +600,7 @@ public class GenCpp : GenCCpp if (expr is FuLiteralString) expr.Accept(this, FuPriority.Argument); else - WritePostfix(expr, ".data()"); + WritePostfix(expr, ".c_str()"); } void WriteRegex!(List args, int argIndex) @@ -759,25 +760,11 @@ public class GenCpp : GenCCpp break; case FuId.IntTryParse: case FuId.LongTryParse: - Include("cstdlib"); - Write("[&] { char *ciend; "); - obj.Accept(this, FuPriority.Assign); - Write(" = std::strtol"); - if (method.Id == FuId.LongTryParse) - WriteChar('l'); - WriteChar('('); - WriteCString(args[0]); - Write(", &ciend"); - WriteTryParseRadix(args); - Write("); return *ciend == '\\0'; }()"); // TODO: && *s != '\0' - break; case FuId.DoubleTryParse: - Include("cstdlib"); - Write("[&] { char *ciend; "); - obj.Accept(this, FuPriority.Assign); - Write(" = std::strtod("); - WriteCString(args[0]); - Write(", &ciend); return *ciend == '\\0'; }()"); // TODO: && *s != '\0' + Include("charconv"); + Include("string_view"); + this.NumberTryParse = true; + WriteCall("FuNumber_TryParse", obj, args[0], args.Count == 2 ? args[1] : null); break; case FuId.StringContains: if (parent > FuPriority.Equality) @@ -1570,9 +1557,7 @@ public class GenCpp : GenCCpp if (type.Id == FuId.StringStorageType && expr.Type.Id == FuId.StringPtrType && !(expr is FuLiteral)) { - Write("std::string("); - expr.Accept(this, FuPriority.Argument); - WriteChar(')'); + WriteCall("std::string", expr); } else { FuCallExpr? call = IsStringSubstring(expr); @@ -1930,6 +1915,7 @@ public class GenCpp : GenCCpp this.InHeaderFile = true; this.UsingStringViewLiterals = false; this.HasEnumFlags = false; + this.NumberTryParse = false; this.StringReplace = false; this.StringToLower = false; this.StringToUpper = false; @@ -1981,6 +1967,16 @@ public class GenCpp : GenCCpp CreateImplementationFile(program, ".hpp"); if (this.UsingStringViewLiterals) WriteLine("using namespace std::string_view_literals;"); + if (this.NumberTryParse) { + WriteNewLine(); + WriteLine("template "); + WriteLine("bool FuNumber_TryParse(T &number, std::string_view s, Args... args)"); + OpenBlock(); + WriteLine("const char *end = s.begin() + s.size();"); + WriteLine("auto result = std::from_chars(s.begin(), end, number, args...);"); + WriteLine("return result.ec == std::errc{} && result.ptr == end;"); + CloseBlock(); + } if (this.StringReplace) { WriteNewLine(); WriteLine("static std::string FuString_Replace(std::string_view s, std::string_view oldValue, std::string_view newValue)"); diff --git a/libfut.cpp b/libfut.cpp index 4d5c7f13..374168a5 100644 --- a/libfut.cpp +++ b/libfut.cpp @@ -1,11 +1,20 @@ // Generated automatically with "fut". Do not edit. #include #include +#include #include #include #include #include "libfut.hpp" +template +bool FuNumber_TryParse(T &number, std::string_view s, Args... args) +{ + const char *end = s.begin() + s.size(); + auto result = std::from_chars(s.begin(), end, number, args...); + return result.ec == std::errc{} && result.ptr == end; +} + static std::string FuString_Replace(std::string_view s, std::string_view oldValue, std::string_view newValue) { std::string result; @@ -2889,7 +2898,7 @@ void FuParser::checkXcrementParent() std::shared_ptr FuParser::parseDouble() { double d; - if (![&] { char *ciend; d = std::strtod(FuString_Replace(getLexeme(), "_", "").data(), &ciend); return *ciend == '\0'; }()) + if (!FuNumber_TryParse(d, FuString_Replace(getLexeme(), "_", ""))) reportError("Invalid floating-point number"); std::shared_ptr result = std::make_shared(); result->line = this->line; @@ -13526,7 +13535,7 @@ void GenCpp::writeCString(const FuExpr * expr) if (dynamic_cast(expr)) expr->accept(this, FuPriority::argument); else - writePostfix(expr, ".data()"); + writePostfix(expr, ".c_str()"); } void GenCpp::writeRegex(const std::vector> * args, int argIndex) @@ -13683,25 +13692,11 @@ void GenCpp::writeCallExpr(const FuExpr * obj, const FuMethod * method, const st break; case FuId::intTryParse: case FuId::longTryParse: - include("cstdlib"); - write("[&] { char *ciend; "); - obj->accept(this, FuPriority::assign); - write(" = std::strtol"); - if (method->id == FuId::longTryParse) - writeChar('l'); - writeChar('('); - writeCString((*args)[0].get()); - write(", &ciend"); - writeTryParseRadix(args); - write("); return *ciend == '\\0'; }()"); - break; case FuId::doubleTryParse: - include("cstdlib"); - write("[&] { char *ciend; "); - obj->accept(this, FuPriority::assign); - write(" = std::strtod("); - writeCString((*args)[0].get()); - write(", &ciend); return *ciend == '\\0'; }()"); + include("charconv"); + include("string_view"); + this->numberTryParse = true; + writeCall("FuNumber_TryParse", obj, (*args)[0].get(), std::ssize(*args) == 2 ? (*args)[1].get() : nullptr); break; case FuId::stringContains: if (parent > FuPriority::equality) @@ -14493,9 +14488,7 @@ void GenCpp::visitLock(const FuLock * statement) void GenCpp::writeStronglyCoerced(const FuType * type, const FuExpr * expr) { if (type->id == FuId::stringStorageType && expr->type->id == FuId::stringPtrType && !dynamic_cast(expr)) { - write("std::string("); - expr->accept(this, FuPriority::argument); - writeChar(')'); + writeCall("std::string", expr); } else { const FuCallExpr * call = isStringSubstring(expr); @@ -14839,6 +14832,7 @@ void GenCpp::writeProgram(const FuProgram * program) this->inHeaderFile = true; this->usingStringViewLiterals = false; this->hasEnumFlags = false; + this->numberTryParse = false; this->stringReplace = false; this->stringToLower = false; this->stringToUpper = false; @@ -14887,6 +14881,16 @@ void GenCpp::writeProgram(const FuProgram * program) createImplementationFile(program, ".hpp"); if (this->usingStringViewLiterals) writeLine("using namespace std::string_view_literals;"); + if (this->numberTryParse) { + writeNewLine(); + writeLine("template "); + writeLine("bool FuNumber_TryParse(T &number, std::string_view s, Args... args)"); + openBlock(); + writeLine("const char *end = s.begin() + s.size();"); + writeLine("auto result = std::from_chars(s.begin(), end, number, args...);"); + writeLine("return result.ec == std::errc{} && result.ptr == end;"); + closeBlock(); + } if (this->stringReplace) { writeNewLine(); writeLine("static std::string FuString_Replace(std::string_view s, std::string_view oldValue, std::string_view newValue)"); diff --git a/libfut.cs b/libfut.cs index 8b6849c2..e02f696a 100644 --- a/libfut.cs +++ b/libfut.cs @@ -13341,6 +13341,8 @@ public class GenCpp : GenCCpp bool HasEnumFlags; + bool NumberTryParse; + bool StringReplace; bool StringToLower; @@ -13916,7 +13918,7 @@ void WriteCString(FuExpr expr) if (expr is FuLiteralString) expr.Accept(this, FuPriority.Argument); else - WritePostfix(expr, ".data()"); + WritePostfix(expr, ".c_str()"); } void WriteRegex(List args, int argIndex) @@ -14072,25 +14074,11 @@ protected override void WriteCallExpr(FuExpr obj, FuMethod method, List break; case FuId.IntTryParse: case FuId.LongTryParse: - Include("cstdlib"); - Write("[&] { char *ciend; "); - obj.Accept(this, FuPriority.Assign); - Write(" = std::strtol"); - if (method.Id == FuId.LongTryParse) - WriteChar('l'); - WriteChar('('); - WriteCString(args[0]); - Write(", &ciend"); - WriteTryParseRadix(args); - Write("); return *ciend == '\\0'; }()"); - break; case FuId.DoubleTryParse: - Include("cstdlib"); - Write("[&] { char *ciend; "); - obj.Accept(this, FuPriority.Assign); - Write(" = std::strtod("); - WriteCString(args[0]); - Write(", &ciend); return *ciend == '\\0'; }()"); + Include("charconv"); + Include("string_view"); + this.NumberTryParse = true; + WriteCall("FuNumber_TryParse", obj, args[0], args.Count == 2 ? args[1] : null); break; case FuId.StringContains: if (parent > FuPriority.Equality) @@ -14875,9 +14863,7 @@ internal override void VisitLock(FuLock statement) protected override void WriteStronglyCoerced(FuType type, FuExpr expr) { if (type.Id == FuId.StringStorageType && expr.Type.Id == FuId.StringPtrType && !(expr is FuLiteral)) { - Write("std::string("); - expr.Accept(this, FuPriority.Argument); - WriteChar(')'); + WriteCall("std::string", expr); } else { FuCallExpr call = IsStringSubstring(expr); @@ -15228,6 +15214,7 @@ public override void WriteProgram(FuProgram program) this.InHeaderFile = true; this.UsingStringViewLiterals = false; this.HasEnumFlags = false; + this.NumberTryParse = false; this.StringReplace = false; this.StringToLower = false; this.StringToUpper = false; @@ -15276,6 +15263,16 @@ public override void WriteProgram(FuProgram program) CreateImplementationFile(program, ".hpp"); if (this.UsingStringViewLiterals) WriteLine("using namespace std::string_view_literals;"); + if (this.NumberTryParse) { + WriteNewLine(); + WriteLine("template "); + WriteLine("bool FuNumber_TryParse(T &number, std::string_view s, Args... args)"); + OpenBlock(); + WriteLine("const char *end = s.begin() + s.size();"); + WriteLine("auto result = std::from_chars(s.begin(), end, number, args...);"); + WriteLine("return result.ec == std::errc{} && result.ptr == end;"); + CloseBlock(); + } if (this.StringReplace) { WriteNewLine(); WriteLine("static std::string FuString_Replace(std::string_view s, std::string_view oldValue, std::string_view newValue)"); diff --git a/libfut.hpp b/libfut.hpp index b2662555..c6bfeb35 100644 --- a/libfut.hpp +++ b/libfut.hpp @@ -2306,6 +2306,7 @@ class GenCpp : public GenCCpp private: bool usingStringViewLiterals; bool hasEnumFlags; + bool numberTryParse; bool stringReplace; bool stringToLower; bool stringToUpper; diff --git a/libfut.js b/libfut.js index ceb4585f..4a15e1e1 100644 --- a/libfut.js +++ b/libfut.js @@ -13760,6 +13760,7 @@ export class GenCpp extends GenCCpp { #usingStringViewLiterals; #hasEnumFlags; + #numberTryParse; #stringReplace; #stringToLower; #stringToUpper; @@ -14339,7 +14340,7 @@ export class GenCpp extends GenCCpp if (expr instanceof FuLiteralString) expr.accept(this, FuPriority.ARGUMENT); else - this.writePostfix(expr, ".data()"); + this.writePostfix(expr, ".c_str()"); } #writeRegex(args, argIndex) @@ -14497,25 +14498,11 @@ export class GenCpp extends GenCCpp break; case FuId.INT_TRY_PARSE: case FuId.LONG_TRY_PARSE: - this.include("cstdlib"); - this.write("[&] { char *ciend; "); - obj.accept(this, FuPriority.ASSIGN); - this.write(" = std::strtol"); - if (method.id == FuId.LONG_TRY_PARSE) - this.writeChar(108); - this.writeChar(40); - this.#writeCString(args[0]); - this.write(", &ciend"); - this.writeTryParseRadix(args); - this.write("); return *ciend == '\\0'; }()"); - break; case FuId.DOUBLE_TRY_PARSE: - this.include("cstdlib"); - this.write("[&] { char *ciend; "); - obj.accept(this, FuPriority.ASSIGN); - this.write(" = std::strtod("); - this.#writeCString(args[0]); - this.write(", &ciend); return *ciend == '\\0'; }()"); + this.include("charconv"); + this.include("string_view"); + this.#numberTryParse = true; + this.writeCall("FuNumber_TryParse", obj, args[0], args.length == 2 ? args[1] : null); break; case FuId.STRING_CONTAINS: if (parent > FuPriority.EQUALITY) @@ -15304,9 +15291,7 @@ export class GenCpp extends GenCCpp writeStronglyCoerced(type, expr) { if (type.id == FuId.STRING_STORAGE_TYPE && expr.type.id == FuId.STRING_PTR_TYPE && !(expr instanceof FuLiteral)) { - this.write("std::string("); - expr.accept(this, FuPriority.ARGUMENT); - this.writeChar(41); + this.writeCall("std::string", expr); } else { let call = GenCpp.isStringSubstring(expr); @@ -15667,6 +15652,7 @@ export class GenCpp extends GenCCpp this.inHeaderFile = true; this.#usingStringViewLiterals = false; this.#hasEnumFlags = false; + this.#numberTryParse = false; this.#stringReplace = false; this.#stringToLower = false; this.#stringToUpper = false; @@ -15716,6 +15702,16 @@ export class GenCpp extends GenCCpp this.createImplementationFile(program, ".hpp"); if (this.#usingStringViewLiterals) this.writeLine("using namespace std::string_view_literals;"); + if (this.#numberTryParse) { + this.writeNewLine(); + this.writeLine("template "); + this.writeLine("bool FuNumber_TryParse(T &number, std::string_view s, Args... args)"); + this.openBlock(); + this.writeLine("const char *end = s.begin() + s.size();"); + this.writeLine("auto result = std::from_chars(s.begin(), end, number, args...);"); + this.writeLine("return result.ec == std::errc{} && result.ptr == end;"); + this.closeBlock(); + } if (this.#stringReplace) { this.writeNewLine(); this.writeLine("static std::string FuString_Replace(std::string_view s, std::string_view oldValue, std::string_view newValue)"); diff --git a/test/Environment.fu b/test/Environment.fu index 6cf2e3a5..f25519ee 100644 --- a/test/Environment.fu +++ b/test/Environment.fu @@ -3,7 +3,7 @@ public static class Test public static bool Run() { string s = "HOME"; - string? homeDir = Environment.GetEnvironmentVariable(s); //FAIL: cl + string? homeDir = Environment.GetEnvironmentVariable(s); //FAIL: cpp TODO; cl return homeDir != null && Environment.GetEnvironmentVariable("NOT_EXISTING_ENV_VAR") == null && Environment.GetEnvironmentVariable("") == null