Skip to content

Commit

Permalink
formatted strings
Browse files Browse the repository at this point in the history
  • Loading branch information
f0xeri committed Mar 3, 2024
1 parent db233ff commit 651bb21
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 9 deletions.
8 changes: 8 additions & 0 deletions check/CheckExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ namespace Slangc::Check {
return true;
}

bool checkExpr(const FormattedStringExprPtr &expr, Context &context, std::vector<ErrorMessage> &errors) {
bool result = true;
for (const auto &arg: expr->values) {
result &= checkExpr(arg, context, errors);
}
return result;
}

bool checkExpr(const UnaryOperatorExprPtr &expr, Context &context, std::vector<ErrorMessage> &errors) {
auto result = checkExpr(expr->expr, context, errors);
if (!result) return false;
Expand Down
92 changes: 92 additions & 0 deletions codegen/CodeGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,98 @@ namespace Slangc {
return context.builder->CreateGlobalStringPtr(value, "", 0, context.module.get());
}

auto FormattedStringExprNode::codegen(Slangc::CodeGenContext &context, std::vector<ErrorMessage> &errors) -> llvm::Value * {
if (context.debug) context.debugBuilder->emitLocation(loc);
// llvm ir code do something like this:
/*
size_t needed = snprintf(NULL, 0, "%s: %s (%d)", msg, strerror(errno), errno) + 1;
char *buffer = malloc(needed);
sprintf(buffer, "%s: %s (%d)", msg, strerror(errno), errno);
return buffer;
*/
// int snprintf (char* s, size_t n, const char* format, ...);
auto snprintfFunc = context.module->getOrInsertFunction(
"snprintf",
FunctionType::get(
Type::getInt32Ty(*context.llvmContext),{
PointerType::get(Type::getInt8Ty(*context.llvmContext), 0),
Type::getInt64Ty(*context.llvmContext),
PointerType::get(Type::getInt8Ty(*context.llvmContext), 0)
},
true
)
);

std::vector<Value*> args;
std::vector<Type*> argTypes;
Value* formatString = nullptr;
std::string fmtString;
// build format string and args
for (auto& arg: values) {
auto temp = context.loadValue;
context.loadValue = true;
auto val = processNode(arg, context, errors);
context.loadValue = temp;
auto exprType = getExprType(arg, context.context, errors).value();
bool charArray = false;
if (auto arr = std::get_if<ArrayExprPtr>(&exprType)) {
if (auto type = std::get_if<TypeExprPtr>(&arr->get()->type)) {
if (type->get()->type == "character") {
charArray = true;
}
}
}
if (auto arr = std::get_if<StringExprPtr>(&arg)) charArray = true;

if (val->getType()->isFloatingPointTy()) {
val = context.builder->CreateFPExt(val, Type::getDoubleTy(*context.llvmContext));
fmtString +="%f";
}
else if (val->getType()->isPointerTy() && charArray) {
fmtString += "%s";
}
else if (val->getType()->isPointerTy()) {
fmtString += "%p";
}
else if (val->getType()->isIntegerTy(8)) {
fmtString += "%c";
}
else if (val->getType()->isIntegerTy(1)) {
val = context.builder->CreateIntCast(val, Type::getInt32Ty(*context.llvmContext), false);
fmtString += "%d";
}
else if (val->getType()->isIntegerTy()) {
fmtString += "%d";
}
else {
fmtString += "%s";
}

args.push_back(val);
argTypes.push_back(val->getType());
}
formatString = context.builder->CreateGlobalStringPtr(fmtString);
args.insert(args.begin(), formatString);
args.insert(args.begin(), ConstantInt::get(Type::getInt64Ty(*context.llvmContext), 0));
args.insert(args.begin(), ConstantPointerNull::get(PointerType::get(Type::getInt8Ty(*context.llvmContext), 0)));

auto needed = context.builder->CreateCall(snprintfFunc, args);
auto neededPlusOne = context.builder->CreateAdd(needed, ConstantInt::get(Type::getInt32Ty(*context.llvmContext), 1));
auto mallocFunc = context.module->getOrInsertFunction(
"malloc",
FunctionType::get(
PointerType::get(Type::getInt8Ty(*context.llvmContext), 0),
Type::getInt32Ty(*context.llvmContext),
false
)
);
auto buffer = context.builder->CreateCall(mallocFunc, neededPlusOne);
args[0] = buffer;
args[1] = context.builder->CreateIntCast(neededPlusOne, Type::getInt64Ty(*context.llvmContext), false);
auto call = context.builder->CreateCall(snprintfFunc, args);
return buffer;
}

auto TypeExprNode::codegen(CodeGenContext &context, std::vector<ErrorMessage>& errors) -> Value* {
return {};
}
Expand Down
19 changes: 14 additions & 5 deletions lexer/Lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ namespace Slangc {
if (identifierValue.empty()) {
return false;
}
// formatted string check
if (identifierValue == "f" && sourceText.at(identifierValue.size()) == '"') {
return false;
}
auto identifierCol = currentColumn;
currentColumn += identifierValue.size();
sourceText.remove_prefix(identifierValue.size());
Expand Down Expand Up @@ -208,20 +212,25 @@ namespace Slangc {
}

bool Lexer::lexString(std::string_view &sourceText) {
bool isFormatted = false;
if (sourceText.front() != '"') {
return false;
if (sourceText.starts_with("f\"")) {
isFormatted = true;
}
else return false;
}
auto stringColumn = currentColumn;
//auto stringValueView = takeWhile(sourceText.substr(1), [=](char c) { return c != '"'; });
auto endIt = sourceText.begin() + 1;
auto endIt = sourceText.begin() + (isFormatted ? 2 : 1);
while (endIt != sourceText.end()) {
if (*endIt == '"')
if (*(endIt - 1) != '\\') {
break;
}
++endIt;
}
auto stringValueView = sourceText.substr(1, std::distance(sourceText.begin(), endIt - 1));
// if isFormatted, take full string including f" and closing "
auto stringValueView = sourceText.substr(0, std::distance(sourceText.begin(), endIt + (isFormatted ? 1 : 1)));

if (!stringValueView.empty()) {
if (stringValueView.back() == sourceText.back()) {
Expand All @@ -230,8 +239,8 @@ namespace Slangc {
}
}

currentColumn += stringValueView.size() + 2;
sourceText.remove_prefix(stringValueView.size() + 2);
currentColumn += stringValueView.size();
sourceText.remove_prefix(stringValueView.size());

auto stringValue = std::string(stringValueView);

Expand Down
13 changes: 13 additions & 0 deletions parser/AST.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ namespace Slangc {
}
};

struct FormattedStringExprNode {
SourceLoc loc{0, 0};
bool isConst = false;
std::vector<ExprPtrVariant> values;
FormattedStringExprNode(SourceLoc loc, std::vector<ExprPtrVariant> values) : loc(loc), values(std::move(values)) {};
auto codegen(CodeGenContext &context, std::vector<ErrorMessage>& errors) -> llvm::Value*;
auto getType(const Context& analysis, std::vector<ErrorMessage>& errors) -> std::optional<ExprPtrVariant> {
return std::make_unique<ArrayExprNode>(loc, std::nullopt,
std::make_unique<TypeExprNode>("character"),
std::make_unique<IntExprNode>(loc, 0));
}
};

struct NilExprNode {
SourceLoc loc{0, 0};
std::optional<ExprPtrVariant> type;
Expand Down
4 changes: 3 additions & 1 deletion parser/ASTFwdDecl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace Slangc {
struct OperatorExprNode;
struct RealExprNode;
struct StringExprNode;
struct FormattedStringExprNode;
struct UnaryOperatorExprNode;
struct VarExprNode;
struct IndexExprNode;
Expand All @@ -44,6 +45,7 @@ namespace Slangc {
using OperatorExprPtr = std::shared_ptr<OperatorExprNode>;
using RealExprPtr = std::shared_ptr<RealExprNode>;
using StringExprPtr = std::shared_ptr<StringExprNode>;
using FormattedStringExprPtr = std::shared_ptr<FormattedStringExprNode>;
using UnaryOperatorExprPtr = std::shared_ptr<UnaryOperatorExprNode>;
using VarExprPtr = std::shared_ptr<VarExprNode>;
using IndexExprPtr = std::shared_ptr<IndexExprNode>;
Expand All @@ -53,7 +55,7 @@ namespace Slangc {

using ExprPtrVariant
= std::variant<ArrayExprPtr, BooleanExprPtr, CharExprPtr, FloatExprPtr,
FuncExprPtr, IntExprPtr, NilExprPtr, OperatorExprPtr, RealExprPtr, StringExprPtr,
FuncExprPtr, IntExprPtr, NilExprPtr, OperatorExprPtr, RealExprPtr, StringExprPtr, FormattedStringExprPtr,
UnaryOperatorExprPtr, VarExprPtr, IndexExprPtr, CallExprPtr, AccessExprPtr, TypeExprPtr>;

using VarExprPtrVariant = std::variant<VarExprPtr, IndexExprPtr>;
Expand Down
72 changes: 70 additions & 2 deletions parser/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ namespace Slangc {
else if (tok.type == TokenType::Float) return createExpr<FloatExprNode>(loc, std::stof(tok.value));
else if (tok.type == TokenType::Nil) return createExpr<NilExprNode>(loc);
else if (tok.type == TokenType::String) {
if (tok.value.size() == 1) return createExpr<CharExprNode>(loc, tok.value[0]);
return createExpr<StringExprNode>(loc, tok.value);
return parseString();
}
else if (tok.type == TokenType::Identifier) {
return parseVar();
Expand Down Expand Up @@ -252,4 +251,73 @@ namespace Slangc {
return expr;
}

auto Parser::parseString() -> std::optional<ExprPtrVariant> {
auto tok = prevToken();
SourceLoc loc = tok.location;

if (tok.value.starts_with("f\"")) {
auto value = tok.value.substr(2, tok.value.size() - 3);
std::vector<ExprPtrVariant> values;
std::string currentValue;
bool closedFmt = true;
for (size_t i = 0; i < value.size(); ++i) {
auto ch = value[i];
// if {{ then push { to currentValue
if (ch == '{') {
if (value.size() > i + 1 && value[i + 1] == '{') {
currentValue += ch;
i += 1;
}
else if (!currentValue.empty()) {
values.push_back(createExpr<StringExprNode>(loc, currentValue));
currentValue.clear();
closedFmt = false;
}
}
else if (ch == '}') {
if (closedFmt && value.size() > i + 1 && value[i + 1] == '}') {
currentValue += ch;
i += 1;
}
else
if (!currentValue.empty()) {
auto buffer = SourceBuffer::CreateFromString(currentValue);
Lexer lexer(std::move(buffer), errors);
lexer.tokenize();
Parser parser(filepath, lexer.tokens, driver, context, errors);
auto expr = parser.parseExpr();
if (expr.has_value() && parser.token == parser.tokens.end() - 1) {
values.push_back(std::move(expr.value()));
}
else {
errors.emplace_back(filename, "Failed to parse expression in formatted string literal.", loc);
hasError = true;
}
currentValue.clear();
closedFmt = true;
}
}
else {
currentValue += ch;
}
}
if (!currentValue.empty()) {
values.push_back(createExpr<StringExprNode>(loc, currentValue));
}
if (!closedFmt) {
errors.emplace_back(filename, "Failed to parse formatted string literal: missing closing '}'.", loc);
hasError = true;
}
return createExpr<FormattedStringExprNode>(loc, std::move(values));
}
else {
auto value = tok.value.substr(1, tok.value.size() - 2);
if (value.size() == 1)
return createExpr<CharExprNode>(loc, value[0]);
else
return createExpr<StringExprNode>(loc, value);
}
return std::nullopt;
}

} // Slangc
3 changes: 2 additions & 1 deletion parser/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace Slangc {
Driver &driver;
std::filesystem::path filepath;
public:
explicit Parser(std::filesystem::path &filepath, std::vector<Token> tokens, Driver &driver, Context &context, std::vector<ErrorMessage> &errors)
Parser(std::filesystem::path &filepath, std::vector<Token> tokens, Driver &driver, Context &context, std::vector<ErrorMessage> &errors)
: context(context), driver(driver), errors(errors), tokens(std::move(tokens)), filepath(filepath) {
token = this->tokens.begin();
filename = filepath.string();
Expand Down Expand Up @@ -77,6 +77,7 @@ namespace Slangc {
auto parseMethodDecl(const std::string& typeName, size_t vtableIndex) -> std::optional<DeclPtrVariant>;
auto parseClassDecl() -> std::optional<DeclPtrVariant>;
auto parseVarDecl() -> std::optional<DeclPtrVariant>;
auto parseString() -> std::optional<ExprPtrVariant>;

private:
std::vector<Token> tokens;
Expand Down
4 changes: 4 additions & 0 deletions source/SourceBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,8 @@ namespace Slangc {
SourceBuffer::SourceBuffer(std::string filename, std::string text) : filename(std::move(filename)), text(std::move(text)) {}

SourceBuffer::SourceBuffer(SourceBuffer &&arg) noexcept : filename(std::move(arg.filename)), text(std::move(arg.text)) {}

auto SourceBuffer::CreateFromString(std::string_view text) -> SourceBuffer {
return SourceBuffer{"", std::string(text)};
}
} // Slangc
1 change: 1 addition & 0 deletions source/SourceBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Slangc {
class SourceBuffer {
public:
static auto CreateFromFile(std::string_view path) -> llvm::Expected<SourceBuffer>;
static auto CreateFromString(std::string_view text) -> SourceBuffer;
SourceBuffer() = delete;
SourceBuffer(const SourceBuffer& arg) = default;
SourceBuffer(SourceBuffer&& arg) noexcept;
Expand Down

0 comments on commit 651bb21

Please sign in to comment.