⚡ avoid repeatedly importing the same modules
This commit is contained in:
parent
ccbe341dc5
commit
49f8cefca0
|
@ -5,6 +5,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace nasal {
|
namespace nasal {
|
||||||
|
|
||||||
|
|
|
@ -1166,13 +1166,21 @@ void codegen::repl_mode_info_output_gen(expr* node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::block_gen(code_block* node) {
|
void codegen::block_gen(code_block* node) {
|
||||||
|
bool is_use_statement = true;
|
||||||
for(auto tmp : node->get_expressions()) {
|
for(auto tmp : node->get_expressions()) {
|
||||||
|
if (tmp->get_type()!=expr_type::ast_use) {
|
||||||
|
is_use_statement = false;
|
||||||
|
}
|
||||||
switch(tmp->get_type()) {
|
switch(tmp->get_type()) {
|
||||||
case expr_type::ast_use:
|
case expr_type::ast_use:
|
||||||
if (!local.empty()) {
|
if (!local.empty()) {
|
||||||
die("module import is not allowed here.",
|
die("module import is not allowed here.",
|
||||||
tmp->get_location()
|
tmp->get_location()
|
||||||
);
|
);
|
||||||
|
} else if (!is_use_statement) {
|
||||||
|
die("module import should be used at the top of the file.",
|
||||||
|
tmp->get_location()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case expr_type::ast_null: break;
|
case expr_type::ast_null: break;
|
||||||
|
|
|
@ -2,22 +2,21 @@
|
||||||
#include "symbol_finder.h"
|
#include "symbol_finder.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace nasal {
|
namespace nasal {
|
||||||
|
|
||||||
linker::linker():
|
linker::linker(): show_path_flag(false), library_loaded(false), this_file("") {
|
||||||
show_path(false), lib_loaded(false),
|
const auto seperator= is_windows()? ';':':';
|
||||||
this_file(""), lib_path("") {
|
const auto PATH = std::string(getenv("PATH"));
|
||||||
char sep = is_windows()? ';':':';
|
usize last = 0, position = PATH.find(seperator, 0);
|
||||||
std::string PATH = getenv("PATH");
|
while(position!=std::string::npos) {
|
||||||
usize last = 0, pos = PATH.find(sep, 0);
|
std::string dirpath = PATH.substr(last, position-last);
|
||||||
while(pos!=std::string::npos) {
|
|
||||||
std::string dirpath = PATH.substr(last, pos-last);
|
|
||||||
if (dirpath.length()) {
|
if (dirpath.length()) {
|
||||||
envpath.push_back(dirpath);
|
envpath.push_back(dirpath);
|
||||||
}
|
}
|
||||||
last = pos+1;
|
last = position+1;
|
||||||
pos = PATH.find(sep, last);
|
position = PATH.find(seperator, last);
|
||||||
}
|
}
|
||||||
if (last!=PATH.length()) {
|
if (last!=PATH.length()) {
|
||||||
envpath.push_back(PATH.substr(last));
|
envpath.push_back(PATH.substr(last));
|
||||||
|
@ -26,32 +25,36 @@ linker::linker():
|
||||||
|
|
||||||
std::string linker::get_path(expr* node) {
|
std::string linker::get_path(expr* node) {
|
||||||
if (node->get_type()==expr_type::ast_use) {
|
if (node->get_type()==expr_type::ast_use) {
|
||||||
auto file_relative_path = std::string(".");
|
auto file_relative_path = std::string("");
|
||||||
for(auto i : reinterpret_cast<use_stmt*>(node)->get_path()) {
|
const auto& path = reinterpret_cast<use_stmt*>(node)->get_path();
|
||||||
file_relative_path += (is_windows()? "\\":"/") +i->get_name();
|
for(auto i : path) {
|
||||||
|
file_relative_path += i->get_name();
|
||||||
|
if (i!=path.back()) {
|
||||||
|
file_relative_path += (is_windows()? "\\":"/");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return file_relative_path + ".nas";
|
return file_relative_path + ".nas";
|
||||||
}
|
}
|
||||||
auto call_node = reinterpret_cast<call_expr*>(node);
|
auto call_node = reinterpret_cast<call_expr*>(node);
|
||||||
auto tmp = reinterpret_cast<call_function*>(call_node->get_calls()[0]);
|
auto arguments = reinterpret_cast<call_function*>(call_node->get_calls()[0]);
|
||||||
auto content = reinterpret_cast<string_literal*>(tmp->get_argument()[0]);
|
auto content = reinterpret_cast<string_literal*>(arguments->get_argument()[0]);
|
||||||
return content->get_content();
|
return content->get_content();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string linker::find_real_file_path(
|
std::string linker::find_real_file_path(
|
||||||
const std::string& filename, const span& location) {
|
const std::string& filename, const span& location) {
|
||||||
// first add file name itself into the file path
|
// first add file name itself into the file path
|
||||||
std::vector<std::string> fpath = {filename};
|
std::vector<std::string> path_list = {filename};
|
||||||
|
|
||||||
// generate search path from environ path
|
// generate search path from environ path
|
||||||
for(const auto& p : envpath) {
|
for(const auto& p : envpath) {
|
||||||
fpath.push_back(p + (is_windows()? "\\":"/") + filename);
|
path_list.push_back(p + (is_windows()? "\\":"/") + filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// search file
|
// search file
|
||||||
for(const auto& i : fpath) {
|
for(const auto& path : path_list) {
|
||||||
if (access(i.c_str(), F_OK)!=-1) {
|
if (access(path.c_str(), F_OK)!=-1) {
|
||||||
return i;
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +64,7 @@ std::string linker::find_real_file_path(
|
||||||
find_real_file_path("std\\lib.nas", location):
|
find_real_file_path("std\\lib.nas", location):
|
||||||
find_real_file_path("std/lib.nas", location);
|
find_real_file_path("std/lib.nas", location);
|
||||||
}
|
}
|
||||||
if (!show_path) {
|
if (!show_path_flag) {
|
||||||
err.err("link",
|
err.err("link",
|
||||||
"in <" + location.file + ">: " +
|
"in <" + location.file + ">: " +
|
||||||
"cannot find file <" + filename + ">, " +
|
"cannot find file <" + filename + ">, " +
|
||||||
|
@ -69,13 +72,14 @@ std::string linker::find_real_file_path(
|
||||||
);
|
);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
auto paths = std::string("");
|
auto path_list_info = std::string("");
|
||||||
for(const auto& i : fpath) {
|
for(const auto& path : path_list) {
|
||||||
paths += " -> " + i + "\n";
|
path_list_info += " -> " + path + "\n";
|
||||||
}
|
}
|
||||||
err.err("link",
|
err.err("link",
|
||||||
"in <" + location.file + ">: " +
|
"in <" + location.file + ">: " +
|
||||||
"cannot find file <" + filename + "> in these paths:\n" + paths
|
"cannot find file <" + filename +
|
||||||
|
"> in these paths:\n" + path_list_info
|
||||||
);
|
);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -123,20 +127,20 @@ bool linker::import_check(expr* node) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool linker::exist(const std::string& file) {
|
bool linker::check_exist_or_record_file(const std::string& file) {
|
||||||
// avoid importing the same file
|
// avoid importing the same file
|
||||||
for(const auto& fname : files) {
|
for(const auto& name : imported_files) {
|
||||||
if (file==fname) {
|
if (file==name) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
files.push_back(file);
|
imported_files.push_back(file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool linker::check_self_import(const std::string& file) {
|
bool linker::check_self_import(const std::string& file) {
|
||||||
for(const auto& i : module_load_stack) {
|
for(const auto& name : module_load_stack) {
|
||||||
if (file==i) {
|
if (file==name) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,29 +164,21 @@ void linker::link(code_block* new_tree_root, code_block* old_tree_root) {
|
||||||
old_tree_root->get_expressions().clear();
|
old_tree_root->get_expressions().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
code_block* linker::import_regular_file(expr* node) {
|
code_block* linker::import_regular_file(
|
||||||
|
expr* node, std::unordered_set<std::string>& used_modules) {
|
||||||
// get filename
|
// get filename
|
||||||
auto filename = get_path(node);
|
auto filename = get_path(node);
|
||||||
|
|
||||||
// clear import("xxx/xxx.nas") node
|
|
||||||
if (node->get_type()!=expr_type::ast_use) {
|
|
||||||
auto cast_node = reinterpret_cast<call_expr*>(node);
|
|
||||||
for(auto i : cast_node->get_calls()) {
|
|
||||||
delete i;
|
|
||||||
}
|
|
||||||
cast_node->get_calls().clear();
|
|
||||||
const auto& location = cast_node->get_first()->get_location();
|
|
||||||
delete cast_node->get_first();
|
|
||||||
cast_node->set_first(new nil_expr(location));
|
|
||||||
// this will make node to call_expr(nil),
|
|
||||||
// will not be optimized when generating bytecodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid infinite loading loop
|
// avoid infinite loading loop
|
||||||
filename = find_real_file_path(filename, node->get_location());
|
filename = find_real_file_path(filename, node->get_location());
|
||||||
if (!filename.length()) {
|
if (!filename.length()) {
|
||||||
return new code_block({0, 0, 0, 0, filename});
|
return new code_block({0, 0, 0, 0, filename});
|
||||||
}
|
}
|
||||||
|
if (used_modules.count(filename)) {
|
||||||
|
return new code_block({0, 0, 0, 0, filename});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check self import, avoid infinite loading loop
|
||||||
if (check_self_import(filename)) {
|
if (check_self_import(filename)) {
|
||||||
err.err("link",
|
err.err("link",
|
||||||
"self-referenced module <" + filename + ">:\n" +
|
"self-referenced module <" + filename + ">:\n" +
|
||||||
|
@ -190,7 +186,7 @@ code_block* linker::import_regular_file(expr* node) {
|
||||||
);
|
);
|
||||||
return new code_block({0, 0, 0, 0, filename});
|
return new code_block({0, 0, 0, 0, filename});
|
||||||
}
|
}
|
||||||
exist(filename);
|
check_exist_or_record_file(filename);
|
||||||
|
|
||||||
module_load_stack.push_back(filename);
|
module_load_stack.push_back(filename);
|
||||||
// start importing...
|
// start importing...
|
||||||
|
@ -214,36 +210,37 @@ code_block* linker::import_regular_file(expr* node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
code_block* linker::import_nasal_lib() {
|
code_block* linker::import_nasal_lib() {
|
||||||
auto filename = find_real_file_path("lib.nas", {0, 0, 0, 0, files[0]});
|
auto path = find_real_file_path(
|
||||||
if (!filename.length()) {
|
"lib.nas", {0, 0, 0, 0, this_file}
|
||||||
return new code_block({0, 0, 0, 0, filename});
|
);
|
||||||
|
if (!path.length()) {
|
||||||
|
return new code_block({0, 0, 0, 0, path});
|
||||||
}
|
}
|
||||||
lib_path = filename;
|
|
||||||
|
|
||||||
// avoid infinite loading library
|
// avoid infinite loading library
|
||||||
if (exist(filename)) {
|
if (check_exist_or_record_file(path)) {
|
||||||
return new code_block({0, 0, 0, 0, filename});
|
return new code_block({0, 0, 0, 0, path});
|
||||||
}
|
}
|
||||||
|
|
||||||
// start importing...
|
// start importing...
|
||||||
lexer nasal_lexer;
|
lexer nasal_lexer;
|
||||||
parse nasal_parser;
|
parse nasal_parser;
|
||||||
if (nasal_lexer.scan(filename).geterr()) {
|
if (nasal_lexer.scan(path).geterr()) {
|
||||||
err.err("link",
|
err.err("link",
|
||||||
"error occurred when analysing library <" + filename + ">"
|
"error occurred when analysing library <" + path + ">"
|
||||||
);
|
);
|
||||||
return new code_block({0, 0, 0, 0, filename});
|
return new code_block({0, 0, 0, 0, path});
|
||||||
}
|
}
|
||||||
if (nasal_parser.compile(nasal_lexer).geterr()) {
|
if (nasal_parser.compile(nasal_lexer).geterr()) {
|
||||||
err.err("link",
|
err.err("link",
|
||||||
"error occurred when analysing library <" + filename + ">"
|
"error occurred when analysing library <" + path + ">"
|
||||||
);
|
);
|
||||||
return new code_block({0, 0, 0, 0, filename});
|
return new code_block({0, 0, 0, 0, path});
|
||||||
}
|
}
|
||||||
// swap result out
|
// swap result out
|
||||||
auto parse_result = nasal_parser.swap(nullptr);
|
auto parse_result = nasal_parser.swap(nullptr);
|
||||||
// check if library has 'import' (in fact it should not)
|
// check if library has 'import' (in fact it should not)
|
||||||
return load(parse_result, filename);
|
return load(parse_result, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string linker::generate_module_name(const std::string& file_path) {
|
std::string linker::generate_module_name(const std::string& file_path) {
|
||||||
|
@ -351,26 +348,35 @@ code_block* linker::load(code_block* program_root, const std::string& filename)
|
||||||
auto tree = new code_block({0, 0, 0, 0, filename});
|
auto tree = new code_block({0, 0, 0, 0, filename});
|
||||||
// load library, this ast will be linked with root directly
|
// load library, this ast will be linked with root directly
|
||||||
// so no extra namespace is generated
|
// so no extra namespace is generated
|
||||||
if (!lib_loaded) {
|
if (!library_loaded) {
|
||||||
auto nasal_lib_code_block = import_nasal_lib();
|
auto nasal_lib_code_block = import_nasal_lib();
|
||||||
// insert nasal lib code to the back of tree
|
// insert nasal lib code to the back of tree
|
||||||
link(tree, nasal_lib_code_block);
|
link(tree, nasal_lib_code_block);
|
||||||
delete nasal_lib_code_block;
|
delete nasal_lib_code_block;
|
||||||
lib_loaded = true;
|
library_loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// load imported modules
|
// load imported modules
|
||||||
|
std::unordered_set<std::string> used_modules = {};
|
||||||
for(auto& import_ast_node : program_root->get_expressions()) {
|
for(auto& import_ast_node : program_root->get_expressions()) {
|
||||||
if (!import_check(import_ast_node)) {
|
if (!import_check(import_ast_node)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
auto module_code_block = import_regular_file(import_ast_node);
|
auto module_code_block = import_regular_file(import_ast_node, used_modules);
|
||||||
// this location should not be a reference, may cause use after free!
|
// this location should not be a reference, may cause use after free!
|
||||||
const auto location = import_ast_node->get_location();
|
const auto location = import_ast_node->get_location();
|
||||||
// after importing the regular file as module, delete this node
|
// after importing the regular file as module, delete this node
|
||||||
delete import_ast_node;
|
delete import_ast_node;
|
||||||
// and replace the node with null_expr node
|
// and replace the node with null_expr node
|
||||||
import_ast_node = new null_expr(location);
|
import_ast_node = new null_expr(location);
|
||||||
|
// avoid repeatedly importing the same module
|
||||||
|
const auto& module_path = module_code_block->get_location().file;
|
||||||
|
if (used_modules.count(module_path)) {
|
||||||
|
delete module_code_block;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
used_modules.insert(module_path);
|
||||||
|
}
|
||||||
// then we generate a function warping the code block,
|
// then we generate a function warping the code block,
|
||||||
// and export the necessary global symbols in this code block
|
// and export the necessary global symbols in this code block
|
||||||
// by generate a return statement, with a hashmap return value
|
// by generate a return statement, with a hashmap return value
|
||||||
|
@ -384,14 +390,16 @@ code_block* linker::load(code_block* program_root, const std::string& filename)
|
||||||
|
|
||||||
const error& linker::link(
|
const error& linker::link(
|
||||||
parse& parse, const std::string& self, bool spath = false) {
|
parse& parse, const std::string& self, bool spath = false) {
|
||||||
show_path = spath;
|
// switch for showing path when errors occur
|
||||||
|
show_path_flag = spath;
|
||||||
|
|
||||||
// initializing file map
|
// initializing file map
|
||||||
this_file = self;
|
this_file = self;
|
||||||
files = {self};
|
imported_files = {self};
|
||||||
module_load_stack = {self};
|
module_load_stack = {self};
|
||||||
|
|
||||||
// scan root and import files
|
// scan root and import files
|
||||||
// then generate a new ast and return to import_ast
|
// then generate a new ast and return to import_ast
|
||||||
// the main file's index is 0
|
|
||||||
auto new_tree_root = load(parse.tree(), self);
|
auto new_tree_root = load(parse.tree(), self);
|
||||||
auto old_tree_root = parse.swap(new_tree_root);
|
auto old_tree_root = parse.swap(new_tree_root);
|
||||||
delete old_tree_root;
|
delete old_tree_root;
|
||||||
|
|
|
@ -18,30 +18,32 @@
|
||||||
#include "nasal_parse.h"
|
#include "nasal_parse.h"
|
||||||
#include "symbol_finder.h"
|
#include "symbol_finder.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace nasal {
|
namespace nasal {
|
||||||
|
|
||||||
class linker {
|
class linker {
|
||||||
private:
|
private:
|
||||||
bool show_path;
|
bool show_path_flag;
|
||||||
bool lib_loaded;
|
bool library_loaded;
|
||||||
std::string this_file;
|
std::string this_file;
|
||||||
std::string lib_path;
|
|
||||||
error err;
|
error err;
|
||||||
std::vector<std::string> files;
|
std::vector<std::string> imported_files;
|
||||||
std::vector<std::string> module_load_stack;
|
std::vector<std::string> module_load_stack;
|
||||||
std::vector<std::string> envpath;
|
std::vector<std::string> envpath;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool import_check(expr*);
|
bool import_check(expr*);
|
||||||
bool exist(const std::string&);
|
bool check_exist_or_record_file(const std::string&);
|
||||||
bool check_self_import(const std::string&);
|
bool check_self_import(const std::string&);
|
||||||
std::string generate_self_import_path(const std::string&);
|
std::string generate_self_import_path(const std::string&);
|
||||||
void link(code_block*, code_block*);
|
void link(code_block*, code_block*);
|
||||||
std::string get_path(expr*);
|
std::string get_path(expr*);
|
||||||
std::string find_real_file_path(const std::string&, const span&);
|
std::string find_real_file_path(const std::string&, const span&);
|
||||||
code_block* import_regular_file(expr*);
|
code_block* import_regular_file(expr*, std::unordered_set<std::string>&);
|
||||||
code_block* import_nasal_lib();
|
code_block* import_nasal_lib();
|
||||||
std::string generate_module_name(const std::string&);
|
std::string generate_module_name(const std::string&);
|
||||||
return_expr* generate_module_return(code_block*);
|
return_expr* generate_module_return(code_block*);
|
||||||
|
@ -51,9 +53,7 @@ private:
|
||||||
public:
|
public:
|
||||||
linker();
|
linker();
|
||||||
const error& link(parse&, const std::string&, bool);
|
const error& link(parse&, const std::string&, bool);
|
||||||
const auto& get_file_list() const {return files;}
|
const auto& get_file_list() const {return imported_files;}
|
||||||
const auto& get_this_file() const {return this_file;}
|
|
||||||
const auto& get_lib_path() const {return lib_path;}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,9 @@ var stack=func(){
|
||||||
return pop(vec);
|
return pop(vec);
|
||||||
},
|
},
|
||||||
top: func() {
|
top: func() {
|
||||||
if(size(vec)!=0)
|
if (size(vec)!=0) {
|
||||||
return vec[-1];
|
return vec[-1];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clear: func() {
|
clear: func() {
|
||||||
vec = [];
|
vec = [];
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std.file;
|
use std.file;
|
||||||
|
use std.padding;
|
||||||
|
use std.process_bar;
|
||||||
|
|
||||||
var tips = func() {
|
var tips = func() {
|
||||||
println("usage:");
|
println("usage:");
|
||||||
|
@ -20,7 +22,7 @@ var needle = arg[0];
|
||||||
var do_flat = func(vec) {
|
var do_flat = func(vec) {
|
||||||
var flat = [];
|
var flat = [];
|
||||||
var bfs = [vec];
|
var bfs = [vec];
|
||||||
while(size(bfs)) {
|
while(size(bfs)!=0) {
|
||||||
var d = pop(bfs);
|
var d = pop(bfs);
|
||||||
foreach(var f; d.files) {
|
foreach(var f; d.files) {
|
||||||
if (ishash(f)) {
|
if (ishash(f)) {
|
||||||
|
@ -34,13 +36,13 @@ var do_flat = func(vec) {
|
||||||
return flat;
|
return flat;
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = 0;
|
var result = [];
|
||||||
foreach(var f; do_flat(file.recursive_find_files("."))) {
|
var all_files = file.recursive_find_files(".");
|
||||||
|
foreach(var f; do_flat(all_files)) {
|
||||||
var pos = find(needle, f);
|
var pos = find(needle, f);
|
||||||
if (pos == -1) {
|
if (pos == -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
count += 1;
|
|
||||||
var begin = substr(f, 0, pos);
|
var begin = substr(f, 0, pos);
|
||||||
var end = pos+size(needle)>=size(f)? "":substr(f, pos+size(needle), size(f));
|
var end = pos+size(needle)>=size(f)? "":substr(f, pos+size(needle), size(f));
|
||||||
var file_size = fstat(f).st_size;
|
var file_size = fstat(f).st_size;
|
||||||
|
@ -58,7 +60,20 @@ foreach(var f; do_flat(file.recursive_find_files("."))) {
|
||||||
unit = "gb";
|
unit = "gb";
|
||||||
}
|
}
|
||||||
file_size = int(file_size);
|
file_size = int(file_size);
|
||||||
println(begin, "\e[95;1m", needle, "\e[0m", end, " | ", file_size, " ", unit);
|
append(result, {
|
||||||
|
info: begin~"\e[95;1m"~needle~"\e[0m"~end,
|
||||||
|
size: file_size,
|
||||||
|
unit: unit
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
println("\n", count, " result(s).");
|
var max_len = 0;
|
||||||
|
foreach(var elem; result) {
|
||||||
|
var temp = size(str(elem.size)~" "~elem.unit);
|
||||||
|
max_len = math.max(max_len, temp);
|
||||||
|
}
|
||||||
|
foreach(var elem; result) {
|
||||||
|
var temp = padding.leftpad(str(elem.size)~" "~elem.unit, max_len);
|
||||||
|
println(temp, " | ", elem.info);
|
||||||
|
}
|
||||||
|
println("\n", size(result), " result(s).");
|
Loading…
Reference in New Issue