Save the progress before introducing the semantic analysis
This commit is contained in:
parent
53ec0c240a
commit
9ff707c726
|
@ -28,6 +28,7 @@
|
||||||
#include "serene/expr.hpp"
|
#include "serene/expr.hpp"
|
||||||
#include "serene/llvm/IR/Value.h"
|
#include "serene/llvm/IR/Value.h"
|
||||||
#include "serene/reader/location.hpp"
|
#include "serene/reader/location.hpp"
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/Optional.h"
|
#include "llvm/ADT/Optional.h"
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -54,6 +55,8 @@ public:
|
||||||
|
|
||||||
std::vector<ast_node>::const_iterator begin();
|
std::vector<ast_node>::const_iterator begin();
|
||||||
std::vector<ast_node>::const_iterator end();
|
std::vector<ast_node>::const_iterator end();
|
||||||
|
llvm::ArrayRef<ast_node> asArrayRef();
|
||||||
|
|
||||||
static bool classof(const AExpr *);
|
static bool classof(const AExpr *);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,11 +26,11 @@
|
||||||
#define NAMESPACE_H
|
#define NAMESPACE_H
|
||||||
|
|
||||||
#include "mlir/IR/BuiltinOps.h"
|
#include "mlir/IR/BuiltinOps.h"
|
||||||
|
#include "mlir/IR/Value.h"
|
||||||
#include "serene/expr.hpp"
|
#include "serene/expr.hpp"
|
||||||
#include "serene/llvm/IR/Value.h"
|
#include "serene/llvm/IR/Value.h"
|
||||||
#include "serene/logger.hpp"
|
#include "serene/logger.hpp"
|
||||||
#include "llvm/ADT/ScopedHashTable.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
#include <llvm/IR/Module.h>
|
#include <llvm/IR/Module.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -40,6 +40,9 @@
|
||||||
#define NAMESPACE_LOG(...) ;
|
#define NAMESPACE_LOG(...) ;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using ScopeMap = llvm::DenseMap<llvm::StringRef, mlir::Value>;
|
||||||
|
using PairT = std::pair<llvm::StringRef, mlir::Value>;
|
||||||
|
|
||||||
namespace serene {
|
namespace serene {
|
||||||
class AExpr;
|
class AExpr;
|
||||||
|
|
||||||
|
@ -47,19 +50,20 @@ class Namespace {
|
||||||
private:
|
private:
|
||||||
ast_tree tree{};
|
ast_tree tree{};
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
llvm::ScopedHashTable<mlir::StringRef, mlir::Value> scope;
|
|
||||||
|
ScopeMap rootScope;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
llvm::Optional<llvm::StringRef> filename;
|
llvm::Optional<llvm::StringRef> filename;
|
||||||
mlir::StringRef name;
|
mlir::StringRef name;
|
||||||
|
|
||||||
Namespace(mlir::StringRef ns_name, llvm::Optional<llvm::StringRef> filename)
|
Namespace(llvm::StringRef ns_name, llvm::Optional<llvm::StringRef> filename);
|
||||||
: filename(filename), name(ns_name){};
|
|
||||||
|
|
||||||
ast_tree &Tree();
|
ast_tree &Tree();
|
||||||
mlir::LogicalResult setTree(ast_tree);
|
mlir::LogicalResult setTree(ast_tree);
|
||||||
mlir::Value lookup(mlir::StringRef name);
|
// TODO: Fix it to return llvm::Optional<mlir::Value> instead
|
||||||
mlir::LogicalResult insert_symbol(mlir::StringRef name, mlir::Value v);
|
llvm::Optional<mlir::Value> lookup(llvm::StringRef name);
|
||||||
|
mlir::LogicalResult insert_symbol(llvm::StringRef name, mlir::Value v);
|
||||||
|
|
||||||
void print_scope();
|
void print_scope();
|
||||||
~Namespace();
|
~Namespace();
|
||||||
|
|
|
@ -78,7 +78,27 @@ def ValueOp: Serene_Op<"value"> {
|
||||||
// Build from fix 64 bit int
|
// Build from fix 64 bit int
|
||||||
build(odsBuilder, odsState, odsBuilder.getI64Type(), (uint64_t) value);
|
build(odsBuilder, odsState, odsBuilder.getI64Type(), (uint64_t) value);
|
||||||
}]>,
|
}]>,
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def FnIdOp: Serene_Op<"fn_id"> {
|
||||||
|
|
||||||
|
let summary = "This operation is just a place holder for an anonymouse function";
|
||||||
|
let description = [{
|
||||||
|
A place holder for an anonymous function. For example consider an expression
|
||||||
|
like `(def a (fn (x) x))`, in this case we don't immediately create an anonymous
|
||||||
|
function since we need to set the name and create the function later.
|
||||||
|
}];
|
||||||
|
|
||||||
|
let arguments = (ins StrAttr:$name);
|
||||||
|
let results = (outs NoneType);
|
||||||
|
|
||||||
|
let builders = [
|
||||||
|
OpBuilder<(ins "std::string":$name), [{
|
||||||
|
// Build from fix 64 bit int
|
||||||
|
build(odsBuilder, odsState, odsBuilder.getNoneType(), odsBuilder.getStringAttr(name));
|
||||||
|
}]>,
|
||||||
|
];
|
||||||
|
}
|
||||||
#endif // SERENE_DIALECT
|
#endif // SERENE_DIALECT
|
||||||
|
|
|
@ -26,36 +26,48 @@
|
||||||
|
|
||||||
#include "mlir/IR/Builders.h"
|
#include "mlir/IR/Builders.h"
|
||||||
#include "mlir/IR/BuiltinOps.h"
|
#include "mlir/IR/BuiltinOps.h"
|
||||||
|
#include "mlir/IR/Identifier.h"
|
||||||
#include "mlir/IR/MLIRContext.h"
|
#include "mlir/IR/MLIRContext.h"
|
||||||
#include "serene/expr.hpp"
|
#include "serene/expr.hpp"
|
||||||
#include "serene/list.hpp"
|
#include "serene/list.hpp"
|
||||||
#include "serene/namespace.hpp"
|
#include "serene/namespace.hpp"
|
||||||
#include "serene/number.hpp"
|
#include "serene/number.hpp"
|
||||||
#include "serene/symbol.hpp"
|
#include "serene/symbol.hpp"
|
||||||
|
#include "llvm/ADT/ScopedHashTable.h"
|
||||||
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace serene {
|
namespace serene {
|
||||||
namespace sir {
|
namespace sir {
|
||||||
|
|
||||||
|
using FnIdPair = std::pair<mlir::Identifier, mlir::FuncOp>;
|
||||||
|
|
||||||
class Generator {
|
class Generator {
|
||||||
private:
|
private:
|
||||||
::mlir::OpBuilder builder;
|
::mlir::OpBuilder builder;
|
||||||
std::unique_ptr<::serene::Namespace> ns;
|
|
||||||
::mlir::ModuleOp module;
|
::mlir::ModuleOp module;
|
||||||
|
std::unique_ptr<::serene::Namespace> ns;
|
||||||
|
std::atomic_int anonymousFnCounter{1};
|
||||||
|
llvm::DenseMap<mlir::Identifier, mlir::FuncOp> anonymousFunctions;
|
||||||
|
llvm::ScopedHashTable<llvm::StringRef, mlir::Value> symbolTable;
|
||||||
|
|
||||||
// TODO: Should we use builder here? maybe there is a better option
|
// TODO: Should we use builder here? maybe there is a better option
|
||||||
::mlir::Location toMLIRLocation(serene::reader::Location *);
|
::mlir::Location toMLIRLocation(serene::reader::Location *);
|
||||||
|
|
||||||
|
mlir::FuncOp generateFn(serene::reader::Location, std::string, List *,
|
||||||
|
List *);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Generator(mlir::MLIRContext &context, ::serene::Namespace *ns)
|
Generator(mlir::MLIRContext &context, ::serene::Namespace *ns)
|
||||||
: builder(&context),
|
: builder(&context),
|
||||||
module(mlir::ModuleOp::create(builder.getUnknownLoc())) {
|
module(mlir::ModuleOp::create(builder.getUnknownLoc(), ns->name)) {
|
||||||
this->ns.reset(ns);
|
this->ns.reset(ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
mlir::Operation *generate(Number *);
|
mlir::Operation *generate(Number *);
|
||||||
mlir::Operation *generate(AExpr *);
|
mlir::Operation *generate(AExpr *);
|
||||||
mlir::Operation *generate(List *);
|
mlir::Value generate(List *);
|
||||||
mlir::ModuleOp generate();
|
mlir::ModuleOp generate();
|
||||||
~Generator();
|
~Generator();
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "serene/expr.hpp"
|
#include "serene/expr.hpp"
|
||||||
#include "serene/llvm/IR/Value.h"
|
#include "serene/llvm/IR/Value.h"
|
||||||
#include "serene/symbol.hpp"
|
#include "serene/symbol.hpp"
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/Optional.h"
|
#include "llvm/ADT/Optional.h"
|
||||||
#include <bits/c++config.h>
|
#include <bits/c++config.h>
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
@ -102,14 +103,16 @@ std::unique_ptr<List> List::from(uint begin) {
|
||||||
|
|
||||||
std::vector<ast_node>::const_iterator first = this->nodes_.begin() + begin;
|
std::vector<ast_node>::const_iterator first = this->nodes_.begin() + begin;
|
||||||
std::vector<ast_node>::const_iterator last = this->nodes_.end();
|
std::vector<ast_node>::const_iterator last = this->nodes_.end();
|
||||||
fmt::print("#### {} {} \n", this->nodes_.size(), this->nodes_.max_size());
|
|
||||||
fmt::print("MM {}\n", this->string_repr());
|
|
||||||
|
|
||||||
std::vector<ast_node> newCopy(first, last);
|
std::vector<ast_node> newCopy(first, last);
|
||||||
|
|
||||||
return std::make_unique<List>(newCopy);
|
return std::make_unique<List>(newCopy);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
llvm::ArrayRef<ast_node> List::asArrayRef() {
|
||||||
|
return llvm::makeArrayRef<ast_node>(this->nodes_);
|
||||||
|
}
|
||||||
|
|
||||||
size_t List::count() const { return nodes_.size(); }
|
size_t List::count() const { return nodes_.size(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "serene/namespace.hpp"
|
#include "serene/namespace.hpp"
|
||||||
#include "serene/expr.hpp"
|
#include "serene/expr.hpp"
|
||||||
#include "serene/llvm/IR/Value.h"
|
#include "serene/llvm/IR/Value.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -33,14 +34,21 @@ using namespace llvm;
|
||||||
|
|
||||||
namespace serene {
|
namespace serene {
|
||||||
|
|
||||||
|
Namespace::Namespace(llvm::StringRef ns_name,
|
||||||
|
llvm::Optional<llvm::StringRef> filename) {
|
||||||
|
|
||||||
|
this->filename = filename;
|
||||||
|
this->name = ns_name;
|
||||||
|
};
|
||||||
|
|
||||||
ast_tree &Namespace::Tree() { return this->tree; }
|
ast_tree &Namespace::Tree() { return this->tree; }
|
||||||
|
|
||||||
mlir::Value Namespace::lookup(mlir::StringRef name) {
|
llvm::Optional<mlir::Value> Namespace::lookup(llvm::StringRef name) {
|
||||||
if (auto value = scope.lookup(name)) {
|
if (auto value = rootScope.lookup(name)) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return llvm::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
mlir::LogicalResult Namespace::setTree(ast_tree t) {
|
mlir::LogicalResult Namespace::setTree(ast_tree t) {
|
||||||
|
@ -54,11 +62,8 @@ mlir::LogicalResult Namespace::setTree(ast_tree t) {
|
||||||
|
|
||||||
mlir::LogicalResult Namespace::insert_symbol(mlir::StringRef name,
|
mlir::LogicalResult Namespace::insert_symbol(mlir::StringRef name,
|
||||||
mlir::Value v) {
|
mlir::Value v) {
|
||||||
if (scope.count(name)) {
|
|
||||||
return mlir::failure();
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.insert(name, v);
|
rootScope.insert(PairT(name, v));
|
||||||
return mlir::success();
|
return mlir::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,13 @@
|
||||||
#include "serene/sir/generator.hpp"
|
#include "serene/sir/generator.hpp"
|
||||||
#include "mlir/IR/Builders.h"
|
#include "mlir/IR/Builders.h"
|
||||||
#include "mlir/IR/BuiltinOps.h"
|
#include "mlir/IR/BuiltinOps.h"
|
||||||
|
#include "mlir/IR/Identifier.h"
|
||||||
#include "mlir/IR/MLIRContext.h"
|
#include "mlir/IR/MLIRContext.h"
|
||||||
|
#include "mlir/IR/Value.h"
|
||||||
#include "serene/expr.hpp"
|
#include "serene/expr.hpp"
|
||||||
#include "serene/sir/dialect.hpp"
|
#include "serene/sir/dialect.hpp"
|
||||||
|
#include "llvm/ADT/STLExtras.h"
|
||||||
|
#include "llvm/ADT/ScopedHashTable.h"
|
||||||
#include "llvm/Support/Casting.h"
|
#include "llvm/Support/Casting.h"
|
||||||
#include "llvm/Support/ErrorHandling.h"
|
#include "llvm/Support/ErrorHandling.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
@ -37,7 +41,8 @@ namespace sir {
|
||||||
|
|
||||||
mlir::ModuleOp Generator::generate() {
|
mlir::ModuleOp Generator::generate() {
|
||||||
for (auto x : ns->Tree()) {
|
for (auto x : ns->Tree()) {
|
||||||
module.push_back(generate(x.get()));
|
auto _ = generate(x.get());
|
||||||
|
UNUSED(_);
|
||||||
}
|
}
|
||||||
|
|
||||||
return module;
|
return module;
|
||||||
|
@ -50,7 +55,8 @@ mlir::Operation *Generator::generate(AExpr *x) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case SereneType::List: {
|
case SereneType::List: {
|
||||||
return generate(llvm::cast<List>(x));
|
generate(llvm::cast<List>(x));
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
@ -59,54 +65,116 @@ mlir::Operation *Generator::generate(AExpr *x) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mlir::Operation *Generator::generate(List *l) {
|
mlir::Value Generator::generate(List *l) {
|
||||||
// auto first = l->at(0);
|
auto first = l->at(0);
|
||||||
|
|
||||||
// if (!first) {
|
if (!first) {
|
||||||
// // Empty list.
|
// Empty list.
|
||||||
// // TODO: Return Nil or empty list.
|
// TODO: Return Nil or empty list.
|
||||||
|
|
||||||
// // Just for now.
|
// Just for now.
|
||||||
// return builder.create<ValueOp>(builder.getUnknownLoc(), (uint64_t)0);
|
return builder.create<ValueOp>(builder.getUnknownLoc(), (uint64_t)0);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (first->get()->getType() == SereneType::Symbol) {
|
if (first->get()->getType() == SereneType::Symbol) {
|
||||||
// auto fnNameSymbol = llvm::dyn_cast<Symbol>(first->get());
|
auto fnNameSymbol = llvm::dyn_cast<Symbol>(first->get());
|
||||||
|
|
||||||
// switch (fnNameSymbol->getName()) {
|
if (fnNameSymbol->getName() == "fn") {
|
||||||
// case "def": {
|
if (l->count() <= 3) {
|
||||||
// if (l->count() != 3) {
|
module.emitError("'fn' form needs exactly 2 arguments.");
|
||||||
// llvm_unreachable("'def' form needs exactly 2 arguments.");
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// auto nameSymbol = llvm::dyn_cast<Symbol>(l->at(1).getValue().get());
|
auto args = llvm::dyn_cast<List>(l->at(1).getValue().get());
|
||||||
|
auto body = llvm::dyn_cast<List>(l->from(2).get());
|
||||||
|
|
||||||
// if (!nameSymbol) {
|
if (!args) {
|
||||||
// llvm_unreachable("The first element of 'def' has to be a symbol.");
|
module.emitError("The first element of 'def' has to be a symbol.");
|
||||||
// }
|
}
|
||||||
// auto value = l->at(2).getValue().get();
|
|
||||||
// auto fn = generate(value);
|
|
||||||
// auto loc(value->location->start);
|
|
||||||
// // Define a function
|
|
||||||
|
|
||||||
// ns.insert_symbol(nameSymbol->getName(), llvm::cast<llvm::Value>(fn));
|
// Create a new anonymous function and push it to the anonymous functions
|
||||||
// // This is a generic function, the return type will be inferred later.
|
// map, later on we
|
||||||
// // Arguments type are uniformly unranked tensors.
|
auto loc(fnNameSymbol->location->start);
|
||||||
// break;
|
auto anonymousName = fmt::format("__fn_{}__", anonymousFnCounter);
|
||||||
// }
|
anonymousFnCounter++;
|
||||||
// default: {
|
|
||||||
// }
|
auto fn = generateFn(loc, anonymousName, args, body);
|
||||||
// }
|
mlir::Identifier fnid = builder.getIdentifier(anonymousName);
|
||||||
// }
|
anonymousFunctions.insert({fnid, fn});
|
||||||
|
return builder.create<FnIdOp>(builder.getUnknownLoc(), fnid.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
// auto rest = l->from(1);
|
// auto rest = l->from(1);
|
||||||
|
// auto loc = toMLIRLocation(&first->get()->location->start);
|
||||||
// for (auto x : *rest) {
|
// for (auto x : *rest) {
|
||||||
// generate(x.get());
|
// generate(x.get());
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return builder.create<ValueOp>(builder.getUnknownLoc(), (uint64_t)0);
|
return builder.create<ValueOp>(builder.getUnknownLoc(), (uint64_t)100);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mlir::FuncOp Generator::generateFn(serene::reader::Location loc,
|
||||||
|
std::string name, List *args, List *body) {
|
||||||
|
|
||||||
|
auto location = toMLIRLocation(&loc);
|
||||||
|
llvm::SmallVector<mlir::Type, 4> arg_types(args->count(),
|
||||||
|
builder.getI64Type());
|
||||||
|
auto func_type = builder.getFunctionType(arg_types, builder.getI64Type());
|
||||||
|
auto proto = mlir::FuncOp::create(location, name, func_type);
|
||||||
|
mlir::FuncOp fn(proto);
|
||||||
|
|
||||||
|
if (!fn) {
|
||||||
|
module.emitError("Can not create the function.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &entryBlock = *fn.addEntryBlock();
|
||||||
|
llvm::ScopedHashTableScope<llvm::StringRef, mlir::Value> scope(symbolTable);
|
||||||
|
|
||||||
|
// Declare all the function arguments in the symbol table.
|
||||||
|
for (const auto arg :
|
||||||
|
llvm::zip(args->asArrayRef(), entryBlock.getArguments())) {
|
||||||
|
|
||||||
|
auto argSymbol = llvm::dyn_cast<Symbol>(std::get<0>(arg).get());
|
||||||
|
if (!argSymbol) {
|
||||||
|
module.emitError("Function parameters must be symbols");
|
||||||
|
}
|
||||||
|
if (symbolTable.count(argSymbol->getName())) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
symbolTable.insert(argSymbol->getName(), std::get<1>(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the insertion point in the builder to the beginning of the function
|
||||||
|
// body, it will be used throughout the codegen to create operations in this
|
||||||
|
// function.
|
||||||
|
builder.setInsertionPointToStart(&entryBlock);
|
||||||
|
|
||||||
|
// Emit the body of the function.
|
||||||
|
if (!generate(body)) {
|
||||||
|
fn.erase();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Implicitly return void if no return statement was emitted.
|
||||||
|
// // FIXME: we may fix the parser instead to always return the last
|
||||||
|
// expression
|
||||||
|
// // (this would possibly help the REPL case later)
|
||||||
|
// ReturnOp returnOp;
|
||||||
|
|
||||||
|
// if (!entryBlock.empty())
|
||||||
|
// returnOp = dyn_cast<ReturnOp>(entryBlock.back());
|
||||||
|
// if (!returnOp) {
|
||||||
|
// builder.create<ReturnOp>(loc(funcAST.getProto()->loc()));
|
||||||
|
// } else if (returnOp.hasOperand()) {
|
||||||
|
// // Otherwise, if this return operation has an operand then add a result
|
||||||
|
// to
|
||||||
|
// // the function.
|
||||||
|
// function.setType(builder.getFunctionType(function.getType().getInputs(),
|
||||||
|
// getType(VarType{})));
|
||||||
|
// }
|
||||||
|
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
mlir::Operation *Generator::generate(Number *x) {
|
mlir::Operation *Generator::generate(Number *x) {
|
||||||
return builder.create<ValueOp>(builder.getUnknownLoc(), x->toI64());
|
return builder.create<ValueOp>(builder.getUnknownLoc(), x->toI64());
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,8 +24,10 @@
|
||||||
|
|
||||||
#include "mlir/IR/Builders.h"
|
#include "mlir/IR/Builders.h"
|
||||||
#include "mlir/IR/MLIRContext.h"
|
#include "mlir/IR/MLIRContext.h"
|
||||||
|
#include "mlir/IR/Value.h"
|
||||||
#include "serene/sir/dialect.hpp"
|
#include "serene/sir/dialect.hpp"
|
||||||
#include "serene/sir/sir.hpp"
|
#include "serene/sir/sir.hpp"
|
||||||
|
#include "llvm/Support/Casting.h"
|
||||||
|
|
||||||
namespace serene {
|
namespace serene {
|
||||||
namespace sir {} // namespace sir
|
namespace sir {} // namespace sir
|
||||||
|
|
Loading…
Reference in New Issue