Merge pull request #27 from ValKmjolnir/develop

🎨 bug fix & add experimental REPL & optimize framework of vm
This commit is contained in:
Li Haokun
2023-09-08 00:39:14 +08:00
committed by GitHub
44 changed files with 1104 additions and 443 deletions

1
.gitignore vendored
View File

@@ -48,6 +48,7 @@ nasal.exe
.vscode
dump
fgfs.log
.temp.*
# build dir
build

View File

@@ -38,7 +38,8 @@ set(NASAL_OBJECT_SOURCE_FILE
${CMAKE_SOURCE_DIR}/src/nasal_parse.cpp
${CMAKE_SOURCE_DIR}/src/nasal_vm.cpp
${CMAKE_SOURCE_DIR}/src/optimizer.cpp
${CMAKE_SOURCE_DIR}/src/symbol_finder.cpp)
${CMAKE_SOURCE_DIR}/src/symbol_finder.cpp
${CMAKE_SOURCE_DIR}/src/repl.cpp)
add_library(nasal-object STATIC ${NASAL_OBJECT_SOURCE_FILE})
target_include_directories(nasal-object PRIVATE ${CMAKE_SOURCE_DIR}/src)

View File

@@ -23,7 +23,8 @@ NASAL_HEADER=\
src/math_lib.h\
src/dylib_lib.h\
src/unix_lib.h\
src/coroutine.h
src/coroutine.h\
src/repl.h
NASAL_OBJECT=\
build/nasal_err.o\
@@ -49,6 +50,7 @@ NASAL_OBJECT=\
build/coroutine.o\
build/nasal_vm.o\
build/nasal_dbg.o\
build/repl.o\
build/main.o
# for test
@@ -67,7 +69,10 @@ build/main.o: $(NASAL_HEADER) src/main.cpp | build
build/nasal_misc.o: src/nasal.h src/nasal_misc.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_misc.cpp -fno-exceptions -fPIC -o build/nasal_misc.o -I .
build/nasal_err.o: src/nasal.h src/nasal_err.h src/nasal_err.cpp | build
build/repl.o: src/nasal.h src/repl.h src/repl.cpp | build
$(CXX) -std=$(STD) -c -O3 src/repl.cpp -fno-exceptions -fPIC -o build/repl.o -I .
build/nasal_err.o: src/nasal.h src/repl.h src/nasal_err.h src/nasal_err.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_err.cpp -fno-exceptions -fPIC -o build/nasal_err.o -I .
build/nasal_gc.o: src/nasal.h src/nasal_gc.h src/nasal_gc.cpp | build
@@ -83,6 +88,7 @@ build/nasal_import.o: \
build/nasal_lexer.o: \
src/nasal.h\
src/repl.h\
src/nasal_err.h\
src/nasal_lexer.h src/nasal_lexer.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_lexer.cpp -fno-exceptions -fPIC -o build/nasal_lexer.o -I .

View File

@@ -8,7 +8,7 @@
#include <winsock.h>
#pragma comment(lib,"ws2_32")
class WSAmanager{
class WSAmanager {
private:
WSAData data;
public:

View File

@@ -2,6 +2,8 @@
#include <iostream>
namespace nasal {
bool ast_dumper::visit_null_expr(null_expr* node) {
dump_indent();
std::cout << "null" << format_location(node->get_location());
@@ -478,4 +480,6 @@ bool ast_dumper::visit_return_expr(return_expr* node) {
pop_indent();
}
return true;
}
}
}

View File

@@ -6,6 +6,8 @@
#include <cstring>
#include <sstream>
namespace nasal {
class ast_dumper:public ast_visitor {
private:
std::vector<std::string> indent;
@@ -78,4 +80,6 @@ public:
void dump(code_block* root) {
root->accept(this);
}
};
};
}

View File

@@ -1,5 +1,7 @@
#include "ast_visitor.h"
namespace nasal {
bool ast_visitor::visit_expr(expr* node) {
node->accept(this);
return true;
@@ -231,4 +233,6 @@ bool ast_visitor::visit_return_expr(return_expr* node) {
node->get_value()->accept(this);
}
return true;
}
}
}

View File

@@ -2,6 +2,8 @@
#include "nasal_ast.h"
namespace nasal {
class ast_visitor {
public:
virtual bool visit_expr(expr*);
@@ -40,4 +42,6 @@ public:
virtual bool visit_continue_expr(continue_expr*);
virtual bool visit_break_expr(break_expr*);
virtual bool visit_return_expr(return_expr*);
};
};
}

View File

@@ -11,21 +11,22 @@
#include "nasal_codegen.h"
#include "nasal_vm.h"
#include "nasal_dbg.h"
#include "repl.h"
#include <unordered_map>
#include <thread>
#include <cstdlib>
const u32 VM_RAW_AST = 1;
const u32 VM_AST = 1<<1;
const u32 VM_CODE = 1<<2;
const u32 VM_TIME = 1<<3;
const u32 VM_EXEC = 1<<4;
const u32 VM_DETAIL = 1<<5;
const u32 VM_DEBUG = 1<<6;
const u32 VM_SYMINFO = 1<<7;
const u32 VM_PROFILE = 1<<8;
const u32 VM_PROF_ALL = 1<<9;
const u32 VM_RAW_AST = 1;
const u32 VM_AST = 1<<1;
const u32 VM_CODE = 1<<2;
const u32 VM_TIME = 1<<3;
const u32 VM_EXEC = 1<<4;
const u32 VM_DETAIL = 1<<5;
const u32 VM_DEBUG = 1<<6;
const u32 VM_SYMINFO = 1<<7;
const u32 VM_PROFILE = 1<<8;
const u32 VM_PROF_ALL = 1<<9;
std::ostream& help(std::ostream& out) {
out
@@ -40,6 +41,7 @@ std::ostream& help(std::ostream& out) {
<< "option:\n"
<< " -h, --help | get help.\n"
<< " -v, --version | get version.\n"
<< " -r, --repl | use repl interpreter(experimental).\n"
<< "\nnasal [option] <file> [argv]\n"
<< "option:\n"
<< " -a, --ast | view ast after link/optimize process.\n"
@@ -87,7 +89,7 @@ std::ostream& version(std::ostream& out) {
num = (num+rand())*(1.0/(RAND_MAX+1.0));
}
if (num<0.01) {
parse::easter_egg();
nasal::parse::easter_egg();
}
out << "nasal interpreter version " << __nasver;
out << " (" << __DATE__ << " " << __TIME__ << ")\n";
@@ -110,11 +112,10 @@ void execute(
using clk = std::chrono::high_resolution_clock;
const auto den = clk::duration::period::den;
lexer lex;
parse parse;
linker ld;
codegen gen;
vm ctx;
nasal::lexer lex;
nasal::parse parse;
nasal::linker ld;
nasal::codegen gen;
// lexer scans file to get tokens
lex.scan(file).chkerr();
@@ -122,7 +123,7 @@ void execute(
// parser gets lexer's token list to compile
parse.compile(lex).chkerr();
if (cmd&VM_RAW_AST) {
auto dumper = std::unique_ptr<ast_dumper>(new ast_dumper);
auto dumper = std::unique_ptr<nasal::ast_dumper>(new nasal::ast_dumper);
dumper->dump(parse.tree());
}
@@ -130,11 +131,10 @@ void execute(
ld.link(parse, file, cmd&VM_DETAIL).chkerr();
// optimizer does simple optimization on ast
auto opt = new optimizer;
auto opt = std::unique_ptr<nasal::optimizer>(new nasal::optimizer);
opt->do_optimization(parse.tree());
delete opt;
if (cmd&VM_AST) {
auto dumper = std::unique_ptr<ast_dumper>(new ast_dumper);
auto dumper = std::unique_ptr<nasal::ast_dumper>(new nasal::ast_dumper);
dumper->dump(parse.tree());
}
@@ -150,15 +150,18 @@ void execute(
// run
auto start = clk::now();
if (cmd&VM_DEBUG) {
dbg().run(gen, ld, argv, cmd&VM_PROFILE, cmd&VM_PROF_ALL);
auto debugger = std::unique_ptr<nasal::dbg>(new nasal::dbg);
debugger->run(gen, ld, argv, cmd&VM_PROFILE, cmd&VM_PROF_ALL);
} else if (cmd&VM_TIME || cmd&VM_EXEC) {
ctx.run(gen, ld, argv, cmd&VM_DETAIL);
auto runtime = std::unique_ptr<nasal::vm>(new nasal::vm);
runtime->run(gen, ld, argv, cmd&VM_DETAIL);
}
// get running time
auto end = clk::now();
if (cmd&VM_TIME) {
f64 tm = (clk::now()-start).count()*1.0/den;
std::clog << "process exited after " << tm << "s.\n\n";
std::clog << "process exited after ";
std::clog << (end-start).count()*1.0/den << "s.\n\n";
}
}
@@ -176,6 +179,9 @@ i32 main(i32 argc, const char* argv[]) {
std::clog << help;
} else if (s=="-v" || s=="--version") {
std::clog << version;
} else if (s=="-r" || s=="--repl") {
auto repl = std::unique_ptr<nasal::repl::repl>(new nasal::repl::repl);
repl->execute();
} else if (s[0]!='-') {
execute(s, {}, VM_EXEC);
} else {

View File

@@ -1,6 +1,8 @@
#include "nasal_ast.h"
#include "ast_visitor.h"
namespace nasal {
void expr::accept(ast_visitor* visitor) {
visitor->visit_expr(this);
}
@@ -365,4 +367,6 @@ return_expr::~return_expr() {
void return_expr::accept(ast_visitor* visitor) {
visitor->visit_return_expr(this);
}
}
}

View File

@@ -6,6 +6,8 @@
#include <vector>
#include <unordered_map>
namespace nasal {
enum class expr_type:u32 {
ast_null = 0, // null node
ast_block, // code block
@@ -669,3 +671,5 @@ public:
expr* get_value() {return value;}
void accept(ast_visitor*) override;
};
}

View File

@@ -1,5 +1,7 @@
#include "nasal_codegen.h"
namespace nasal {
void codegen::init_file_map(const std::vector<std::string>& file_list) {
file_map = {};
for(usize i = 0; i<file_list.size(); ++i) {
@@ -75,11 +77,14 @@ void codegen::regist_str(const std::string& str) {
void codegen::find_symbol(code_block* node) {
auto finder = std::unique_ptr<symbol_finder>(new symbol_finder);
for(const auto& i : finder->do_find(node)) {
if (!experimental_namespace.count(i.file)) {
experimental_namespace[i.file] = {};
if (native_function_mapper.count(i.name)) {
die("definition conflicts with native function", i.location);
}
if (local.empty() && !experimental_namespace.at(i.file).count(i.name)) {
experimental_namespace.at(i.file).insert(i.name);
if (!experimental_namespace.count(i.location.file)) {
experimental_namespace[i.location.file] = {};
}
if (local.empty() && !experimental_namespace.at(i.location.file).count(i.name)) {
experimental_namespace.at(i.location.file).insert(i.name);
}
add_symbol(i.name);
}
@@ -1141,7 +1146,7 @@ void codegen::ret_gen(return_expr* node) {
gen(op_ret, 0, node->get_location());
}
const error& codegen::compile(parse& parse, linker& import, bool repl) {
const error& codegen::compile(parse& parse, linker& import) {
init_native_function();
init_file_map(import.get_file_list());
@@ -1154,7 +1159,7 @@ const error& codegen::compile(parse& parse, linker& import, bool repl) {
// search global symbols first
find_symbol(parse.tree());
gen(op_intg, repl? STACK_DEPTH/2:global.size(), parse.tree()->get_location());
gen(op_intg, global.size(), parse.tree()->get_location());
// generate main block
block_gen(parse.tree());
@@ -1252,3 +1257,5 @@ void codegen::symbol_dump(std::ostream& out) const {
}
}
}
}

View File

@@ -28,6 +28,8 @@
#pragma warning (disable:4267)
#endif
namespace nasal {
class codegen {
private:
error err;
@@ -132,7 +134,9 @@ public:
public:
codegen() = default;
const error& compile(parse&, linker&, bool repl = false);
const error& compile(parse&, linker&);
void print(std::ostream&);
void symbol_dump(std::ostream&) const;
};
}

View File

@@ -1,5 +1,7 @@
#include "nasal_dbg.h"
namespace nasal {
void debug_prof_data::init_counter() {
for(usize i = 0; i<debug_prof_data::operand_size; ++i) {
operand_counter[i] = 0;
@@ -276,3 +278,5 @@ void dbg::run(
imm.clear();
return;
}
}

View File

@@ -9,6 +9,8 @@
#include <algorithm>
#include <unordered_map>
namespace nasal {
class debug_prof_data {
private:
static const usize operand_size = op_code_type::op_ret + 1;
@@ -167,3 +169,5 @@ public:
bool
);
};
}

View File

@@ -1,4 +1,8 @@
#include "nasal_err.h"
#include "repl.h"
namespace nasal {
#ifdef _WIN32
#include <windows.h> // use SetConsoleTextAttribute
struct for_reset {
@@ -6,7 +10,11 @@ struct for_reset {
for_reset() {
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &scr);
}
} reset_ter_color;
static for_reset* singleton() {
static for_reset windows_set;
return &windows_set;
}
};
#endif
std::ostream& back_white(std::ostream& s) {
@@ -57,7 +65,7 @@ std::ostream& white(std::ostream& s) {
std::ostream& reset(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
reset_ter_color.scr.wAttributes);
for_reset::singleton()->scr.wAttributes);
#else
s << "\033[0m";
#endif
@@ -68,7 +76,24 @@ void flstream::load(const std::string& f) {
if (file==f) { // don't need to load a loaded file
return;
} else {
file=f;
file = f;
}
if (repl::info::instance()->in_repl_mode &&
repl::info::instance()->repl_file_name==file) {
const auto& source = repl::info::instance()->repl_file_source;
res = {};
size_t pos = 0, last = 0;
while ((pos = source.find("\n", last))!=std::string::npos) {
res.push_back(source.substr(last, pos - last));
last = pos + 1;
}
if (last<source.length()) {
res.push_back(source.substr(last));
} else {
res.push_back("");
}
return;
}
res.clear();
@@ -85,11 +110,6 @@ void flstream::load(const std::string& f) {
}
}
void error::fatal(const std::string& stage, const std::string& info) {
std::cerr << red << stage << ": " << white << info << reset << "\n\n";
std::exit(1);
}
void error::err(const std::string& stage, const std::string& info) {
++cnt;
std::cerr << red << stage << ": " << white << info << reset << "\n\n";
@@ -166,4 +186,6 @@ void error::err(
}
}
std::cerr << "\n\n";
}
}
}

View File

@@ -8,6 +8,8 @@
#include "nasal.h"
namespace nasal {
struct span {
u32 begin_line;
u32 begin_column;
@@ -54,7 +56,6 @@ private:
public:
error():cnt(0) {}
void fatal(const std::string&, const std::string&);
void err(const std::string&, const std::string&);
void warn(const std::string&, const std::string&);
void err(const std::string&, const span&, const std::string&);
@@ -66,3 +67,5 @@ public:
}
u32 geterr() const {return cnt;}
};
}

View File

@@ -107,9 +107,19 @@ void nas_ghost::set(
}
void nas_ghost::clear() {
if (!ptr || !dtor_ptr) {
// do nothing if pointer is null
if (!ptr) {
return;
}
// do clear pointer if destructor function pointer is null
if (!dtor_ptr) {
type_name = "";
ptr = nullptr;
return;
}
// do destruction
dtor_ptr(ptr);
type_name = "";
ptr = nullptr;
@@ -123,17 +133,20 @@ std::ostream& operator<<(std::ostream& out, const nas_ghost& ghost) {
}
void nas_co::clear() {
for(u32 i = 0; i<STACK_DEPTH; ++i) {
stack[i] = var::nil();
if (!ctx.stack) {
return;
}
for(u32 i = 0; i<STACK_DEPTH; ++i) {
ctx.stack[i] = var::nil();
}
ctx.pc = 0;
ctx.localr = nullptr;
ctx.memr = nullptr;
ctx.canary = stack+STACK_DEPTH-1;
ctx.top = stack;
ctx.canary = ctx.stack+STACK_DEPTH-1;
ctx.top = ctx.stack;
ctx.funcr = var::nil();
ctx.upvalr = var::nil();
ctx.stack = stack;
status = status::suspended;
}
@@ -393,7 +406,13 @@ void gc::concurrent_mark(std::vector<var>& vec, usize begin, usize end) {
}
void gc::mark_context_root(std::vector<var>& bfs_queue) {
// scan global
for(usize i = 0; i<main_context_global_size; ++i) {
auto& val = main_context_global[i];
if (val.type>vm_num) {
bfs_queue.push_back(val);
}
}
// scan now running context, this context maybe related to coroutine or main
for(var* i = rctx->stack; i<=rctx->top; ++i) {
if (i->type>vm_num) {
@@ -469,7 +488,7 @@ void gc::mark_upval(std::vector<var>& bfs_queue, nas_upval& upval) {
void gc::mark_co(std::vector<var>& bfs_queue, nas_co& co) {
bfs_queue.push_back(co.ctx.funcr);
bfs_queue.push_back(co.ctx.upvalr);
for(var* i = co.stack; i<=co.ctx.top; ++i) {
for(var* i = co.ctx.stack; i<=co.ctx.top; ++i) {
if (i->type>vm_num) {
bfs_queue.push_back(*i);
}

View File

@@ -202,14 +202,14 @@ public:
};
struct context {
u32 pc;
var* localr;
var* memr;
var funcr;
var upvalr;
var* canary;
var* stack;
var* top;
u32 pc = 0;
var* localr = nullptr;
var* memr = nullptr;
var funcr = var::nil();
var upvalr = var::nil();
var* canary = nullptr;
var* stack = nullptr;
var* top = nullptr;
};
struct nas_co {
@@ -219,11 +219,16 @@ struct nas_co {
dead
};
var stack[STACK_DEPTH];
context ctx;
status status;
nas_co() {clear();}
nas_co() {
ctx.stack = new var[STACK_DEPTH];
clear();
}
~nas_co() {
delete[] ctx.stack;
}
void clear();
};
@@ -281,6 +286,10 @@ struct gc {
/* main context temporary storage */
context mctx;
/* global storage */
var* main_context_global = nullptr;
usize main_context_global_size = 0;
/* runtime context */
context* rctx = nullptr;
nas_co* cort = nullptr; // running coroutine
@@ -315,7 +324,11 @@ struct gc {
i64 max_mark_time = 0;
i64 max_sweep_time = 0;
gc(context* _ctx): rctx(_ctx) {}
void set(context* _ctx, var* _global, usize _size) {
rctx = _ctx;
main_context_global = _global;
main_context_global_size = _size;
}
private:
/* gc functions */

View File

@@ -1,6 +1,8 @@
#include "nasal_import.h"
#include "symbol_finder.h"
namespace nasal {
linker::linker():
show_path(false), lib_loaded(false),
this_file(""), lib_path("") {
@@ -346,3 +348,5 @@ const error& linker::link(
delete old_tree_root;
return err;
}
}

View File

@@ -20,7 +20,9 @@
#include <vector>
class linker{
namespace nasal {
class linker {
private:
bool show_path;
bool lib_loaded;
@@ -31,6 +33,7 @@ private:
std::vector<std::string> module_load_stack;
std::vector<std::string> envpath;
private:
bool import_check(expr*);
bool exist(const std::string&);
u16 find(const std::string&);
@@ -53,3 +56,5 @@ public:
const auto& get_this_file() const {return this_file;}
const auto& get_lib_path() const {return lib_path;}
};
}

View File

@@ -5,17 +5,20 @@
#endif
#include "nasal_lexer.h"
#include "repl.h"
namespace nasal {
bool lexer::skip(char c) {
return c==' ' || c=='\n' || c=='\t' || c=='\r' || c==0;
}
bool lexer::is_id(char c) {
return (c=='_') || ('a'<=c && c<='z') || ('A'<=c && c<='Z') || (c<0);
return (c=='_') || std::isalpha(c) || (c<0);
}
bool lexer::is_hex(char c) {
return ('0'<=c && c<='9') || ('a'<=c && c<='f') || ('A'<=c && c<='F');
return std::isxdigit(c);
}
bool lexer::is_oct(char c) {
@@ -23,7 +26,7 @@ bool lexer::is_oct(char c) {
}
bool lexer::is_dec(char c) {
return '0'<=c && c<='9';
return std::isdigit(c);
}
bool lexer::is_str(char c) {
@@ -58,10 +61,18 @@ void lexer::err_char() {
err.err("lexer",
{line, column-1, line, column, filename},
"invalid character 0x"+chrhex(c));
err.fatal("lexer", "fatal error occurred, stop");
++invalid_char;
}
void lexer::open(const std::string& file) {
if (repl::info::instance()->in_repl_mode &&
repl::info::instance()->repl_file_name==file) {
err.load(file);
filename = file;
res = repl::info::instance()->repl_file_source;
return;
}
// check file exsits and it is a regular file
struct stat buffer;
if (stat(file.c_str(), &buffer)==0 && !S_ISREG(buffer.st_mode)) {
@@ -115,7 +126,7 @@ std::string lexer::utf8_gen() {
err.err("lexer",
{line, column-1, line, column, filename},
"invalid utf-8 <"+utf_info+">");
err.fatal("lexer", "fatal error occurred, stop");
++invalid_char;
}
str += tmp;
column += 2; // may have some problems because not all the unicode takes 2 space
@@ -127,7 +138,7 @@ token lexer::id_gen() {
u32 begin_line = line;
u32 begin_column = column;
std::string str = "";
while(ptr<res.size() && (is_id(res[ptr])||is_dec(res[ptr]))) {
while(ptr<res.size() && (is_id(res[ptr]) || is_dec(res[ptr]))) {
if (res[ptr]<0) { // utf-8
str += utf8_gen();
} else { // ascii
@@ -350,8 +361,18 @@ const error& lexer::scan(const std::string& file) {
} else {
err_char();
}
if (invalid_char>10) {
err.err("lexer", "too many invalid characters, stop");
break;
}
}
if (toks.size()) {
toks.push_back({toks.back().loc, tok::eof, "<eof>"});
} else {
toks.push_back({{line, column, line, column, filename}, tok::eof, "<eof>"});
}
toks.push_back({{line, column, line, column, filename}, tok::eof, "<eof>"});
res = "";
return err;
}
}

View File

@@ -19,6 +19,8 @@
#define S_ISREG(m) (((m)&0xF000)==0x8000)
#endif
namespace nasal {
enum class tok:u32 {
null=0, // null token (default token type)
num, // number literal
@@ -95,8 +97,11 @@ private:
usize ptr;
std::string filename;
std::string res;
error err;
u64 invalid_char;
std::vector<token> toks;
const std::unordered_map<std::string, tok> typetbl {
{"true" ,tok::tktrue },
{"false" ,tok::tkfalse },
@@ -175,7 +180,9 @@ private:
token dots();
token calc_opr();
public:
lexer(): line(1), column(0), ptr(0), filename(""), res("") {}
lexer(): line(1), column(0), ptr(0), filename(""), res(""), invalid_char(0) {}
const error& scan(const std::string&);
const std::vector<token>& result() const {return toks;}
};
}

View File

@@ -7,7 +7,7 @@
enum op_code_type:u8 {
op_exit, // stop the virtual machine
op_intg, // global scope size, set stack top
op_intg, // init global scope
op_intl, // local scope size
op_loadg, // load global value
op_loadl, // load local value

View File

@@ -1,6 +1,8 @@
#include "nasal_ast.h"
#include "nasal_parse.h"
namespace nasal {
const error& parse::compile(const lexer& lexer) {
toks=lexer.result().data();
ptr=in_func=in_loop=0;
@@ -13,7 +15,7 @@ const error& parse::compile(const lexer& lexer) {
match(tok::semi);
} else if (need_semi_check(root->get_expressions().back()) && !lookahead(tok::eof)) {
// the last expression can be recognized without semi
die(prevspan, "expected \";\"");
die(prevspan, "expected \";\" after this token");
}
}
update_location(root);
@@ -60,6 +62,13 @@ void parse::die(const span& loc, std::string info) {
err.err("parse", loc, info);
}
void parse::next() {
if (lookahead(tok::eof)) {
return;
}
++ptr;
}
void parse::match(tok type, const char* info) {
if (!lookahead(type)) {
if (info) {
@@ -70,13 +79,10 @@ void parse::match(tok type, const char* info) {
case tok::num:die(thisspan, "expected number"); break;
case tok::str:die(thisspan, "expected string"); break;
case tok::id: die(thisspan, "expected identifier");break;
default: die(thisspan, "expected '"+tokname.at(type)+"'"); break;
default: die(thisspan, "expected \""+tokname.at(type)+"\""); break;
}
return;
}
if (lookahead(tok::eof)) {
return;
}
next();
}
@@ -91,7 +97,7 @@ bool parse::is_call(tok type) {
bool parse::check_comma(const tok* panic_set) {
for(u32 i=0;panic_set[i]!=tok::null;++i) {
if (lookahead(panic_set[i])) {
die(prevspan, "expected ',' between scalars");
die(prevspan, "expected \",\" between scalars");
return true;
}
}
@@ -118,7 +124,11 @@ bool parse::check_tuple() {
}
bool parse::check_func_end(expr* node) {
auto type=node->get_type();
// avoid error parse caused nullptr return value
if (!node) {
return true;
}
auto type = node->get_type();
if (type==expr_type::ast_func) {
return true;
} else if (type==expr_type::ast_def) {
@@ -129,9 +139,21 @@ bool parse::check_func_end(expr* node) {
return false;
}
bool parse::check_in_curve_multi_definition() {
// we do not allow syntax like:
// func {}(var a, b, c)
// but we still allow syntax like:
// func {}(var a = 1)
// in fact, this syntax is not recommended
if (!lookahead(tok::lcurve) || toks[ptr+1].type!=tok::var) {
return false;
}
return toks[ptr+2].type==tok::id && toks[ptr+3].type==tok::comma;
}
bool parse::check_special_call() {
// special call means like this: function_name(a:1,b:2,c:3);
u32 check_ptr=ptr, curve=1, bracket=0, brace=0;
u32 check_ptr = ptr, curve = 1, bracket = 0, brace = 0;
while(toks[++check_ptr].type!=tok::eof && curve) {
switch(toks[check_ptr].type) {
case tok::lcurve: ++curve; break;
@@ -154,7 +176,11 @@ bool parse::check_special_call() {
}
bool parse::need_semi_check(expr* node) {
auto type=node->get_type();
// avoid error parse caused nullptr return value
if (!node) {
return true;
}
auto type = node->get_type();
if (type==expr_type::ast_for ||
type==expr_type::ast_forei ||
type==expr_type::ast_while ||
@@ -165,7 +191,10 @@ bool parse::need_semi_check(expr* node) {
}
void parse::update_location(expr* node) {
node->update_location(toks[ptr].loc);
if (!ptr) {
return;
}
node->update_location(toks[ptr-1].loc);
}
null_expr* parse::null() {
@@ -228,7 +257,7 @@ vector_expr* parse::vec() {
}
}
update_location(node);
match(tok::rbracket, "expected ']' when generating vector");
match(tok::rbracket, "expected \"]\" when generating vector");
return node;
}
@@ -240,13 +269,13 @@ hash_expr* parse::hash() {
if (lookahead(tok::comma)) {
match(tok::comma);
} else if (lookahead(tok::id) || lookahead(tok::str)) { // first set of hashmember
die(prevspan, "expected ',' between hash members");
die(prevspan, "expected \",\" between hash members");
} else {
break;
}
}
update_location(node);
match(tok::rbrace, "expected '}' when generating hash");
match(tok::rbrace, "expected \"}\" when generating hash");
return node;
}
@@ -301,13 +330,13 @@ void parse::params(function* func_node) {
if (lookahead(tok::comma)) {
match(tok::comma);
} else if (lookahead(tok::id)) { // first set of identifier
die(prevspan, "expected ',' between identifiers");
die(prevspan, "expected \",\" between identifiers");
} else {
break;
}
}
update_location(func_node);
match(tok::rcurve, "expected ')' after parameter list");
match(tok::rcurve, "expected \")\" after parameter list");
return;
}
@@ -348,7 +377,7 @@ expr* parse::expression() {
case tok::cont: return continue_expression();
case tok::brk: return break_expression();
case tok::ret: return return_expression();
case tok::semi: break;
case tok::semi: break;
default:
die(thisspan, "incorrect token <"+toks[ptr].str+">");
next();
@@ -373,10 +402,10 @@ code_block* parse::expression_block() {
match(tok::semi);
} else if (need_semi_check(node->get_expressions().back()) && !lookahead(tok::rbrace)) {
// the last expression can be recognized without semi
die(prevspan, "expected ';'");
die(prevspan, "expected \";\" after this token");
}
}
match(tok::rbrace, "expected '}' when generating expressions");
match(tok::rbrace, "expected \"}\" when generating expressions");
} else {
node->add_expression(expression());
if (lookahead(tok::semi)) {
@@ -626,8 +655,12 @@ expr* parse::scalar() {
die(thisspan, "expected scalar");
return null();
}
// check call and avoid ambiguous syntax
if (is_call(toks[ptr].type) && !(lookahead(tok::lcurve) && toks[ptr+1].type==tok::var)) {
// check call and avoid ambiguous syntax:
// var f = func(){}
// (var a, b, c) = (1, 2, 3);
// will be incorrectly recognized like:
// var f = func(){}(var a, b, c)
if (is_call(toks[ptr].type) && !check_in_curve_multi_definition()) {
auto call_node = new call_expr(toks[ptr].loc);
call_node->set_first(node);
while(is_call(toks[ptr].type)) {
@@ -685,7 +718,7 @@ call_vector* parse::callv() {
die(thisspan, "expected index value");
}
update_location(node);
match(tok::rbracket, "expected ']' when calling vector");
match(tok::rbracket, "expected \"]\" when calling vector");
return node;
}
@@ -712,7 +745,7 @@ call_function* parse::callf() {
break;
}
update_location(node);
match(tok::rcurve, "expected ')' when calling function");
match(tok::rcurve, "expected \")\" when calling function");
return node;
}
@@ -780,7 +813,7 @@ multi_identifier* parse::multi_id() {
if (lookahead(tok::comma)) {
match(tok::comma);
} else if (lookahead(tok::id)) { // first set of identifier
die(prevspan, "expected ',' between identifiers");
die(prevspan, "expected \",\" between identifiers");
} else {
break;
}
@@ -810,7 +843,7 @@ tuple_expr* parse::multi_scalar() {
}
}
update_location(node);
match(tok::rcurve, "expected ')' after multi-scalar");
match(tok::rcurve, "expected \")\" after multi-scalar");
return node;
}
@@ -877,7 +910,7 @@ for_expr* parse::for_loop() {
} else {
node->set_initial(calc());
}
match(tok::semi, "expected ';' in for(;;)");
match(tok::semi, "expected \";\" in for(;;)");
// conditional expression
if (lookahead(tok::eof)) {
@@ -888,7 +921,7 @@ for_expr* parse::for_loop() {
} else {
node->set_condition(calc());
}
match(tok::semi, "expected ';' in for(;;)");
match(tok::semi, "expected \";\" in for(;;)");
//after loop expression
if (lookahead(tok::eof)) {
@@ -925,7 +958,7 @@ forei_expr* parse::forei_loop() {
die(thisspan, "expected iterator");
}
node->set_iterator(iter_gen());
match(tok::semi, "expected ';' in foreach/forindex(iter;vector)");
match(tok::semi, "expected \";\" in foreach/forindex(iter;vector)");
if (lookahead(tok::eof)) {
die(thisspan, "expected vector");
}
@@ -1026,3 +1059,5 @@ return_expr* parse::return_expression() {
update_location(node);
return node;
}
}

View File

@@ -7,6 +7,8 @@
#include "nasal_lexer.h"
#include "nasal_err.h"
namespace nasal {
class parse {
#define thisspan (toks[ptr].loc)
@@ -77,13 +79,14 @@ private:
private:
void die(const span&,std::string);
void next() {++ptr;}
void match(tok type, const char* info=nullptr);
void next();
void match(tok, const char* info=nullptr);
bool lookahead(tok);
bool is_call(tok);
bool check_comma(const tok*);
bool check_tuple();
bool check_func_end(expr*);
bool check_in_curve_multi_definition();
bool check_special_call();
bool need_semi_check(expr*);
void update_location(expr*);
@@ -154,4 +157,6 @@ public:
}
const error& compile(const lexer&);
static void easter_egg();
};
};
}

View File

@@ -1,11 +1,13 @@
#include "nasal_vm.h"
namespace nasal {
void vm::init(
const std::vector<std::string>& strs,
const std::vector<f64>& nums,
const std::vector<nasal_builtin_table>& natives,
const std::vector<opcode>& code,
const std::unordered_map<std::string, i32>& global,
const std::unordered_map<std::string, i32>& global_symbol,
const std::vector<std::string>& filenames,
const std::vector<std::string>& argv
) {
@@ -13,6 +15,7 @@ void vm::init(
cstr = strs.data();
bytecode = code.data();
files = filenames.data();
global_size = global_symbol.size();
/* set native functions */
native = natives;
@@ -23,28 +26,29 @@ void vm::init(
ctx.memr = nullptr;
ctx.funcr = nil;
ctx.upvalr = nil;
ctx.canary = stack+STACK_DEPTH-1; // stack[STACK_DEPTH-1]
ctx.top = stack;
ctx.stack = stack;
ctx.canary = ctx.stack+STACK_DEPTH-1; // stack[STACK_DEPTH-1]
ctx.top = ctx.stack; // nothing is on stack
/* clear main stack */
/* clear main stack and global */
for(u32 i = 0; i<STACK_DEPTH; ++i) {
stack[i] = nil;
ctx.stack[i] = nil;
global[i] = nil;
}
/* init gc */
ngc.set(&ctx, global, global_size);
ngc.init(strs, argv);
/* init vm globals */
auto map_instance = ngc.alloc(vm_map);
stack[global.at("globals")] = map_instance;
for(const auto& i : global) {
map_instance.map().mapper[i.first] = stack+i.second;
global[global_symbol.at("globals")] = map_instance;
for(const auto& i : global_symbol) {
map_instance.map().mapper[i.first] = global+i.second;
}
/* init vm arg */
auto arg_instance = ngc.alloc(vm_vec);
stack[global.at("arg")] = arg_instance;
global[global_symbol.at("arg")] = arg_instance;
arg_instance.vec().elems = ngc.env_argv;
}
@@ -89,18 +93,19 @@ void vm::valinfo(var& val) {
}
void vm::traceback() {
/* bytecode[0].num is the global size */
var* bottom = ngc.rctx->stack==stack? stack+bytecode[0].num:ngc.rctx->stack;
var* ctx_top = ngc.rctx->stack==stack? ctx.top:ngc.rctx->top;
var* bottom = ctx.stack;
var* top = ctx.top;
std::stack<u32> ret;
for(var* i = bottom; i<=ctx_top; ++i) {
for(var* i = bottom; i<=top; ++i) {
if (i->type==vm_ret && i->ret()!=0) {
ret.push(i->ret());
}
}
ret.push(ctx.pc); // store the position program crashed
std::clog << "trace back ("
<< (ngc.rctx->stack==stack? "main":"coroutine")
<< (ngc.cort? "coroutine":"main")
<< ")\n";
codestream::set(cnum, cstr, native.data(), files);
for(u32 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) {
@@ -121,19 +126,17 @@ void vm::traceback() {
}
void vm::stackinfo(const u32 limit = 10) {
/* bytecode[0].num is the global size */
const u32 gsize = ngc.rctx->stack==stack? bytecode[0].num:0;
var* t = ctx.top;
var* bottom = ngc.rctx->stack+gsize;
var* top = ctx.top;
var* bottom = ctx.stack;
std::clog << "stack (0x" << std::hex << (u64)bottom << std::dec;
std::clog << " <+" << gsize << ">, limit " << limit << ", total ";
std::clog << (t<bottom? 0:(i64)(t-bottom+1)) << ")\n";
for(u32 i = 0; i<limit && t>=bottom; ++i, --t) {
std::clog << ", limit " << limit << ", total ";
std::clog << (top<bottom? 0:(i64)(top-bottom+1)) << ")\n";
for(u32 i = 0; i<limit && top>=bottom; ++i, --top) {
std::clog << " 0x" << std::hex
<< std::setw(6) << std::setfill('0')
<< (u64)(t-ngc.rctx->stack) << std::dec
<< (u64)(top-bottom) << std::dec
<< " ";
valinfo(t[0]);
valinfo(top[0]);
}
}
@@ -141,7 +144,7 @@ void vm::reginfo() {
std::clog << "registers (" << (ngc.cort? "coroutine":"main")
<< ")\n" << std::hex
<< " [pc ] | pc | 0x" << ctx.pc << "\n"
<< " [global] | addr | 0x" << (u64)stack << "\n"
<< " [global] | addr | 0x" << (u64)global << "\n"
<< " [local ] | addr | 0x" << (u64)ctx.localr << "\n"
<< " [memr ] | addr | 0x" << (u64)ctx.memr << "\n"
<< " [canary] | addr | 0x" << (u64)ctx.canary << "\n"
@@ -152,17 +155,16 @@ void vm::reginfo() {
}
void vm::gstate() {
// bytecode[0].op is op_intg
if (!bytecode[0].num || stack[0].type==vm_none) {
if (!global_size || global[0].type==vm_none) {
return;
}
std::clog << "global (0x" << std::hex
<< (u64)stack << " <+0>)\n" << std::dec;
for(u32 i = 0; i<bytecode[0].num; ++i) {
<< (u64)global << ")\n" << std::dec;
for(usize i = 0; i<global_size; ++i) {
std::clog << " 0x" << std::hex << std::setw(6)
<< std::setfill('0') << i << std::dec
<< " ";
valinfo(stack[i]);
valinfo(global[i]);
}
}
@@ -172,7 +174,7 @@ void vm::lstate() {
}
const u32 lsize = ctx.funcr.func().lsize;
std::clog << "local (0x" << std::hex << (u64)ctx.localr
<< " <+" << (u64)(ctx.localr-ngc.rctx->stack)
<< " <+" << (u64)(ctx.localr-ctx.stack)
<< ">)\n" << std::dec;
for(u32 i = 0; i<lsize; ++i) {
std::clog << " 0x" << std::hex << std::setw(6)
@@ -217,7 +219,7 @@ void vm::die(const std::string& str) {
detail();
}
if (ngc.rctx->stack==stack) {
if (!ngc.cort) {
// in main context, exit directly
std::exit(1);
} else {
@@ -442,3 +444,5 @@ mcallh: exec_nodie(o_mcallh); // -0
ret: exec_nodie(o_ret ); // -2
#endif
}
}

View File

@@ -13,10 +13,12 @@
#pragma warning (disable:4102)
#endif
namespace nasal {
class vm {
protected:
/* registers and constants of vm */
/* registers of vm */
context ctx;
/* constants */
@@ -29,7 +31,8 @@ protected:
gc ngc;
/* main stack */
var stack[STACK_DEPTH];
var* global = nullptr;
usize global_size = 0;
/* values used for debugger */
const std::string* files = nullptr; // file name list
@@ -46,7 +49,7 @@ protected:
const std::vector<std::string>&);
/* debug functions */
bool verbose;
bool verbose = false;
void valinfo(var&);
void traceback();
void stackinfo(const u32);
@@ -151,7 +154,14 @@ protected:
public:
/* constructor of vm instance */
vm(): ngc(&ctx), verbose(false) {}
vm() {
ctx.stack = new var[STACK_DEPTH];
global = new var[STACK_DEPTH];
}
~vm() {
delete[] ctx.stack;
delete[] global;
}
/* execution entry */
void run(
@@ -173,7 +183,7 @@ inline bool vm::cond(var& val) {
inline void vm::o_intg() {
// global values store on stack
ctx.top += imm[ctx.pc];
// ctx.top += imm[ctx.pc];
// point to the top
--ctx.top;
}
@@ -184,7 +194,7 @@ inline void vm::o_intl() {
}
inline void vm::o_loadg() {
stack[imm[ctx.pc]] = (ctx.top--)[0];
global[imm[ctx.pc]] = (ctx.top--)[0];
}
inline void vm::o_loadl() {
@@ -534,7 +544,7 @@ inline void vm::o_feach() {
inline void vm::o_callg() {
// get main stack directly
(++ctx.top)[0] = stack[imm[ctx.pc]];
(++ctx.top)[0] = global[imm[ctx.pc]];
}
inline void vm::o_calll() {
@@ -819,7 +829,7 @@ inline void vm::o_slc2() {
}
inline void vm::o_mcallg() {
ctx.memr = stack+imm[ctx.pc];
ctx.memr = global+imm[ctx.pc];
(++ctx.top)[0] = ctx.memr[0];
// push value in this memory space on stack
// to avoid being garbage collected
@@ -946,4 +956,6 @@ inline void vm::o_ret() {
if (!ctx.pc) {
ngc.ctxreserve();
}
}
}
}

View File

@@ -1,5 +1,7 @@
#include "optimizer.h"
namespace nasal {
void optimizer::const_string(
binary_operator* node,
string_literal* left_node,
@@ -129,4 +131,6 @@ bool optimizer::visit_unary_operator(unary_operator* node) {
void optimizer::do_optimization(code_block* root) {
root->accept(this);
}
}
}

View File

@@ -5,6 +5,8 @@
#include "nasal_ast.h"
#include "ast_visitor.h"
namespace nasal {
class optimizer:public ast_visitor {
private:
void const_string(binary_operator*, string_literal*, string_literal*);
@@ -18,3 +20,5 @@ public:
public:
void do_optimization(code_block*);
};
}

147
src/repl.cpp Normal file
View File

@@ -0,0 +1,147 @@
#include "repl.h"
#include "nasal_lexer.h"
#include "nasal_parse.h"
#include "nasal_import.h"
#include "optimizer.h"
#include "nasal_codegen.h"
#include "nasal_vm.h"
namespace nasal {
namespace repl {
std::string repl::readline(std::string prompt = ">>> ") {
auto line = std::string("");
std::cout << prompt;
std::getline(std::cin, line,'\n');
return line;
}
void repl::update_temp_file() {
auto content = std::string("");
for(const auto& i : source) {
content += i + "\n";
}
info::instance()->repl_file_source = content;
}
bool repl::check_need_more_input() {
while(true) {
update_temp_file();
auto nasal_lexer = std::unique_ptr<lexer>(new lexer);
if (nasal_lexer->scan("<nasal-repl>").geterr()) {
return false;
}
i64 in_curve = 0;
i64 in_bracket = 0;
i64 in_brace = 0;
for(const auto& t : nasal_lexer->result()) {
switch(t.type) {
case tok::lcurve: ++in_curve; break;
case tok::rcurve: --in_curve; break;
case tok::lbracket: ++in_bracket; break;
case tok::rbracket: --in_bracket; break;
case tok::lbrace: ++in_brace; break;
case tok::rbrace: --in_brace; break;
default: break;
}
}
if (in_curve<=0 && in_bracket<=0 && in_brace<=0) {
break;
}
auto line = readline("... ");
source.back() += "\n" + line;
}
return true;
}
void repl::help() {
std::cout << ".h, .help | show help\n";
std::cout << ".e, .exit | quit the REPL\n";
std::cout << ".q, .quit | quit the REPL\n";
std::cout << ".c, .clear | clear the screen\n";
std::cout << "\n";
}
bool repl::run() {
update_temp_file();
using clk = std::chrono::high_resolution_clock;
const auto den = clk::duration::period::den;
auto start = clk::now();
auto nasal_lexer = std::unique_ptr<lexer>(new lexer);
auto nasal_parser = std::unique_ptr<parse>(new parse);
auto nasal_linker = std::unique_ptr<linker>(new linker);
auto nasal_opt = std::unique_ptr<optimizer>(new optimizer);
auto nasal_codegen = std::unique_ptr<codegen>(new codegen);
auto nasal_runtime = std::unique_ptr<vm>(new vm);
if (nasal_lexer->scan("<nasal-repl>").geterr()) {
return false;
}
if (nasal_parser->compile(*nasal_lexer).geterr()) {
return false;
}
if (nasal_linker->link(*nasal_parser, "<nasal-repl>", true).geterr()) {
return false;
}
nasal_opt->do_optimization(nasal_parser->tree());
if (nasal_codegen->compile(*nasal_parser, *nasal_linker).geterr()) {
return false;
}
auto end = clk::now();
std::clog << "[compile time: " << (end-start).count()*1000.0/den << " ms]\n";
nasal_runtime->run(*nasal_codegen, *nasal_linker, {}, false);
return true;
}
void repl::execute() {
source = {};
info::instance()->in_repl_mode = true;
std::cout << "Nasal REPL interpreter(experimental).\n";
help();
while(true) {
auto line = readline();
if (!line.length()) {
continue;
}
if (line == ".e" || line == ".exit" || line == ".q" || line == ".quit") {
break;
} else if (line == ".h" || line == ".help") {
help();
continue;
} else if (line == ".c" || line == ".clear") {
std::cout << "\033c";
continue;
} else if (line[0] == "."[0]) {
std::cout << "no such command \"" << line;
std::cout << "\", input \".help\" for help\n";
continue;
}
source.push_back(line);
if (!check_need_more_input()) {
source.pop_back();
continue;
}
// run program
if (!run()) {
source.pop_back();
}
std::cout << "\n";
}
}
}
}

42
src/repl.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include "nasal.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include <sstream>
namespace nasal {
namespace repl {
struct info {
bool in_repl_mode = false;
std::string repl_file_name = "<nasal-repl>";
std::string repl_file_source = "";
// singleton
static info* instance() {
static info info;
return &info;
}
};
class repl {
private:
std::vector<std::string> source;
private:
std::string readline(std::string);
bool check_need_more_input();
void update_temp_file();
void help();
bool run();
public:
void execute();
};
}
}

View File

@@ -1,16 +1,18 @@
#include "symbol_finder.h"
namespace nasal {
bool symbol_finder::visit_definition_expr(definition_expr* node) {
if (node->get_variable_name()) {
symbols.push_back({
node->get_variable_name()->get_name(),
node->get_variable_name()->get_location().file
node->get_variable_name()->get_location()
});
} else {
for(auto i : node->get_variables()->get_variables()) {
symbols.push_back({
i->get_name(),
i->get_location().file
i->get_location()
});
}
}
@@ -30,7 +32,8 @@ bool symbol_finder::visit_iter_expr(iter_expr* node) {
if (node->get_name()) {
symbols.push_back({
node->get_name()->get_name(),
node->get_name()->get_location().file});
node->get_name()->get_location()
});
}
return true;
}
@@ -39,4 +42,6 @@ const std::vector<symbol_finder::symbol_info>& symbol_finder::do_find(code_block
symbols.clear();
root->accept(this);
return symbols;
}
}
}

View File

@@ -7,11 +7,13 @@
#include <sstream>
#include <vector>
namespace nasal {
class symbol_finder:public ast_visitor {
public:
struct symbol_info {
std::string name;
std::string file;
span location;
};
private:
@@ -22,4 +24,6 @@ public:
bool visit_function(function*) override;
bool visit_iter_expr(iter_expr*) override;
const std::vector<symbol_finder::symbol_info>& do_find(code_block*);
};
};
}

View File

@@ -1,16 +1,16 @@
# lib csv.nas
# ValKmjolnir 2022/10/15
var read_csv=func(path,delimeter=",",endline="\n"){
var context=io.readfile(path);
context=split(endline,context);
var read = func(path, delimeter=",", endline="\n"){
var context = io.readfile(path);
context = split(endline, context);
forindex(var i;context){
context[i]=split(delimeter,context[i]);
context[i] = split(delimeter,context[i]);
}
if(size(context)<=1){
die("incorrect csv file <"~path~">: "~size(context)~" line(s).");
}
return {
property:context[0],
data:context[1:]
property: context[0],
data: context[1:]
};
}

View File

@@ -1,78 +1,85 @@
# lib file.nas
# ValKmjolnir 2022/3/6
var file={
SEEK_SET:io.SEEK_SET,
SEEK_CUR:io.SEEK_CUR,
SEEK_END:io.SEEK_END,
new: func(filename,mode="r"){
if(!io.exists(filename))
return nil;
var fd=io.open(filename,mode);
return {
close: func(){io.close(fd);},
read: func(len){
var buf=mut("");
io.read(fd,buf,len);
return buf;
},
write: func(str){return io.write(fd,str);},
seek: func(pos,whence){return io.seek(fd,pos,whence);},
tell: func(){return io.tell(fd);},
readln: func(){return io.readln(fd);},
stat: func(){return io.stat(filename);},
eof: func(){return io.eof(fd);}
};
}
};
var find_all_files_with_extension=func(path,extensions...){
var in_vec=func(ext){
foreach(var i;extensions){
if(ext==i){
var SEEK_SET = io.SEEK_SET;
var SEEK_CUR = io.SEEK_CUR;
var SEEK_END = io.SEEK_END;
var new = func(filename, mode="r"){
if (!io.exists(filename)) {
return nil;
}
var fd = io.open(filename, mode);
return {
close: func() {io.close(fd);},
read: func(len) {
var buf = mut("");
io.read(fd, buf, len);
return buf;
},
write: func(str) {return io.write(fd, str);},
seek: func(pos, whence) {return io.seek(fd, pos, whence);},
tell: func() {return io.tell(fd);},
readln: func() {return io.readln(fd);},
stat: func() {return io.stat(filename);},
eof: func() {return io.eof(fd);}
};
}
var find_all_files_with_extension = func(path, extensions...){
var in_vec = func(ext) {
foreach(var i;extensions) {
if (ext==i){
return 1;
}
}
return 0;
}
var res=[];
var res = [];
foreach(var f;find_all_files(path)){
var tmp=split('.',f);
if(size(tmp)>1 and in_vec(tmp[-1])){
append(res,f);
var tmp = split('.', f);
if (size(tmp)>1 and in_vec(tmp[-1])) {
append(res, f);
}
}
return res;
}
var find_all_files=func(path){
if(!io.exists(path))
var find_all_files = func(path){
if (!io.exists(path)) {
return [];
var dd=unix.opendir(path);
var res=[];
while(var n=unix.readdir(dd))
if(unix.isfile(path~"/"~n))
append(res,n);
}
var dd = unix.opendir(path);
var res = [];
while(var n = unix.readdir(dd))
if(unix.isfile(path~"/"~n)) {
append(res, n);
}
unix.closedir(dd);
return res;
}
var recursive_find_files=func(path){
if(!io.exists(path))
var recursive_find_files = func(path) {
if (!io.exists(path)) {
return nil;
var dd=unix.opendir(path);
var res={
dir:path,
files:[]
}
var dd = unix.opendir(path);
var res = {
dir: path,
files: []
};
while(var n=unix.readdir(dd)){
if(unix.isfile(path~"/"~n)){
while(var n = unix.readdir(dd)) {
if (unix.isfile(path~"/"~n)) {
append(res.files,n);
}elsif(unix.isdir(path~"/"~n) and n!="." and n!=".."){
var tmp=recursive_find_files(path~"/"~n);
if(tmp!=nil)
} elsif (unix.isdir(path~"/"~n) and n!="." and n!="..") {
var tmp = recursive_find_files(path~"/"~n);
if (tmp!=nil) {
append(res.files,tmp);
}
}
}
unix.closedir(dd);
return res;

View File

@@ -1,59 +1,59 @@
# lib json.nas
# 2021 ValKmjolnir
var JSON=func() {
var (
j_eof,
j_lbrace,
j_rbrace,
j_lbrkt,
j_rbrkt,
j_comma,
j_colon,
j_str,
j_num,
j_id
)=(0,1,2,3,4,5,6,7,8,9);
var j_content=[
"eof",
"`{`",
"`}`",
"`[`",
"`]`",
"`,`",
"`:`",
"string",
"number",
"identifier"
];
var (
_j_eof, _j_lbrace, _j_rbrace, _j_lbrkt, _j_rbrkt,
_j_comma, _j_colon, _j_str, _j_num, _j_id
) = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
var text='';
var line=1;
var text_size=0;
var ptr=0;
var token={content:'',type:''};
var _j_content = [
"eof",
"`{`",
"`}`",
"`[`",
"`]`",
"`,`",
"`:`",
"string",
"number",
"identifier"
];
var init=func() {
text='';
line=1;
text_size=0;
ptr=0;
token={content:'',type:''};
var JSON = func() {
var text = "";
var line = 1;
var text_size = 0;
var ptr = 0;
var token = {
content: "",
type: ""
};
var init = func() {
text = "";
line = 1;
text_size = 0;
ptr = 0;
token = {
content: "",
type: ""
};
}
var isnum=func(c) {
var isnum = func(c) {
return '0'<=c and c<='9';
}
var isid=func(c) {
var tmp=c[0];
var isid = func(c) {
var tmp = c[0];
return ('a'[0]<=tmp and tmp<='z'[0]) or
('A'[0]<=tmp and tmp<='Z'[0]) or
c=='_';
('A'[0]<=tmp and tmp<='Z'[0]) or
c=='_';
}
var check=func() {
var c=char(text[ptr]);
var check = func() {
var c = char(text[ptr]);
return (
c=='{' or c=='}' or
c=='[' or c==']' or
@@ -63,151 +63,152 @@ var JSON=func() {
);
}
var get=func(str) {
var get = func(str) {
init();
if(!size(str)) {
if (!size(str)) {
println("JSON.parse: empty string");
str="[]";
str = "[]";
}
text=str;
text_size=size(text);
text = str;
text_size = size(text);
return;
}
var next=func() {
var next = func() {
while(ptr<text_size and !check()) {
if(char(text[ptr])=='\n')
line+=1;
ptr+=1;
if (char(text[ptr])=='\n') {
line += 1;
}
ptr += 1;
}
if(ptr>=text_size) {
token.content="eof";
token.type=j_eof;
token.content = "eof";
token.type = _j_eof;
return;
}
var c=char(text[ptr]);
if(c=='{') {
token.content='{';
token.type=j_lbrace;
} elsif(c=='}') {
token.content='}';
token.type=j_rbrace;
} elsif(c=='[') {
token.content='[';
token.type=j_lbrkt;
} elsif(c==']') {
token.content=']';
token.type=j_rbrkt;
} elsif(c==',') {
token.content=',';
token.type=j_comma;
} elsif(c==':') {
token.content=':';
token.type=j_colon;
} elsif(c=='\"' or c=='\'') {
var strbegin=c;
var s="";
ptr+=1;
var c = char(text[ptr]);
if (c=='{') {
token.content = '{';
token.type = _j_lbrace;
} elsif (c=='}') {
token.content = '}';
token.type = _j_rbrace;
} elsif (c=='[') {
token.content = '[';
token.type = _j_lbrkt;
} elsif (c==']') {
token.content = ']';
token.type = _j_rbrkt;
} elsif (c==',') {
token.content = ',';
token.type = _j_comma;
} elsif (c==':') {
token.content = ':';
token.type = _j_colon;
} elsif (c=='\"' or c=='\'') {
var strbegin = c;
var s = "";
ptr += 1;
while(ptr<text_size and char(text[ptr])!=strbegin) {
s~=char(text[ptr]);
ptr+=1;
if(char(text[ptr-1])=="\\" and ptr<text_size) {
s~=char(text[ptr]);
ptr+=1;
s ~= char(text[ptr]);
ptr += 1;
if (char(text[ptr-1])=="\\" and ptr<text_size) {
s ~= char(text[ptr]);
ptr += 1;
}
}
token.content=s;
token.type=j_str;
} elsif(isnum(c)) {
var s=c;
ptr+=1;
token.type=_j_str;
} elsif (isnum(c)) {
var s = c;
ptr += 1;
while(ptr<text_size and ((isnum(char(text[ptr])) or char(text[ptr])=='.'))) {
s~=char(text[ptr]);
ptr+=1;
s ~= char(text[ptr]);
ptr += 1;
}
ptr-=1;
token.content=num(s);
token.type=j_num;
} elsif(isid(c)) {
var s=c;
ptr+=1;
ptr -= 1;
token.content = num(s);
token.type = _j_num;
} elsif (isid(c)) {
var s = c;
ptr += 1;
while(ptr<text_size and (isid(char(text[ptr])) or isnum(char(text[ptr])))) {
s~=char(text[ptr]);
ptr+=1;
s ~= char(text[ptr]);
ptr += 1;
}
ptr-=1;
token.content=s;
token.type=j_id;
ptr -= 1;
token.content = s;
token.type = _j_id;
}
ptr+=1;
ptr += 1;
return;
}
var match=func(type) {
var match = func(type) {
if(token.type!=type)
println("JSON.parse: line ",line,": expect ",j_content[type]," but get `",token.content,"`.");
println("JSON.parse: line ",line,": expect ",_j_content[type]," but get `",token.content,"`.");
next();
return;
}
var member=func(hash) {
var name=token.content;
if(token.type==j_rbrace) {
var member = func(hash) {
var name = token.content;
if(token.type==_j_rbrace) {
return;
}
if(token.type==j_str) {
match(j_str);
if(token.type==_j_str) {
match(_j_str);
} else {
match(j_id);
match(_j_id);
}
match(j_colon);
if(token.type==j_lbrace) {
hash[name]=hash_gen();
} elsif(token.type==j_lbrkt) {
hash[name]=vec_gen();
} elsif(token.type==j_str or token.type==j_num) {
hash[name]=token.content;
match(_j_colon);
if(token.type==_j_lbrace) {
hash[name] = hash_gen();
} elsif(token.type==_j_lbrkt) {
hash[name] = vec_gen();
} elsif(token.type==_j_str or token.type==_j_num) {
hash[name] = token.content;
next();
}
return;
}
var hash_gen=func() {
var hash={};
match(j_lbrace);
var hash_gen = func() {
var hash = {};
match(_j_lbrace);
member(hash);
while(token.type==j_comma) {
match(j_comma);
while(token.type==_j_comma) {
match(_j_comma);
member(hash);
}
match(j_rbrace);
match(_j_rbrace);
return hash;
}
var vec_gen=func() {
var vec=[];
match(j_lbrkt);
if(token.type==j_lbrace) {
append(vec,hash_gen());
} elsif(token.type==j_lbrkt) {
append(vec,vec_gen());
} elsif(token.type==j_str or token.type==j_num) {
append(vec,token.content);
var vec_gen = func() {
var vec = [];
match(_j_lbrkt);
if(token.type==_j_lbrace) {
append(vec, hash_gen());
} elsif(token.type==_j_lbrkt) {
append(vec, vec_gen());
} elsif(token.type==_j_str or token.type==_j_num) {
append(vec, token.content);
next();
}
while(token.type==j_comma) {
match(j_comma);
if(token.type==j_lbrace) {
append(vec,hash_gen());
} elsif(token.type==j_lbrkt) {
append(vec,vec_gen());
} elsif(token.type==j_str or token.type==j_num) {
append(vec,token.content);
while(token.type==_j_comma) {
match(_j_comma);
if(token.type==_j_lbrace) {
append(vec, hash_gen());
} elsif(token.type==_j_lbrkt) {
append(vec, vec_gen());
} elsif(token.type==_j_str or token.type==_j_num) {
append(vec, token.content);
next();
}
}
match(j_rbrkt);
match(_j_rbrkt);
return vec;
}
@@ -220,10 +221,10 @@ var JSON=func() {
get(str);
next();
if(token.type==j_lbrkt) {
var res=vec_gen();
if (token.type==_j_lbrkt) {
var res = vec_gen();
} else {
var res=hash_gen();
var res = hash_gen();
}
init();
@@ -232,55 +233,55 @@ var JSON=func() {
};
}();
JSON.stringify=func(object) {
JSON.stringify = func(object) {
if(typeof(object)!="hash" and typeof(object)!="vec") {
println("JSON.stringify: must use hashmap or vector");
return "[]";
}
var s="";
var gen=func(elem) {
var t=typeof(elem);
var s = "";
var gen = func(elem) {
var t = typeof(elem);
if(t=="num") {
s~=str(elem);
s ~= str(elem);
} elsif(t=="str") {
s~='"'~elem~'"';
s ~= '"'~elem~'"';
} elsif(t=="vec") {
vgen(elem);
} elsif(t=="hash") {
hgen(elem);
} else {
s~='"undefined"';
s ~= '"undefined"';
}
}
var vgen=func(v) {
s~="[";
var vsize=size(v);
for(var i=0;i<vsize;i+=1) {
var vgen = func(v) {
s ~= "[";
var vsize = size(v);
for(var i = 0; i<vsize; i += 1) {
gen(v[i]);
if(i!=vsize-1) {
if (i!=vsize-1) {
s~=",";
}
}
s~="]";
s ~= "]";
}
var hgen=func(h) {
s~="{";
var k=keys(h);
var vsize=size(k);
for(var i=0;i<vsize;i+=1) {
s~=k[i]~":";
var hgen = func(h) {
s ~= "{";
var k = keys(h);
var vsize = size(k);
for(var i = 0; i<vsize; i += 1) {
s ~= k[i]~":";
gen(h[k[i]]);
if(i!=vsize-1) {
s~=",";
if (i!=vsize-1) {
s ~= ",";
}
}
s~="}";
s ~= "}";
}
if(typeof(object)=="vec") {
if (typeof(object)=="vec") {
vgen(object);
} else {
hgen(object);

View File

@@ -1,20 +1,24 @@
# padding.nas
# ValKmjolnir 2022/9/4
var leftpad=func(s,len,char=" "){
if(typeof(s)=="num")
s=str(s);
var strlen=size(s);
for(var i=strlen;i<len;i+=1)
s=char~s;
var leftpad = func(s, len, char=" ") {
if (typeof(s)=="num") {
s = str(s);
}
var strlen = size(s);
for(var i = strlen; i<len; i += 1) {
s = char~s;
}
return s;
}
var rightpad=func(s,len,char=" "){
if(typeof(s)=="num")
s=str(s);
var strlen=size(s);
for(var i=strlen;i<len;i+=1)
s~=char;
var rightpad = func(s, len, char=" ") {
if (typeof(s)=="num") {
s = str(s);
}
var strlen = size(s);
for(var i = strlen; i<len; i += 1) {
s ~= char;
}
return s;
}

View File

@@ -10,42 +10,152 @@
# available in C++; just use node.getNode(path).whatever() instead.
#
# unfinished
var getprop = func {}
var fgcommand = func {}
var _globals = func {}
var _createCondition = func {}
var _new = func {}
var string = {};
var _getNode = func {};
var _getParent = func {};
var _getChildren = func {};
var _setChildren = func {};
var _alias = func {};
var _equals = func {};
var _unalias = func {};
var _adjustValue = func {};
var _setDoubleValue = func {};
var _setIntValue = func {};
var _setBoolValue = func {};
var _toggleBoolValue = func {};
var _setValue = func {};
var _setValues = func {};
var _getAttribute = func {};
var _setAttribute = func {};
var _getValue = func {};
var _getType = func {};
var _isNumeric = func {};
var _isInt = func {};
var _getIndex = func {};
var _getName = func {};
var _getAliasTarget = func {};
var _getChild = func {};
var _addChild = func {};
var _addChildren = func {};
var _removeChild = func {};
var _removeChildren = func {};
var _removeAllChildren = func {};
var _new = func {
return {
type: "",
name: "",
value: nil,
parent_list:[],
child_list:[]
};
}
var _fg_props_globals = _new();
var _globals = func {
return _fg_props_globals;
}
var getprop = func(arg...) {
die("unimplemented");
}
var addcommand = func(name, code) {
die("unimplemented");
}
var fgcommand = func(name, args = nil) {
die("unimplemented");
}
var _createCondition = func {
die("unimplemented");
}
var _getNode = func {
die("unimplemented");
};
var _getParent = func {
die("unimplemented");
};
var _getChildren = func {
die("unimplemented");
};
var _setChildren = func {
die("unimplemented");
};
var _alias = func {
die("unimplemented");
};
var _equals = func {
die("unimplemented");
};
var _unalias = func {
die("unimplemented");
};
var _adjustValue = func {
die("unimplemented");
};
var _setDoubleValue = func {
die("unimplemented");
};
var _setIntValue = func {
die("unimplemented");
};
var _setBoolValue = func {
die("unimplemented");
};
var _toggleBoolValue = func {
die("unimplemented");
};
var _setValue = func {
die("unimplemented");
};
var _setValues = func {
die("unimplemented");
};
var _getAttribute = func {
die("unimplemented");
};
var _setAttribute = func {
die("unimplemented");
};
var _getValue = func {
die("unimplemented");
};
var _getType = func {
die("unimplemented");
};
var _isNumeric = func {
die("unimplemented");
};
var _isInt = func {
die("unimplemented");
};
var _getIndex = func {
die("unimplemented");
};
var _getName = func {
die("unimplemented");
};
var _getAliasTarget = func {
die("unimplemented");
};
var _getChild = func {
die("unimplemented");
};
var _addChild = func {
die("unimplemented");
};
var _addChildren = func {
die("unimplemented");
};
var _removeChild = func {
die("unimplemented");
};
var _removeChildren = func {
die("unimplemented");
};
var _removeAllChildren = func {
die("unimplemented");
};
var Node = {
@@ -190,8 +300,9 @@ var Node = {
#
Node.new = func(values = nil) {
var result = wrapNode(_new());
if(ishash(values))
if(ishash(values)) {
result.setValues(values);
}
return result;
}
@@ -331,14 +442,18 @@ var wrap = func(node) {
# Node object and its _g (ghost) field set to the specified object.
# Nasal's literal syntax can be pleasingly terse. I like that. :)
#
var wrapNode = func(node) { { parents : [Node], _g : node } }
var wrapNode = func(node) {
return { parents : [Node], _g : node };
}
##
# Global property tree. Set once at initialization. Is that OK?
# Does anything ever call globals.set_props() from C++? May need to
# turn this into a function if so.
#
var globals = wrapNode(_globals());
var globals = func {
return wrapNode(_globals());
}();
##
# Shortcut for props.globals.getNode().

View File

@@ -1,10 +1,10 @@
# string.nas
# ValKmjolnir 2022/10/5
var join=func(sep, vec){
var join = func(sep, vec){
var len = size(vec);
var res = "";
for(var i = 0; i<len; i+=1) {
for(var i = 0; i<len; i += 1) {
res ~= vec[i];
res ~= (i==len-1? "":sep);
}

View File

@@ -49,7 +49,7 @@ var count=func(s,c){
}
var column=func(number){
number=number>=1000?substr(str(number/1000),0,3)~'k':str(number);
number=number>=1000?substr(str(number/1000),0,4)~'k':str(number);
return padding.leftpad(number,6);
}

View File

@@ -159,4 +159,46 @@ for(var i=10;i<1e6;i*=10) {
println("cartesian");
for(var i=100;i<600;i+=100) {
cartesian(i);
}
var person_data = [];
var person = func(name, age) {
append(person_data, [name, age]);
return person_data;
}
var know_data = [];
var know = func(name_a, name_b) {
append(know_data, [name_a, name_b]);
}
person("a", 1);
person("b", 2);
person("c", 3);
person("d", 4);
know("a", "b");
know("b", "c");
know("c", "d");
know("d", "a");
# maybe_know(a, b) :- know(a, tmp), know(tmp, b).
var maybe_know_data = [];
var temp = [];
foreach(var i; know_data) {
foreach(var j; know_data) {
append(temp, [i[0], i[1], j[0], j[1]]);
}
}
foreach(var i; temp) {
if (!cmp(i[1], i[2])) {
append(maybe_know_data, [i, i[0], i[3]]);
}
}
foreach(var res; maybe_know_data) {
println(res[0], " -> ", res[1], " ", res[2]);
}

View File

@@ -48,28 +48,31 @@ http.establish("127.0.0.1",8080);
var highlight_style="
<style>
body{
body {
background: #303030;
}
pre{
pre {
background: #303030;
font-family: 'Courier New', Courier, monospace;
font-size: small;
color: #d4d4d4;
}
code{
code {
color: white;
font-family: 'Courier New', Courier, monospace;
font-size: small;
text-align: left;
}
code.key{color: #f895e7;}
code.id{color: #8abef0;}
code.opr{color: #f895e7;}
code.brace{color: #eafd70;}
code.str{color: #a5ffd0;}
code.num{color: #ff9a41;}
code.note{color: #808080;}
code.key {
color: #f895e7;
font-weight: bold;
}
code.id {color: #8abef0;}
code.opr {color: #f895e7;}
code.brace {color: #eafd70;}
code.str {color: #a5ffd0;}
code.num {color: #ff9a41;}
code.note {color: #808080;}
</style>";
var html_read_file=func(filename){

76
tools/repl.nas Normal file
View File

@@ -0,0 +1,76 @@
# experimental repl
# 2023/8/19 by ValKmjolnir
var help = func() {
println(" .help | show help");
println(" .exit | quit the REPL");
println(" .quit | quit the REPL");
println(" .clear | clear the screen");
println();
}
var content = [];
var log_cache = "";
println("Nasal: This is experimental REPL");
println("Tips : \";\" is automatically added at the end of the input line.");
help();
var count_bracket = func(line) {
var len = size(line);
var count = 0;
for(var i = 0; i < len; i += 1) {
if (line[i] == "{"[0]) {
count += 1;
} elsif (line[i] == "}"[0]) {
count -= 1;
}
}
return count;
}
while(1) {
var line = readline(">>> ");
if (!size(line)) {
continue;
}
if (line == ".exit" or line == ".quit") {
break;
} elsif (line == ".help") {
println();
help();
continue;
} elsif (line == ".clear") {
print("\ec");
continue;
} elsif (line[0] == "."[0]) {
println("no such command \"", line, "\", input \".help\" for help\n");
continue;
}
var in_bracket_level = count_bracket(line);
while(in_bracket_level > 0) {
var temp_line = readline("... ");
in_bracket_level += count_bracket(temp_line);
line ~= temp_line ~ "\n";
}
append(content, line);
var source = "";
foreach(var i; content) {
source ~= i ~ ";\n";
}
io.fout(".temp.nas", source);
var result = system("nasal .temp.nas > .temp.log");
if (result != 0) {
pop(content);
continue;
}
var log = io.readfile(".temp.log");
if (size(log) and size(log) != size(log_cache)) {
println(substr(log, size(log_cache), size(log)), "\n");
log_cache = log;
}
}