1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

initial commit

This commit is contained in:
initial commit 2019-09-07 14:03:22 +04:00 committed by vvaltman
commit c2da007f40
1610 changed files with 398047 additions and 0 deletions

200
crypto/fift/Dictionary.cpp Normal file
View file

@ -0,0 +1,200 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "Dictionary.h"
namespace fift {
//
// WordDef
//
void WordDef::run(IntCtx& ctx) const {
auto next = run_tail(ctx);
while (next.not_null()) {
next = next->run_tail(ctx);
}
}
//
// StackWord
//
Ref<WordDef> StackWord::run_tail(IntCtx& ctx) const {
f(ctx.stack);
return {};
}
//
// CtxWord
//
Ref<WordDef> CtxWord::run_tail(IntCtx& ctx) const {
f(ctx);
return {};
}
//
// CtxTailWord
//
Ref<WordDef> CtxTailWord::run_tail(IntCtx& ctx) const {
return f(ctx);
}
//
// WordList
//
WordList::WordList(std::vector<Ref<WordDef>>&& _list) : list(std::move(_list)) {
}
WordList::WordList(const std::vector<Ref<WordDef>>& _list) : list(_list) {
}
WordList& WordList::push_back(Ref<WordDef> word_def) {
list.push_back(std::move(word_def));
return *this;
}
WordList& WordList::push_back(WordDef& wd) {
list.emplace_back(&wd);
return *this;
}
Ref<WordDef> WordList::run_tail(IntCtx& ctx) const {
if (list.empty()) {
return {};
}
auto it = list.cbegin(), it2 = list.cend() - 1;
while (it < it2) {
(*it)->run(ctx);
++it;
}
return *it;
}
void WordList::close() {
list.shrink_to_fit();
}
WordList& WordList::append(const std::vector<Ref<WordDef>>& other) {
list.insert(list.end(), other.begin(), other.end());
return *this;
}
//
// WordRef
//
WordRef::WordRef(Ref<WordDef> _def, bool _act) : def(std::move(_def)), active(_act) {
}
WordRef::WordRef(StackWordFunc func) : def(Ref<StackWord>{true, std::move(func)}), active(false) {
}
WordRef::WordRef(CtxWordFunc func, bool _act) : def(Ref<CtxWord>{true, std::move(func)}), active(_act) {
}
WordRef::WordRef(CtxTailWordFunc func, bool _act) : def(Ref<CtxTailWord>{true, std::move(func)}), active(_act) {
}
Ref<WordDef> WordRef::get_def() const & {
return def;
}
Ref<WordDef> WordRef::get_def() && {
return std::move(def);
}
void WordRef::operator()(IntCtx& ctx) const {
def->run(ctx);
}
bool WordRef::is_active() const {
return active;
}
//
// Dictionary
//
WordRef* Dictionary::lookup(td::Slice name) {
auto it = words_.find(name);
if (it == words_.end()) {
return nullptr;
}
return &it->second;
}
void Dictionary::def_ctx_word(std::string name, CtxWordFunc func) {
def_word(std::move(name), std::move(func));
}
void Dictionary::def_active_word(std::string name, CtxWordFunc func) {
Ref<WordDef> wdef = Ref<CtxWord>{true, std::move(func)};
def_word(std::move(name), {std::move(wdef), true});
}
void Dictionary::def_stack_word(std::string name, StackWordFunc func) {
def_word(std::move(name), std::move(func));
}
void Dictionary::def_ctx_tail_word(std::string name, CtxTailWordFunc func) {
def_word(std::move(name), std::move(func));
}
void Dictionary::def_word(std::string name, WordRef word) {
auto res = words_.emplace(name, std::move(word));
LOG_IF(FATAL, !res.second) << "Cannot redefine word: " << name;
}
void Dictionary::undef_word(td::Slice name) {
auto it = words_.find(name);
if (it == words_.end()) {
return;
}
words_.erase(it);
}
void interpret_nop(vm::Stack& stack) {
}
Ref<WordDef> Dictionary::nop_word_def = Ref<StackWord>{true, interpret_nop};
//
// functions for wordef
//
Ref<WordDef> pop_exec_token(vm::Stack& stack) {
stack.check_underflow(1);
auto wd_ref = stack.pop().as_object<WordDef>();
if (wd_ref.is_null()) {
throw IntError{"execution token expected"};
}
return wd_ref;
}
Ref<WordList> pop_word_list(vm::Stack& stack) {
stack.check_underflow(1);
auto wl_ref = stack.pop().as_object<WordList>();
if (wl_ref.is_null()) {
throw IntError{"word list expected"};
}
return wl_ref;
}
void push_argcount(vm::Stack& stack, int args) {
stack.push_smallint(args);
stack.push({vm::from_object, Dictionary::nop_word_def});
}
} // namespace fift

182
crypto/fift/Dictionary.h Normal file
View file

@ -0,0 +1,182 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include <functional>
#include <map>
#include "IntCtx.h"
namespace fift {
using td::Ref;
/*
*
* WORD CLASSES
*
*/
typedef std::function<void(vm::Stack&)> StackWordFunc;
typedef std::function<void(IntCtx&)> CtxWordFunc;
class WordDef : public td::CntObject {
public:
WordDef() = default;
virtual ~WordDef() override = default;
virtual Ref<WordDef> run_tail(IntCtx& ctx) const = 0;
void run(IntCtx& ctx) const;
virtual bool is_list() const {
return false;
}
virtual long long list_size() const {
return -1;
}
virtual const std::vector<Ref<WordDef>>* get_list() const {
return nullptr;
}
};
class StackWord : public WordDef {
StackWordFunc f;
public:
StackWord(StackWordFunc _f) : f(std::move(_f)) {
}
~StackWord() override = default;
Ref<WordDef> run_tail(IntCtx& ctx) const override;
};
class CtxWord : public WordDef {
CtxWordFunc f;
public:
CtxWord(CtxWordFunc _f) : f(std::move(_f)) {
}
~CtxWord() override = default;
Ref<WordDef> run_tail(IntCtx& ctx) const override;
};
typedef std::function<Ref<WordDef>(IntCtx&)> CtxTailWordFunc;
class CtxTailWord : public WordDef {
CtxTailWordFunc f;
public:
CtxTailWord(CtxTailWordFunc _f) : f(std::move(_f)) {
}
~CtxTailWord() override = default;
Ref<WordDef> run_tail(IntCtx& ctx) const override;
};
class WordList : public WordDef {
std::vector<Ref<WordDef>> list;
public:
~WordList() override = default;
WordList() = default;
WordList(std::vector<Ref<WordDef>>&& _list);
WordList(const std::vector<Ref<WordDef>>& _list);
WordList& push_back(Ref<WordDef> word_def);
WordList& push_back(WordDef& wd);
Ref<WordDef> run_tail(IntCtx& ctx) const override;
void close();
bool is_list() const override {
return true;
}
long long list_size() const override {
return (long long)list.size();
}
const std::vector<Ref<WordDef>>* get_list() const override {
return &list;
}
WordList& append(const std::vector<Ref<WordDef>>& other);
WordList* make_copy() const override {
return new WordList(list);
}
};
class WordRef {
Ref<WordDef> def;
bool active;
public:
WordRef() = delete;
WordRef(const WordRef& ref) = default;
WordRef(WordRef&& ref) = default;
WordRef(Ref<WordDef> _def, bool _act = false);
WordRef(StackWordFunc func);
WordRef(CtxWordFunc func, bool _act = false);
WordRef(CtxTailWordFunc func, bool _act = false);
//WordRef(const std::vector<Ref<WordDef>>& word_list);
//WordRef(std::vector<Ref<WordDef>>&& word_list);
WordRef& operator=(const WordRef&) = default;
WordRef& operator=(WordRef&&) = default;
Ref<WordDef> get_def() const &;
Ref<WordDef> get_def() &&;
void operator()(IntCtx& ctx) const;
bool is_active() const;
~WordRef() = default;
};
/*
WordRef::WordRef(const std::vector<Ref<WordDef>>& word_list) : def(Ref<WordList>{true, word_list}) {
}
WordRef::WordRef(std::vector<Ref<WordDef>>&& word_list) : def(Ref<WordList>{true, std::move(word_list)}) {
}
*/
/*
*
* DICTIONARIES
*
*/
class Dictionary {
public:
WordRef* lookup(td::Slice name);
void def_ctx_word(std::string name, CtxWordFunc func);
void def_ctx_tail_word(std::string name, CtxTailWordFunc func);
void def_active_word(std::string name, CtxWordFunc func);
void def_stack_word(std::string name, StackWordFunc func);
void def_word(std::string name, WordRef word);
void undef_word(td::Slice name);
auto begin() const {
return words_.begin();
}
auto end() const {
return words_.end();
}
static Ref<WordDef> nop_word_def;
private:
std::map<std::string, WordRef, std::less<>> words_;
};
/*
*
* AUX FUNCTIONS FOR WORD DEFS
*
*/
Ref<WordDef> pop_exec_token(vm::Stack& stack);
Ref<WordList> pop_word_list(vm::Stack& stack);
void push_argcount(vm::Stack& stack, int args);
} // namespace fift

76
crypto/fift/Fift.cpp Normal file
View file

@ -0,0 +1,76 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "Fift.h"
#include "words.h"
#include "td/utils/PathView.h"
namespace fift {
Fift::Fift(Config config) : config_(std::move(config)) {
}
Fift::Config& Fift::config() {
return config_;
}
td::Result<int> Fift::interpret_file(std::string fname, std::string current_dir, bool is_interactive) {
auto r_file = config_.source_lookup.lookup_source(fname, current_dir);
if (r_file.is_error()) {
return td::Status::Error("cannot locate file `" + fname + "`");
}
auto file = r_file.move_as_ok();
IntCtx ctx;
std::stringstream ss(file.data);
ctx.input_stream = &ss;
ctx.filename = td::PathView(file.path).file_name().str();
ctx.currentd_dir = td::PathView(file.path).parent_dir().str();
ctx.include_depth = is_interactive ? 0 : 1;
return do_interpret(ctx);
}
td::Result<int> Fift::interpret_istream(std::istream& stream, std::string current_dir, bool is_interactive) {
IntCtx ctx;
ctx.input_stream = &stream;
ctx.filename = "stdin";
ctx.currentd_dir = current_dir;
ctx.include_depth = is_interactive ? 0 : 1;
return do_interpret(ctx);
}
td::Result<int> Fift::do_interpret(IntCtx& ctx) {
ctx.ton_db = &config_.ton_db;
ctx.source_lookup = &config_.source_lookup;
ctx.dictionary = &config_.dictionary;
ctx.output_stream = config_.output_stream;
ctx.error_stream = config_.error_stream;
if (!ctx.output_stream) {
return td::Status::Error("Cannot run interpreter without output_stream");
}
try {
return funny_interpret_loop(ctx);
} catch (fift::IntError ab) {
return td::Status::Error(ab.msg);
} catch (fift::Quit q) {
return q.res;
}
return 0;
}
} // namespace fift

53
crypto/fift/Fift.h Normal file
View file

@ -0,0 +1,53 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "SourceLookup.h"
#include "vm/db/TonDb.h"
#include "Dictionary.h"
#include "td/utils/Status.h"
namespace fift {
struct IntCtx;
int funny_interpret_loop(IntCtx& ctx);
struct Fift {
public:
struct Config {
fift::SourceLookup source_lookup;
vm::TonDb ton_db;
fift::Dictionary dictionary;
std::ostream* output_stream{&std::cout};
std::ostream* error_stream{&std::cerr};
};
// Fift must own ton_db and dictionary, no concurrent access is allowed
explicit Fift(Config config);
td::Result<int> interpret_file(std::string fname, std::string current_dir, bool interactive = false);
td::Result<int> interpret_istream(std::istream& stream, std::string current_dir, bool interactive = true);
Config& config();
private:
Config config_;
td::Result<int> do_interpret(IntCtx& ctx);
};
} // namespace fift

192
crypto/fift/IntCtx.cpp Normal file
View file

@ -0,0 +1,192 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "IntCtx.h"
namespace fift {
td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx) {
if (ctx.include_depth) {
return os << ctx.filename << ":" << ctx.line_no << ": ";
} else {
return os;
}
}
std::ostream& operator<<(std::ostream& os, const IntCtx& ctx) {
return os << (PSLICE() << ctx).c_str();
}
void CharClassifier::import_from_string(td::Slice str, int space_cls) {
set_char_class(' ', space_cls);
set_char_class('\t', space_cls);
int cls = 3;
for (char c : str) {
if (c == ' ') {
cls--;
} else {
set_char_class(c, cls);
}
}
}
void CharClassifier::import_from_string(std::string str, int space_cls) {
import_from_string(td::Slice{str}, space_cls);
}
void CharClassifier::import_from_string(const char* str, int space_cls) {
import_from_string(td::Slice{str}, space_cls);
}
CharClassifier CharClassifier::from_string(td::Slice str, int space_cls) {
return CharClassifier{str, space_cls};
}
void CharClassifier::set_char_class(int c, int cl) {
c &= 0xff;
cl &= 3;
int offs = (c & 3) * 2;
int mask = (3 << offs);
cl <<= offs;
unsigned char* p = data_ + (c >> 2);
*p = static_cast<unsigned char>((*p & ~mask) | cl);
}
IntCtx::Savepoint::Savepoint(IntCtx& _ctx, std::string new_filename, std::string new_current_dir,
std::istream* new_input_stream)
: ctx(_ctx)
, old_line_no(_ctx.line_no)
, old_filename(_ctx.filename)
, old_current_dir(_ctx.currentd_dir)
, old_input_stream(_ctx.input_stream)
, old_curline(_ctx.str)
, old_curpos(_ctx.input_ptr - _ctx.str.c_str()) {
ctx.line_no = 0;
ctx.filename = new_filename;
ctx.currentd_dir = new_current_dir;
ctx.input_stream = new_input_stream;
ctx.str = "";
ctx.input_ptr = 0;
++(ctx.include_depth);
}
IntCtx::Savepoint::~Savepoint() {
ctx.line_no = old_line_no;
ctx.filename = old_filename;
ctx.currentd_dir = old_current_dir;
ctx.input_stream = old_input_stream;
ctx.str = old_curline;
ctx.input_ptr = ctx.str.c_str() + old_curpos;
--(ctx.include_depth);
}
bool IntCtx::load_next_line() {
if (!std::getline(*input_stream, str)) {
return false;
}
if (!str.empty() && str.back() == '\r') {
str.pop_back();
}
set_input(str);
return true;
}
bool IntCtx::is_sb() const {
return !eof() && line_no == 1 && *input_ptr == '#' && input_ptr[1] == '!';
}
td::Slice IntCtx::scan_word_to(char delim, bool err_endl) {
auto ptr = input_ptr;
while (*ptr && *ptr != delim) {
ptr++;
}
if (*ptr) {
std::swap(ptr, input_ptr);
return td::Slice{ptr, input_ptr++};
} else if (err_endl && delim) {
throw IntError{std::string{"end delimiter `"} + delim + "` not found"};
} else {
std::swap(ptr, input_ptr);
return td::Slice{ptr, input_ptr};
}
}
td::Slice IntCtx::scan_word() {
skipspc(true);
auto ptr = input_ptr;
while (*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '\r') {
ptr++;
}
auto ptr2 = ptr;
std::swap(ptr, input_ptr);
skipspc();
return td::Slice{ptr, ptr2};
}
td::Slice IntCtx::scan_word_ext(const CharClassifier& classifier) {
skipspc(true);
auto ptr = input_ptr;
while (*ptr && *ptr != '\r' && *ptr != '\n') {
int c = classifier.classify(*ptr);
if ((c & 1) && ptr != input_ptr) {
break;
}
ptr++;
if (c & 2) {
break;
}
}
std::swap(ptr, input_ptr);
return td::Slice{ptr, input_ptr};
}
void IntCtx::skipspc(bool skip_eol) {
do {
while (*input_ptr == ' ' || *input_ptr == '\t' || *input_ptr == '\r') {
++input_ptr;
}
if (!skip_eol || *input_ptr) {
break;
}
} while (load_next_line());
}
void check_compile(const IntCtx& ctx) {
if (ctx.state <= 0) {
throw IntError{"compilation mode only"};
}
}
void check_execute(const IntCtx& ctx) {
if (ctx.state != 0) {
throw IntError{"interpret mode only"};
}
}
void check_not_int_exec(const IntCtx& ctx) {
if (ctx.state < 0) {
throw IntError{"not allowed in internal interpret mode"};
}
}
void check_int_exec(const IntCtx& ctx) {
if (ctx.state >= 0) {
throw IntError{"internal interpret mode only"};
}
}
} // namespace fift

147
crypto/fift/IntCtx.h Normal file
View file

@ -0,0 +1,147 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "crypto/vm/db/TonDb.h" // FIXME
#include "crypto/vm/stack.hpp"
#include "crypto/common/bitstring.h"
#include <cstdint>
#include <cstring>
#include <iostream>
#include <string>
namespace fift {
class Dictionary;
class SourceLookup;
struct IntError {
std::string msg;
IntError(std::string _msg) : msg(_msg) {
}
};
class CharClassifier {
unsigned char data_[64];
public:
CharClassifier() {
std::memset(data_, 0, sizeof(data_));
}
CharClassifier(td::Slice str, int space_cls = 3) : CharClassifier() {
import_from_string(str, space_cls);
}
CharClassifier(std::string str, int space_cls = 3) : CharClassifier(td::Slice{str}, space_cls) {
}
CharClassifier(const char* str, int space_cls = 3) : CharClassifier(td::Slice{str}, space_cls) {
}
void import_from_string(td::Slice str, int space_cls = 3);
void import_from_string(std::string str, int space_cls = 3);
void import_from_string(const char* str, int space_cls = 3);
static CharClassifier from_string(td::Slice str, int space_cls = 3);
void set_char_class(int c, int cl);
int classify(int c) const {
c &= 0xff;
int offs = (c & 3) * 2;
return (data_[(unsigned)c >> 2] >> offs) & 3;
}
};
struct IntCtx {
vm::Stack stack;
int state{0};
int include_depth{0};
int line_no{0};
std::string filename;
std::string currentd_dir;
std::istream* input_stream{nullptr};
std::ostream* output_stream{nullptr};
std::ostream* error_stream{nullptr};
vm::TonDb* ton_db{nullptr};
Dictionary* dictionary{nullptr};
SourceLookup* source_lookup{nullptr};
private:
std::string str;
const char* input_ptr;
public:
IntCtx() = default;
operator vm::Stack&() {
return stack;
}
td::Slice scan_word_to(char delim, bool err_endl = true);
td::Slice scan_word();
td::Slice scan_word_ext(const CharClassifier& classifier);
void skipspc(bool skip_eol = false);
bool eof() const {
return !*input_stream;
}
bool not_eof() const {
return !eof();
}
void set_input(std::string input_str) {
str = input_str;
input_ptr = str.c_str();
++line_no;
}
void set_input(const char* ptr) {
input_ptr = ptr;
}
const char* get_input() const {
return input_ptr;
}
bool load_next_line();
bool is_sb() const;
void clear() {
state = 0;
stack.clear();
}
class Savepoint {
IntCtx& ctx;
int old_line_no;
std::string old_filename;
std::string old_current_dir;
std::istream* old_input_stream;
std::string old_curline;
std::ptrdiff_t old_curpos;
public:
Savepoint(IntCtx& _ctx, std::string new_filename, std::string new_current_dir, std::istream* new_input_stream);
~Savepoint();
};
};
void check_compile(const IntCtx& ctx);
void check_execute(const IntCtx& ctx);
void check_not_int_exec(const IntCtx& ctx);
void check_int_exec(const IntCtx& ctx);
td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx);
std::ostream& operator<<(std::ostream& os, const IntCtx& ctx);
} // namespace fift

View file

@ -0,0 +1,89 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "SourceLookup.h"
#include "td/utils/PathView.h"
#include "td/utils/PathView.h"
#include "td/utils/port/path.h"
#include "td/utils/filesystem.h"
#include <fstream>
namespace fift {
td::Result<FileLoader::File> OsFileLoader::read_file(td::CSlice filename) {
File res;
TRY_RESULT(data, td::read_file_str(filename));
res.data = std::move(data);
TRY_RESULT(path, td::realpath(filename));
res.path = std::move(path);
return std::move(res);
}
td::Status OsFileLoader::write_file(td::CSlice filename, td::Slice data) {
return td::write_file(filename, data);
}
td::Result<FileLoader::File> OsFileLoader::read_file_part(td::CSlice filename, td::int64 size, td::int64 offset) {
File res;
TRY_RESULT(data, td::read_file_str(filename, size, offset));
res.data = std::move(data);
TRY_RESULT(path, td::realpath(filename));
res.path = std::move(path);
return std::move(res);
}
bool OsFileLoader::is_file_exists(td::CSlice filename) {
return td::stat(filename).is_ok();
}
void SourceLookup::add_include_path(td::string path) {
if (path.empty()) {
return;
}
if (!td::PathView(path).is_dir()) {
path += TD_DIR_SLASH;
}
source_include_path_.push_back(std::move(path));
}
td::Result<FileLoader::File> SourceLookup::lookup_source(std::string filename, std::string current_dir) {
CHECK(file_loader_);
if (!current_dir.empty() && !td::PathView(current_dir).is_dir()) {
current_dir += TD_DIR_SLASH;
}
if (td::PathView(filename).is_absolute()) {
return read_file(filename);
}
if (!current_dir.empty()) {
auto res = read_file(current_dir + filename);
if (res.is_ok()) {
return res;
}
}
for (auto& dir : source_include_path_) {
auto res = read_file(dir + filename);
if (res.is_ok()) {
return res;
}
}
return td::Status::Error(PSLICE() << "failed to lookup file: " << filename);
}
} // namespace fift

View file

@ -0,0 +1,71 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include <iostream>
#include "td/utils/Status.h"
namespace fift {
class FileLoader {
public:
virtual ~FileLoader() = default;
struct File {
std::string data;
std::string path;
};
virtual td::Result<File> read_file(td::CSlice filename) = 0;
virtual td::Status write_file(td::CSlice filename, td::Slice data) = 0;
virtual td::Result<File> read_file_part(td::CSlice filename, td::int64 size, td::int64 offset) = 0;
virtual bool is_file_exists(td::CSlice filename) = 0;
};
class OsFileLoader : public FileLoader {
public:
td::Result<File> read_file(td::CSlice filename) override;
td::Status write_file(td::CSlice filename, td::Slice data) override;
td::Result<File> read_file_part(td::CSlice filename, td::int64 size, td::int64 offset) override;
bool is_file_exists(td::CSlice filename) override;
};
class SourceLookup {
public:
SourceLookup() = default;
explicit SourceLookup(std::unique_ptr<FileLoader> file_loader) : file_loader_(std::move(file_loader)) {
}
void add_include_path(td::string path);
td::Result<FileLoader::File> lookup_source(std::string filename, std::string current_dir);
td::Result<FileLoader::File> read_file(td::CSlice path) {
return file_loader_->read_file(path);
}
td::Status write_file(td::CSlice path, td::Slice data) {
return file_loader_->write_file(path, data);
}
td::Result<FileLoader::File> read_file_part(td::CSlice filename, td::int64 size, td::int64 offset) {
return file_loader_->read_file_part(filename, size, offset);
}
bool is_file_exists(td::CSlice filename) {
return file_loader_->is_file_exists(filename);
}
protected:
std::unique_ptr<FileLoader> file_loader_;
std::vector<std::string> source_include_path_;
};
} // namespace fift

221
crypto/fift/fift-main.cpp Normal file
View file

@ -0,0 +1,221 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/stack.hpp"
#include <cassert>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <fstream>
#include <memory>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <map>
#include <functional>
#include <getopt.h>
#include "Fift.h"
#include "Dictionary.h"
#include "SourceLookup.h"
#include "words.h"
#include "vm/db/TonDb.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Parser.h"
#include "td/utils/port/path.h"
void usage(const char* progname) {
std::cerr << "A simple Fift interpreter. Type `bye` to quit, or `words` to get a list of all commands\n";
std::cerr
<< "usage: " << progname
<< " [-i] [-n] [-I <source-include-path>] {-L <library-fif-file>} <source-file1-fif> <source-file2-fif> ...\n";
std::cerr << "\t-n\tDo not preload standard preamble file `Fift.fif`\n"
"\t-i\tForce interactive mode even if explicit source file names are indicated\n"
"\t-I<source-search-path>\tSets colon-separated library source include path. If not indicated, "
"$FIFTPATH is used instead.\n"
"\t-L<library-fif-file>\tPre-loads a library source file\n"
"\t-d<ton-db-path>\tUse a ton database\n"
"\t-s\tScript mode: use first argument as a fift source file and import remaining arguments as $n)\n"
"\t-v<verbosity-level>\tSet verbosity level\n";
std::exit(2);
}
void parse_include_path_set(std::string include_path_set, std::vector<std::string>& res) {
td::Parser parser(include_path_set);
while (!parser.empty()) {
auto path = parser.read_till_nofail(':');
if (!path.empty()) {
res.push_back(path.str());
}
parser.skip_nofail(':');
}
}
int main(int argc, char* const argv[]) {
bool interactive = false;
bool fift_preload = true, no_env = false;
bool script_mode = false;
std::vector<std::string> library_source_files, source_list;
std::vector<std::string> source_include_path;
std::string ton_db_path;
fift::Fift::Config config;
int i;
int new_verbosity_level = VERBOSITY_NAME(INFO);
while (!script_mode && (i = getopt(argc, argv, "hinI:L:d:sv:")) != -1) {
switch (i) {
case 'i':
interactive = true;
break;
case 'n':
fift_preload = false;
break;
case 'I':
parse_include_path_set(optarg, source_include_path);
no_env = true;
break;
case 'L':
library_source_files.emplace_back(optarg);
break;
case 'd':
ton_db_path = optarg;
break;
case 's':
script_mode = true;
break;
case 'v':
new_verbosity_level = VERBOSITY_NAME(FATAL) + td::to_integer<int>(td::Slice(optarg));
break;
case 'h':
default:
usage(argv[0]);
}
}
SET_VERBOSITY_LEVEL(new_verbosity_level);
while (optind < argc) {
source_list.emplace_back(argv[optind++]);
if (script_mode) {
break;
}
}
if (!no_env) {
const char* path = std::getenv("FIFTPATH");
if (path) {
parse_include_path_set(path ? path : "/usr/lib/fift", source_include_path);
}
}
std::string current_dir;
auto r_current_dir = td::realpath(".");
if (r_current_dir.is_ok()) {
current_dir = r_current_dir.move_as_ok();
source_include_path.push_back(current_dir);
}
config.source_lookup = fift::SourceLookup(std::make_unique<fift::OsFileLoader>());
for (auto& path : source_include_path) {
config.source_lookup.add_include_path(path);
}
if (!ton_db_path.empty()) {
auto r_ton_db = vm::TonDbImpl::open(ton_db_path);
if (r_ton_db.is_error()) {
LOG(ERROR) << "Error opening ton database: " << r_ton_db.error().to_string();
std::exit(2);
}
config.ton_db = r_ton_db.move_as_ok();
// FIXME //std::atexit([&] { config.ton_db.reset(); });
}
fift::init_words_common(config.dictionary);
fift::init_words_vm(config.dictionary);
fift::init_words_ton(config.dictionary);
if (script_mode) {
fift::import_cmdline_args(config.dictionary, source_list.empty() ? "" : source_list[0], argc - optind,
argv + optind);
}
fift::Fift fift(std::move(config));
if (fift_preload) {
auto status = fift.interpret_file("Fift.fif", "");
if (status.is_error()) {
LOG(ERROR) << "Error interpreting standard preamble file `Fift.fif`: " << status.error().message()
<< "\nCheck that correct include path is set by -I or by FIFTPATH environment variable, or disable "
"standard preamble by -n.\n";
std::exit(2);
}
}
for (auto source : library_source_files) {
auto status = fift.interpret_file(source, "");
if (status.is_error()) {
LOG(ERROR) << "Error interpreting preloaded file `" << source << "`: " << status.error().message();
std::exit(2);
}
}
if (source_list.empty()) {
interactive = true;
}
for (const auto& source : source_list) {
bool is_stdin = (source.empty() || source == "-");
auto status =
!is_stdin ? fift.interpret_file(source, current_dir) : fift.interpret_istream(std::cin, current_dir, false);
if (status.is_error()) {
if (!is_stdin) {
LOG(ERROR) << "Error interpreting file `" << source << "`: " << status.error().message();
} else {
LOG(ERROR) << "Error interpreting stdin: " << status.error().message();
}
std::exit(2);
}
auto res = status.move_as_ok();
if (res) {
std::exit(~res);
}
}
if (interactive) {
auto status = fift.interpret_istream(std::cin, current_dir);
if (status.is_error()) {
LOG(ERROR) << status.error().message();
std::exit(2);
} else {
int res = status.move_as_ok();
if (res) {
std::exit(~res);
}
}
}
}

1101
crypto/fift/lib/Asm.fif Normal file

File diff suppressed because it is too large Load diff

83
crypto/fift/lib/Fift.fif Normal file
View file

@ -0,0 +1,83 @@
{ 0 word drop 0 'nop } :: //
{ char " word 1 { swap { abort } if drop } } ::_ abort"
{ { bl word dup "" $= abort"comment extends after end of file" "*/" $= } until 0 'nop } :: /*
// { bl word 1 2 ' (create) } "::" 1 (create)
// { bl word 0 2 ' (create) } :: :
// { bl word 2 2 ' (create) } :: :_
// { bl word 3 2 ' (create) } :: ::_
// { bl word 0 (create) } : create
// { bl word (forget) } : forget
{ bl word 1 ' (forget) } :: [forget]
{ char " word 1 ' type } ::_ ."
{ swap ({) over 2+ -roll swap (compile) (}) } : does
{ 1 'nop does create } : constant
{ 2 'nop does create } : 2constant
{ hole constant } : variable
10 constant ten
{ bl word 1 { find 0= abort"word not found" } } :: (')
{ bl word find not abort"-?" 0 swap } :: [compile]
{ bl word 1 {
dup find { " -?" $+ abort } ifnot nip execute
} } :: @'
{ bl word 1 { swap 1 'nop does swap 0 (create) }
} :: =:
{ bl word 1 { -rot 2 'nop does swap 0 (create) }
} :: 2=:
{ <b swap s, b> } : s>c
{ s>c hash } : shash
// to be more efficiently re-implemented in C++ in the future
{ dup 0< ' negate if } : abs
{ 2dup > ' swap if } : minmax
{ minmax drop } : min
{ minmax nip } : max
"" constant <#
' $reverse : #>
{ swap 10 /mod char 0 + rot swap hold } : #
{ { # over 0<= } until } : #s
{ 0< { char - hold } if } : sign
// { dup abs <# #s rot sign #> nip } : (.)
// { (.) type } : ._
// { ._ space } : .
{ bl (-trailing) } : -trailing
{ char 0 (-trailing) } : -trailing0
{ char " word 1 ' $+ } ::_ +"
{ find 0<> dup ' nip if } : (def?)
{ bl word 1 ' (def?) } :: def?
{ bl word 1 { (def?) not } } :: undef?
{ def? ' skip-to-eof if } : skip-ifdef
{ bl word dup (def?) { drop skip-to-eof } { 'nop swap 0 (create) } cond } : library
{ bl word dup (def?) { 2drop skip-to-eof } { swap 1 'nop does swap 0 (create) } cond } : library-version
{ char ) word "$" swap $+ 1 { find 0= abort"undefined parameter" execute } } ::_ $(
// b s -- ?
{ sbitrefs rot brembitrefs rot >= -rot <= and } : s-fits?
{ 0 swap ! } : 0!
{ tuck @ + swap ! } : +!
{ tuck @ swap - swap ! } : -!
{ 1 swap +! } : 1+!
{ -1 swap +! } : 1-!
{ null swap ! } : null!
0 tuple constant nil
{ 1 tuple } : single
{ 2 tuple } : pair
{ 3 tuple } : triple
{ 1 untuple } : unsingle
{ 2 untuple } : unpair
{ 3 untuple } : untriple
{ over tuple? { swap count = } { 2drop false } cond } : tuple-len?
{ 0 tuple-len? } : nil?
{ 1 tuple-len? } : single?
{ 2 tuple-len? } : pair?
{ 3 tuple-len? } : triple?
{ 0 [] } : first
{ 1 [] } : second
{ 2 [] } : third
' pair : cons
' unpair : uncons
{ 0 [] } : car
{ 1 [] } : cdr
{ cdr car } : cadr
{ cdr cdr } : cddr
{ cdr cdr car } : caddr
{ null ' cons rot times } : list
{ true (atom) drop } : atom
{ bl word atom 1 'nop } ::_ `

436
crypto/fift/lib/Lisp.fif Normal file
View file

@ -0,0 +1,436 @@
library Lisp // tiny Lisp (or rather Scheme) interpreter
"Lists.fif" include
variable lisp-dict
{ hole dup 1 { @ execute } does create } : recursive
{ atom>$ +" undefined" abort } : report-not-found
// a l -- d -1 or a 0 Look up definition d of atom a in dictionary l
{ { dup null? { drop false true }
{ uncons -rot unpair -rot over eq?
{ drop nip true true } { nip swap false } cond
} cond
} until
} : lookup-in
// a dict -- def
{ lookup-in ' report-not-found ifnot } : lookup-or-fail
{ lisp-dict @ lookup-or-fail } : lisp-dict-lookup
// a d -- Defines a with definition d in dictionary lisp-dict
{ pair lisp-dict @ cons lisp-dict ! } : lisp-dict-int-define
{ box lisp-dict-int-define } : lisp-dict-define
// a d -- Defines new a with defininition d
{ over lisp-dict @ lookup-in { 2drop atom>$ +" already defined" abort }
{ drop lisp-dict-int-define } cond
} : lisp-dict-int-define-new
{ box lisp-dict-int-define-new } : lisp-dict-define-new
// a e -- Defines a with executable definition given by e
{ single lisp-dict-define-new } : lisp-dict-define-exec
// expr ctx def -- val
{ dup first execute } : run-definition
// expr ctx -- val
recursive lisp-ctx-eval {
over tuple?
{ over first over lisp-ctx-eval run-definition }
{ over atom? { lookup-or-fail @ } { drop } cond }
cond
} swap !
// exp -- value
{ lisp-dict @ lisp-ctx-eval } : lisp-eval
// (exprs) ctx -- (vals)
recursive lisp-ctx-eval-list
{ over null? { drop } {
swap uncons -rot over lisp-ctx-eval -rot lisp-ctx-eval-list cons
} cond
} swap !
// (exprs) ctx -- val
{ null rot {
dup null? { drop nip true } {
nip uncons swap 2 pick lisp-ctx-eval swap false
} cond } until
} : lisp-ctx-eval-list-last
// l c -- (args)
{ swap uncons nip swap lisp-ctx-eval-list } : extract-eval-arg-list
{ drop uncons nip } : extract-arg-list
// (x1 .. xn) e n -- x1 .. xn e
{ { swap uncons rot } swap times
swap null? not abort"invalid number of arguments"
} : unpack-list
// l c n e -- v
{ swap 2swap extract-eval-arg-list // e n (args)
-rot unpack-list execute
} : eval-exec-fixed
// l c n e -- v
{ 2 pick pair
swap 2swap extract-arg-list // [e c] n (args)
-rot unpack-list unpair swap execute
} : exec-fixed
// l c e -- v
{ -rot extract-eval-arg-list // e (args)
swap execute
} : eval-exec-list
{ -rot tuck extract-arg-list // e c (args)
swap rot execute
} : exec-list
// e a n --
{ rot 2 { // expr ctx def n e
rot drop eval-exec-fixed } does
lisp-dict-define-exec
} : lisp-fixed-primitive
{ rot 2 { rot drop exec-fixed } does lisp-dict-define-exec
} : lisp-fixed-lazy-primitive
// e a --
{ swap 1 { nip eval-exec-list } does lisp-dict-define-exec
} : lisp-primitive
{ swap 1 { nip exec-list } does lisp-dict-define-exec
} : lisp-lazy-primitive
// Uncomment next line for Fift booleans
// false constant #f true constant #t null constant no-answer
// Uncomment next line for Scheme booleans
`#f constant #f `#t constant #t #f constant no-answer
{ #f eq? } : lisp-false?
{ lisp-false? 0= } : lisp-true?
{ ' #t ' #f cond } : lisp-bool
// temp for defining a lot of primitives
{ bl word atom lisp-primitive } : L:
{ bl word atom swap lisp-dict-define } : L=:
{ bl word atom swap lisp-fixed-primitive } : #L:
{ 0 #L: } : 0L:
{ 1 #L: } : 1L:
{ 2 #L: } : 2L:
// basic primitives
{ sum-list } L: +
{ - } 2L: -
{ dup null? { drop 1 } { ' * foldl-ne } cond } L: *
{ / } 2L: /
{ mod } 2L: modulo
{ abs } 1L: abs
{ ' min foldl-ne } L: min
{ ' max foldl-ne } L: max
{ true ' and foldl } L: integer-and
{ false ' or foldl } L: integer-or
{ 0 ' xor foldl } L: integer-xor
{ not } 1L: integer-not
{ = lisp-bool } 2L: =
{ <> lisp-bool } 2L: <>
{ < lisp-bool } 2L: <
{ <= lisp-bool } 2L: <=
{ > lisp-bool } 2L: >
{ >= lisp-bool } 2L: >=
{ eq? lisp-bool } 2L: eq?
{ eqv? lisp-bool } 2L: eqv?
{ equal? lisp-bool } 2L: equal?
{ cons } 2L: cons
{ car } 1L: car
{ cdr } 1L: cdr
{ cadr } 1L: cadr
{ cddr } 1L: cddr
{ caddr } 1L: caddr
{ cdr cddr } 1L: cdddr
{ concat-list-lists } L: append
{ list-reverse } 1L: reverse
{ list-tail } 2L: list-tail
{ list-ref } 2L: list-ref
{ list-member-eq } 2L: memq
{ list-member-eqv } 2L: memv
{ list-member-equal } 2L: member
{ assq ' #f ifnot } 2L: assq
{ assv ' #f ifnot } 2L: assv
{ assoc ' #f ifnot } 2L: assoc
{ list? lisp-bool } 1L: list?
{ pair? lisp-bool } 1L: pair?
{ tuple? lisp-bool } 1L: tuple?
{ string? lisp-bool } 1L: string?
{ integer? lisp-bool } 1L: integer?
{ integer? lisp-bool } 1L: number?
{ count } 1L: width
{ list-length } 1L: length
{ [] } 2L: tuple-ref
{ first } 1L: first
{ second } 1L: second
{ third } 1L: third
{ 3 [] } 1L: fourth
{ list>tuple } 1L: list->tuple
{ explode list } 1L: tuple->list
null L=: null
{ atom? lisp-bool } 1L: symbol?
{ atom } 1L: string->symbol
{ atom>$ } 1L: symbol->string
{ dup #f eq? swap #t eq? or lisp-bool } 1L: boolean?
#t L=: else
#f L=: #f
#t L=: #t
{ null? lisp-bool } 1L: null?
{ 0= lisp-bool } 1L: zero?
{ 0> lisp-bool } 1L: positive?
{ 0< lisp-bool } 1L: negative?
{ 1 and 0= lisp-bool } 1L: even?
{ 1 and 0<> lisp-bool } 1L: odd?
{ bye } 0L: exit
{ .l null } 1L: write
{ lisp-eval } 1L: eval
{ drop } `quote 1 lisp-fixed-lazy-primitive
'nop L: list
{ list>tuple } L: tuple
{ list-last } L: begin
{ $len } 1L: string-length
{ concat-string-list } L: string-append
{ $= lisp-bool } 2L: string=?
{ $cmp 0< lisp-bool } 2L: string<?
{ $cmp 0<= lisp-bool } 2L: string<=?
{ $cmp 0> lisp-bool } 2L: string>?
{ $cmp 0>= lisp-bool } 2L: string>=?
{ (number) dup 1 = { drop } { ' 2drop if no-answer } cond
} 1L: string->number
{ (.) } 1L: number->string
{ box? lisp-bool } 1L: box?
{ box } 1L: box
{ hole } 0L: new-box
{ @ } 1L: unbox
{ tuck swap ! } 2L: set-box!
{ abort } 1L: error
{ dup find { nip execute } { +" -?" abort } cond } : find-execute
{ explode-list 1- roll find-execute } L: fift-exec
{ explode-list dup 1- swap roll find-execute } L: fift-exec-cnt
{ uncons swap find-execute } L: fift-exec-list
// end of basic primitives
forget L: forget #L: forget L=:
forget 0L: forget 1L: forget 2L:
{ { dup tuple? ' do-quote if } list-map } : map-quote
{ uncons ' cons foldr-ne map-quote
null swap cons lisp-dict @ rot run-definition
} `apply lisp-primitive // bad: should have preserved original context
// e1 e2 e3 ctx
{ 3 exch 3 pick lisp-ctx-eval lisp-true? ' swap if nip swap lisp-ctx-eval }
`if 3 lisp-fixed-lazy-primitive
// (e) ctx
{ #t -rot
{ over null? { 2drop true } {
swap uncons swap 2 pick lisp-ctx-eval dup lisp-true? // v' c t v ?
{ swap 2swap nip false } { -rot 2drop nip true } cond
} cond } until
} `and lisp-lazy-primitive
{ #f -rot
{ over null? { 2drop true } {
swap uncons swap 2 pick lisp-ctx-eval dup lisp-false? // v' c t v ?
{ swap 2swap nip false } { -rot 2drop nip true } cond
} cond } until
} `or lisp-lazy-primitive
{ lisp-false? lisp-bool } `not 1 lisp-fixed-primitive
// cond-clause ctx -- v -1 or 0
{ swap uncons -rot dup `else eq? {
drop lisp-ctx-eval-list-last true } {
over lisp-ctx-eval lisp-true? {
lisp-ctx-eval-list-last true } {
2drop false
} cond } cond
} : eval-cond-clause
// (clauses) ctx -- v
{ { over null? { no-answer true } {
swap uncons -rot over eval-cond-clause } cond
} until -rot 2drop
} `cond lisp-lazy-primitive
{ lisp-dict @ lookup-in { hole tuck lisp-dict-int-define } ifnot
} : lisp-create-global-var
// a e ctx -- old (simple) define
{ drop over atom? not abort"only a variable can be define'd"
over lisp-create-global-var swap lisp-eval swap !
} drop // `define 2 lisp-fixed-lazy-primitive
{ tuck lisp-ctx-eval rot dup atom? not abort"only a variable can be set"
rot lookup-or-fail dup @ -rot !
} `set! 2 lisp-fixed-lazy-primitive
// define lambda
{ { dup null? { drop true true }
{ uncons swap atom? { false } { drop false true } cond } cond
} until
} : var-list?
{ { dup null? over atom? or { drop true true }
{ uncons swap atom? { false } { drop false true } cond } cond
} until
} : lambda-var-list?
// (quote x) -- x -1 ; else 0
{ dup pair? { uncons swap `quote eq? { car true } { drop false } cond }
{ drop false } cond
} : is-quote?
recursive match-arg-list-acc
// l (vars) (args) -- ((var . arg) ...)+l -1 or ? 0
{ over atom? { over `_ eq? { 2drop } { pair swap cons } cond true } {
over null? { nip null? } { // (vars) (args)
over tuple? not { 2drop false } {
over is-quote? { eq? nip } { // (v) (a)
dup tuple? not { 2drop false } {
over count over count over <> { drop 2drop false } { // l [v] [a] n
3 roll 0 rot { // [v] [a] l i
dup 0< {
3 pick over [] swap // [v] [a] l vi i
3 pick over [] 2swap rot // [v] [a] i l vi ai
match-arg-list-acc { // [v] [a] i l'
swap 1+ } { nip -1 } cond
} ifnot
} swap times
2swap 2drop 0>=
} cond } cond } cond } cond } cond } cond
} swap !
{ null -rot match-arg-list-acc } : match-arg-list
// ((var . arg)...) ctx -- ctx'
{ { over null? not }
{ swap uncons swap unpair box pair rot cons } while
nip
} : extend-ctx-by-list
// ((vars) body) ctx
{ swap uncons -rot
dup lambda-var-list? not abort"invalid formal parameter list"
{ // l-expr ctx' [_ body ctx (vars)]
-rot 2 pick 3 [] swap rot // [_ body ...] (vars) ctx' l-expr
uncons nip swap lisp-ctx-eval-list // [_ body ...] (vars) (arg-vals)
match-arg-list not abort"invalid arguments to lambda" // [_ body ...] ((var arg)...)
over third extend-ctx-by-list // [_ body ctx (vars)] ctx''
swap second swap lisp-ctx-eval-list-last
} 3 -roll 4 tuple
} : make-lambda
{ make-lambda } `lambda lisp-lazy-primitive
// (a e) ctx -- more sophisticated (define a e)
{ drop uncons swap dup atom? { // (e) a
tuck lisp-create-global-var
swap lisp-dict @ lisp-ctx-eval-list-last swap !
} { // (e) (a v..)
uncons over atom? not abort"only variables can be define'd" // (e) a (v..)
rot cons over lisp-create-global-var // a ((v..) (e)) h
swap lisp-dict @ make-lambda swap !
} cond
} `define lisp-lazy-primitive
// ((x e) ..) ctx -- ((x.v) ..)
recursive eval-assign-list
{ over null? { drop } {
swap uncons swap uncons // ctx t x (e)
over atom? not abort"invalid variable name in assignment list"
3 pick lisp-ctx-eval-list-last // ctx t x v
pair swap rot eval-assign-list cons
} cond
} swap !
// (((x v) ..) body) ctx -- let construct
{ swap uncons swap 2 pick eval-assign-list // ctx body ((x v)...)
rot extend-ctx-by-list lisp-ctx-eval-list-last
} `let lisp-lazy-primitive
// ((x e) ..) ctx -- ctx'
{ swap {
dup null? { drop true } {
uncons swap uncons // ctx t x (e)
over atom? not abort"invalid variable name in assignment list"
3 pick lisp-ctx-eval-list-last // ctx t x v
box pair rot cons swap false
} cond } until
} : compute-let*-ctx
// (((x v) ..) body) ctx -- let* construct
{ swap uncons swap rot compute-let*-ctx lisp-ctx-eval-list-last
} `let* lisp-lazy-primitive
// ((x e) ..) ctx -- ((h e) ..) ctx' , with x bound to h in ctx'
recursive prepare-letrec-ctx {
over null? {
swap uncons swap uncons swap // ctx t (e) x
hole tuck pair swap rot cons // ctx t (x.h) (h e)
3 -roll rot cons prepare-letrec-ctx // (h e) t ctx'
-rot cons swap
} ifnot
} swap !
// (((x v) ..) body) ctx -- letrec construct
{ swap uncons swap rot prepare-letrec-ctx swap { // body ctx' ((h e)..)
dup null? { drop true } {
uncons -rot uncons 2 pick lisp-ctx-eval-list-last // body t ctx' h v
swap ! swap false
} cond } until
lisp-ctx-eval-list-last
} `letrec lisp-lazy-primitive
// (e (p e)...) ctx -- match construct
{ swap uncons swap 2 pick lisp-ctx-eval swap { // ctx v ((p e)..)
dup null? { drop 2drop no-answer true } {
uncons swap uncons swap 3 pick // ctx v t e p v
match-arg-list { // ctx v t e ((x' . v')...)
2swap 2drop rot extend-ctx-by-list lisp-ctx-eval-list-last true } {
2drop false
} cond } cond } until
} `match lisp-lazy-primitive
//
lisp-dict @ constant original-lisp-dict
{ original-lisp-dict lisp-dict ! } : reset-lisp
{ ' drop { lisp-eval .l cr } List-generic( } :_ LISP-EVAL-PRINT(
// LISP-EVAL-PRINT((+ 3 4) (* 5 6)) computes and prints 12 and 30
{ hole dup 1 { @ nip } does swap
1 { swap lisp-eval swap ! } does
List-generic(
} :_ LISP-EVAL(
// LISP-EVAL((+ 3 4) (* 5 6)) computes 12 and 30, returns only 30
// /*
LISP-EVAL-PRINT(
(define succ (lambda (x) (+ x 1)))
(define (twice f) (lambda (x) (f (f x))))
(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))
(fact ((twice succ) 5))
(define compare (lambda (x y) (cond ((< x y) 'less) ((= x y) 'equal) (else 'greater))))
(compare 2 3)
(compare 7 (+ 2 3))
(define next (let ((cnt 0)) (lambda () (set! cnt (+ cnt 1)) cnt)))
(list (next) (next))
(define new-counter (lambda () (let ((x 0)) (lambda () (set! x (+ x 1)) x))))
(define c1 (new-counter))
(define c2 (new-counter))
(list (c1) (c1) (c2) (c1) (c2) (c1) (c1) (c2) (c2))
(let* ((x (+ 2 3)) (y (* x x)) (z (+ x y))) (list x y z))
(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1)))))
(odd? (lambda (n) (if (= n 0) #f (even? (- n 1))))))
(even? 88))
(define (len l) (if (null? l) 0 (+ 1 (len (cdr l)))))
(len '(2 3 9))
(define (len2 l) (match l (() 0) ((x . t) (+ 1 (len2 t)))))
(len2 '(2 3 9))
(define (foo x) (match x
(('zero) 0)
(('succ x) (+ (foo x) 1))
(('plus x y) (+ (foo x) (foo y)))
(('minus x y) (- (foo x) (foo y)))
(x x)))
(foo '(plus (succ (zero)) (minus (succ (succ 5)) 3)))
(define (bar x) (match x
(['zero] 0)
(['succ x] (+ (bar x) 1))
(['plus x y] (+ (bar x) (bar y)))
(['minus x y] (- (bar x) (bar y)))
(['const x] x)))
(bar '[plus [succ [zero]] [minus [succ [succ [const 5]]] [const 3]]])
(define (map f l) (letrec
((map-f (lambda (l) (match l
(() ())
((h . t) (cons (f h) (map-f t)))))))
(map-f l)))
(map (lambda (x) (* x (+ 2 x))) '(2 3 9))
(define (make-promise proc) (let ((result-ready? #f) (result #f))
(lambda ()
(if result-ready? result
(let ((x (proc)))
(if result-ready? result
(begin (set! result x) (set! result-ready? #t) result)))))))
(define (force promise) (promise))
)
// */
// words for invoking Lisp definitions from Fift
// (args) def -- val
{ null rot map-quote cons lisp-dict @ rot run-definition
} : invoke-lisp-definition
{ atom lisp-dict-lookup 1 { @ invoke-lisp-definition }
} : (invoke-lisp)
{ bl word (invoke-lisp) } :: invoke-lisp
// ( 2 3 ) invoke-lisp compare .l
{ atom lisp-dict-lookup 2 { @ mklist-1 invoke-lisp-definition }
} : (invoke-lisp-fixed)
{ bl word (invoke-lisp-fixed) } :: invoke-lisp-fixed
// 9 8 2 invoke-lisp-fixed compare .l
{ bl word (invoke-lisp) does } : make-lisp-invoker
{ bl word (invoke-lisp-fixed) does } : make-lisp-fixed-invoker
// 2 make-lisp-fixed-invoker compare : compare
// 3 9 compare
// import Lisp definitions as Fift words
{ bl word dup (invoke-lisp) does swap 0 (create) } : import-lisp
{ bl word tuck (invoke-lisp-fixed) does swap 0 (create) } : import-lisp-fixed
// 1 import-lisp-fixed fact
// 7 fact .

184
crypto/fift/lib/Lists.fif Normal file
View file

@ -0,0 +1,184 @@
library Lists // List utilities
//
{ hole dup 1 { @ execute } does create } : recursive
// x x' -- ? recursively compares two S-expressions
recursive equal? {
dup tuple? {
over tuple? {
over count over count over = { // t t' l ?
0 { dup 0>= { 2dup [] 3 pick 2 pick [] equal? { 1+ } { drop -1 } cond
} if } rot times
nip nip 0>=
} { drop 2drop false } cond
} { 2drop false } cond
} { eqv? } cond
} swap !
// (a1 .. an) -- (an .. a1)
{ null swap { dup null? not } { uncons swap rot cons swap } while drop } : list-reverse
// (a1 .. an) -- an Computes last element of non-empty list l
{ { uncons dup null? { drop true } { nip false } cond } until } : list-last
// l l' -- l++l' Concatenates two lists
recursive list+ {
over null? { nip } { swap uncons rot list+ cons } cond
} swap !
// l l' -- l'' -1 or 0, where l = l' ++ l''
// Removes prefix from list
{ { dup null? { drop true true } {
swap dup null? { 2drop false true } { // l' l
uncons swap rot uncons -rot equal? { false } {
2drop false true
} cond } cond } cond } until
} : list-
// (a1 .. an) -- a1 .. an n Explodes a list
{ 0 { over null? not } { swap uncons rot 1+ } while nip } : explode-list
// (a1 .. an) x -- a1 .. an n x Explodes a list under the topmost element
{ swap explode-list dup 1+ roll } : explode-list-1
// l -- t Transforms a list into a tuple with the same elements
{ explode-list tuple } : list>tuple
// a1 ... an n x -- (a1 .. an) x
{ null swap rot { -rot cons swap } swap times } : mklist-1
// (s1 ... sn) -- s1+...+sn Concatenates a list of strings
{ "" { over null? not } { swap uncons -rot $+ } while nip
} : concat-string-list
// (x1 ... xn) -- x1+...+xn Sums a list of integers
{ 0 { over null? not } { swap uncons -rot + } while nip
} : sum-list
// (a1 ... an) a e -- e(...e(e(a,a1),a2),...),an)
{ -rot { over null? not } { swap uncons -rot 3 pick execute } while nip nip
} : foldl
// (a1 ... an) e -- e(...e(e(a1,a2),a3),...),an)
{ swap uncons swap rot foldl } : foldl-ne
// (a1 ... an) a e -- e(a1,e(a2,...,e(an,a)...))
recursive foldr {
rot dup null? { 2drop } {
uncons -rot 2swap swap 3 pick foldr rot execute
} cond
} swap !
// (a1 ... an) e -- e(a1,e(a2,...,e(a[n-1],an)...))
recursive foldr-ne {
over cdr null? { drop car } {
swap uncons 2 pick foldr-ne rot execute
} cond
} swap !
// (l1 ... ln) -- l1++...++ln Concatenates a list of lists
{ dup null? { ' list+ foldr-ne } ifnot } : concat-list-lists
// (a1 .. an . t) n -- t Computes the n-th tail of a list
{ ' cdr swap times } : list-tail
// (a0 .. an ..) n -- an Computes the n-th element of a list
{ list-tail car } : list-ref
// l -- ?
{ { dup null? { drop true true } {
dup pair? { cdr false } {
drop false true
} cond } cond } until
} : list?
// l -- n
{ 0 { over null? not } { 1+ swap uncons nip swap } while nip
} : list-length
// l e -- t // returns tail of l after first member that satisfies e
{ swap {
dup null? { nip true } {
tuck car over execute { drop true } {
swap cdr false
} cond } cond } until
} : list-tail-from
// a l -- t // tail of l after first occurence of a using eq?
{ swap 1 ' eq? does list-tail-from } : list-member-eq
{ swap 1 ' eqv? does list-tail-from } : list-member-eqv
{ swap 1 ' equal? does list-tail-from } : list-member-equal
// a l -- ?
{ list-member-eq null? not } : list-member?
{ list-member-eqv null? not } : list-member-eqv?
// l -- a -1 or 0 // returns car l if l is non-empty
{ dup null? { drop false } { car true } cond
} : safe-car
{ dup null? { drop false } { car second true } cond
} : get-first-value
// l e -- v -1 or 0
{ list-tail-from safe-car } : assoc-gen
{ list-tail-from get-first-value } : assoc-gen-x
// a l -- (a.v) -1 or 0 -- returns first entry (a . v) in l
{ swap 1 { swap first eq? } does assoc-gen } : assq
{ swap 1 { swap first eqv? } does assoc-gen } : assv
{ swap 1 { swap first equal? } does assoc-gen } : assoc
// a l -- v -1 or 0 -- returns v from first entry (a . v) in l
{ swap 1 { swap first eq? } does assoc-gen-x } : assq-val
{ swap 1 { swap first eqv? } does assoc-gen-x } : assv-val
{ swap 1 { swap first equal? } does assoc-gen-x } : assoc-val
// (a1 .. an) e -- (e(a1) .. e(an))
recursive list-map {
over null? { drop } {
swap uncons -rot over execute -rot list-map cons
} cond
} swap !
//
// create Lisp-style lists using words "(" and ")"
//
variable ')
'nop box constant ',
{ ") without (" abort } ') !
{ ') @ execute } : )
anon constant dot-marker
// m x1 ... xn t m -- (x1 ... xn . t)
{ swap
{ -rot 2dup eq? not }
{ over dot-marker eq? abort"invalid dotted list"
swap rot cons } while 2drop
} : list-tail-until-marker
// m x1 ... xn m -- (x1 ... xn)
{ null swap list-tail-until-marker } : list-until-marker
{ over dot-marker eq? { nip 2dup eq? abort"invalid dotted list" }
{ null swap } cond
list-tail-until-marker
} : list-until-marker-ext
{ ') @ ', @ } : ops-get
{ ', ! ') ! } : ops-set
{ anon dup ops-get 3 { ops-set list-until-marker-ext } does ') ! 'nop ', !
} : (
// test of Lisp-style lists
// ( 42 ( `+ 9 ( `* 3 4 ) ) "test" ) .l cr
// ( `eq? ( `* 3 4 ) 3 4 * ) .l cr
// `alpha ( `beta `gamma `delta ) cons .l cr
// { ( `eq? ( `* 3 5 pick ) 3 4 roll * ) } : 3*sample
// 17 3*sample .l cr
// similar syntax _( x1 .. xn ) for tuples
{ 2 { 1+ 2dup pick eq? } until 3 - nip } : count-to-marker
{ count-to-marker tuple nip } : tuple-until-marker
{ anon dup ops-get 3 { ops-set tuple-until-marker } does ') ! 'nop ', ! } : _(
// test of tuples
// _( _( 2 "two" ) _( 3 "three" ) _( 4 "four" ) ) .dump cr
// pseudo-Lisp tokenizer
"()[]'" 34 hold constant lisp-delims
{ lisp-delims 11 (word) } : lisp-token
{ null cons `quote swap cons } : do-quote
{ 1 { ', @ 2 { 2 { ', ! execute ', @ execute } does ', ! }
does ', ! } does
} : postpone-prefix
{ ', @ 1 { ', ! } does ', ! } : postpone-',
( `( ' ( pair
`) ' ) pair
`[ ' _( pair
`] ' ) pair
`' ' do-quote postpone-prefix pair
`. ' dot-marker postpone-prefix pair
`" { char " word } pair
`;; { 0 word drop postpone-', } pair
) constant lisp-token-dict
variable eol
{ eol @ eol 0! anon dup ') @ 'nop 3
{ ops-set list-until-marker-ext true eol ! } does ') ! rot ', !
{ lisp-token dup (number) dup { roll drop } {
drop atom dup lisp-token-dict assq { nip second execute } if
} cond
', @ execute
eol @
} until
-rot eol ! execute
} :_ List-generic(
{ 'nop 'nop List-generic( } :_ LIST(
// LIST((lambda (x) (+ x 1)) (* 3 4))
// LIST('(+ 3 4))
// LIST(2 3 "test" . 9)
// LIST((process '[plus 3 4]))

266
crypto/fift/lib/Stack.fif Normal file
View file

@ -0,0 +1,266 @@
library Stack // advanced stack manupulation library
"Lists.fif" include
// S(a b c - a c 2 a b) would compile to code performing the requested stack manipulation
// interface to low-level stack manipulation primitives
{ (number) 1- abort"index expected" dup 0 < over 255 > or
abort"index 0..255 expected"
} : (idx)
// push(n) : a0 .. an - a0 .. an a0 equivalent to "n pick"
// push(0) = dup, push(1) = over
{ 0 char ) word (idx) <push> } ::_ push(
// pop(n) : a0 a1 .. a(n-1) an - an a1 .. a(n-1)
// pop(0) = drop, pop(1) = nip
{ 0 char ) word (idx) <pop> } ::_ pop(
// xchg(i,j) : equivalent to "i j exch2"
{ 0 char , word (idx) char ) word (idx) <xchg> } ::_ xchg(
// xchg0(i) : equivalent to "i exch" or "xchg(0,i)"
// xchg0(1) = swap
{ 0 char ) word (idx) 0 <xchg> } ::_ xchg0(
forget (idx)
// parser for stack notation expressions
")" 34 hold +" -" constant stk-delims
anon constant stk-start
anon constant stk-to
variable stk-mode
{ stk-delims 11 (word) } : stk-token
'nop : mk-lit
// stk-start vn ... v0 -- stk-start ... v0 i where v[i]=v0
{ 0 {
1+ 2dup 2+ pick dup stk-start eq? { 2drop drop 0 true } { eqv? } cond
} until
} : stk-lookup
// stk-start a1 .. an stk-to b1 .. bm -- [a1 .. an] [b1 .. bm]
{ stk-mode @ 0= abort"identifier expected" } : chk-lit
{ stk-to list-until-marker stk-mode !
stk-start list-until-marker stk-mode @
stk-mode 0!
} : build-stk-effect
{ stk-start stk-mode 0! {
stk-token dup ")" $= { drop true } {
dup "-" $= {
drop stk-mode @ abort"duplicate -" true stk-mode ! stk-to false } {
dup 34 chr $= { chk-lit drop char " word mk-lit false } {
dup (number) ?dup { chk-lit 1- { swap mk-lit -rot } if mk-lit nip false } {
atom dup `_ eq? { stk-mode @ abort"identifier expected" false } {
stk-lookup 0= stk-mode @ = {
stk-mode @ { atom>$ +" -?" } { atom>$ +" redefined" } cond abort } {
false
} cond } cond } cond } cond } cond } cond } until
stk-mode @ 0= abort"'-' expected"
build-stk-effect
} :_ parse-stk-list(
// stack operation list construction
variable op-rlist
{ op-rlist null! } : clear-op-list
{ op-rlist @ list-reverse } : get-op-list
{ op-rlist @ cons op-rlist ! } : issue-op
{ minmax `xchg -rot triple } : op-xchg
{ `push swap pair } : op-push
{ `lit swap pair } : op-lit
{ `pop swap pair } : op-pop
0 op-pop constant op-drop
{ 2dup <> { op-xchg issue-op } if } : issue-xchg
{ op-push issue-op } : issue-push
{ op-lit issue-op } : issue-lit
{ op-pop issue-op } : issue-pop
{ op-drop issue-op } : issue-drop
{ ' issue-drop swap times } : issue-drop-#
// emulated stack contents
variable emul-stk
{ emul-stk @ count } : emul-depth
{ emul-depth 1- swap - } : adj-i
{ emul-depth 1- tuck swap - swap rot - swap } : adj-ij
// i j --
{ adj-ij 2dup emul-stk @ tuck swap [] swap rot [] rot // i sj si j
emul-stk @ -rot []= swap rot []= emul-stk !
} : emul-xchg
{ emul-stk @ tpop drop emul-stk ! } : emul-drop
// i --
{ 0 emul-xchg emul-drop } : emul-pop
// i -- s[i]
{ emul-stk @ swap [] } : emul-stk[]
// i -- si
{ adj-i emul-stk[] } : emul-get
{ 0 emul-get } : emul-tos
// v i -- ? Check whether s[i]=v
{ dup emul-depth < { emul-stk[] eqv? } { 2drop false } cond } : emul[]-eq?
// v -- i or -1 Returns maximum i with s[i]=v
{ emul-stk @ dup count { // v s i
?dup 0= { -1 true } { 1- 2dup [] 3 pick eqv? } cond // v s i' ?
} until nip nip
} : emul-stk-lookup-rev
// i --
{ emul-get emul-stk @ swap , emul-stk ! } : emul-push
{ emul-stk @ swap , emul-stk ! } : emul-lit
// show emulated stack contents similarly to .s
{ emul-stk @ explode dup 1 reverse ' .l swap times cr } : .e
// both issue an operation and emulate it
{ 2dup issue-xchg emul-xchg } : issue-emul-xchg
{ dup issue-push emul-push } : issue-emul-push
{ dup issue-lit emul-lit } : issue-emul-lit
{ dup issue-pop emul-pop } : issue-emul-pop
{ issue-drop emul-drop } : issue-emul-drop
{ ' issue-emul-drop swap times } : issue-emul-drop-#
// b.. s -- b.. s moves tos value to stk[s]
{ dup emul-stk[] 2 pick cdr list-member-eqv? {
dup adj-i 0 issue-emul-xchg } { dup adj-i issue-emul-pop } cond
} : move-tos-to
// new s -- ops registered
{ { over null? not } {
// .sl .e get-op-list .l cr
// get-op-list list-length 100 > abort"too long"
emul-depth over >
{ over emul-tos swap list-member-eqv? not } { false } cond {
// b.. s tos unneeded
issue-emul-drop } {
over car // b.. s b1
2dup swap emul[]-eq? { drop swap cdr swap 1+ } {
dup emul-stk-lookup-rev // b.. s b1 i
dup 0< { // b.. s b1 i not found, must be a literal
drop dup atom? abort"unavailable value"
issue-emul-lit } {
dup 3 pick < { // b.. s b1 i found in bottom s stack values
nip adj-i issue-emul-push // b.. s
dup emul-depth 1- < { move-tos-to } if
} {
emul-depth 1- over = { // b.. s b1 i found in tos
2drop move-tos-to
} { // b.. s b1 i
nip over adj-ij issue-emul-xchg
} cond } cond } cond } cond } cond } while
nip emul-depth swap - issue-emul-drop-#
} : generate-reorder-ops
// old new -- op-list
{ emul-stk @ op-rlist @ 2swap
swap list>tuple emul-stk ! clear-op-list
0 generate-reorder-ops get-op-list
-rot op-rlist ! emul-stk !
} : generate-reorder
{ parse-stk-list( generate-reorder } :_ SG(
// op-list rewriting according to a ruleset
// l f l1 l2 -- l' -1 or l f with l' = l2 + (l - l1)
{ push(3) rot list- { list+ nip nip true } { drop } cond
} : try-rule
// l f ll -- l' -1 or l f
{ { dup null? not } { uncons 3 -roll unpair try-rule rot } while drop
} : try-ruleset
// l ll -- l'
{ swap { over false swap try-ruleset 0= } until nip
} : try-ruleset*
// l ruleset -- l'
recursive try-ruleset*-everywhere {
tuck try-ruleset* dup null? { nip } {
uncons rot try-ruleset*-everywhere cons } cond
} swap !
LIST(
[([xchg 0 1] [xchg 0 2]) ([rot])]
[([xchg 0 1] [xchg 1 2]) ([-rot])]
[([xchg 0 2] [xchg 1 2]) ([rot])]
[([xchg 0 2] [xchg 0 1]) ([-rot])]
[([xchg 1 2] [xchg 0 1]) ([rot])]
[([xchg 1 2] [xchg 0 2]) ([-rot])]
[([xchg 0 1] [rot]) ([xchg 0 2])]
[([-rot] [xchg 0 1]) ([xchg 0 2])]
[([xchg 0 2] [xchg 1 3]) ([2swap])]
[([xchg 1 3] [xchg 0 2]) ([2swap])]
[([push 1] [push 1]) ([2dup])]
[([push 3] [push 3]) ([2over])]
[([pop 0] [pop 0]) ([2drop])]
[([pop 1] [pop 0]) ([2drop])]
[([xchg 0 1] [push 1]) ([tuck])]
[([rot] [-rot]) ()]
[([-rot] [rot]) ()]
) constant fift-stack-ruleset
{ fift-stack-ruleset try-ruleset*-everywhere } : fift-ops-rewrite
{ SG( fift-ops-rewrite } :_ SGF(
// helpers for creating Fift source strings for one fift-op
// i j -- s
{ minmax over { "xchg(" rot (.) $+ +"," swap (.) $+ +")" }
{ nip dup 1 = { drop "swap" } {
?dup { "xchg0(" swap (.) $+ +")" } { "" } cond
} cond } cond
} : source-<xchg>
// i -- s
{ dup 1 = { drop "over" } {
?dup { "push(" swap (.) $+ +")" } { "dup" } cond
} cond
} : source-<push>
// i -- s
{ dup 1 = { drop "nip" } {
?dup { "pop(" swap (.) $+ +")" } { "drop" } cond
} cond
} : source-<pop>
// lit -- s
{ dup string? { char " chr swap $+ char " hold } { (.) } cond
} : source-<lit>
// dictionary with all fift op compilation/source creation
{ 0 swap (compile) } : fop-compile
( _( `xchg 2 { <xchg> fop-compile } { source-<xchg> swap cons } )
_( `push 1 { <push> fop-compile } { source-<push> swap cons } )
_( `pop 1 { <pop> fop-compile } { source-<pop> swap cons } )
_( `lit 1 { 1 'nop (compile) } { source-<lit> swap cons } )
_( `rot 0 { ' rot fop-compile } { "rot" swap cons } )
_( `-rot 0 { ' -rot fop-compile } { "-rot" swap cons } )
_( `tuck 0 { ' tuck fop-compile } { "tuck" swap cons } )
_( `2swap 0 { ' 2swap fop-compile } { "2swap" swap cons } )
_( `2drop 0 { ' 2drop fop-compile } { "2drop" swap cons } )
_( `2dup 0 { ' 2dup fop-compile } { "2dup" swap cons } )
_( `2over 0 { ' 2over fop-compile } { "2over" swap cons } )
) box constant fift-op-dict
{ dup atom? { atom>$ } { drop "" } cond
"unknown operation " swap $+ abort
} : report-unknown-op
variable 'fop-entry-exec
// process fift-op according to 'fop-entry-exec
// ... op - ...
{ dup first dup fift-op-dict @ assq { report-unknown-op } ifnot
dup second 1+ push(3) count <> abort"incorrect param count"
nip swap explode dup roll drop 1- roll // o2 .. on entry
'fop-entry-exec @ execute
} : process-fift-op
// compile op-list into Fift wordlist
// wl op-list -- wl'
{ { third execute } 'fop-entry-exec !
swap ' process-fift-op foldl } : compile-fift-op*
// op-list -- e
{ fift-ops-rewrite ({) swap compile-fift-op* (}) } : ops>wdef
// S(<orig-stack> - <new-stack>) compiles a "word" performing required action
{ SG( ops>wdef 0 swap } ::_ S(
// 1 2 3 S(a b c - c a b a) .s would print 3 1 2 1
// transform op-list into Fift source
// ls op -- ls'
{ fift-ops-rewrite
{ 3 [] execute } 'fop-entry-exec !
null ' process-fift-op foldl
dup null? { drop "" } { { +" " swap $+ } foldr-ne } cond
} : ops>$
{ SG( ops>$ 1 'nop } ::_ $S(
{ SG( ops>$ type } :_ .$S(
// $S(a b c - b c a c a c) => string "rot 2dup over"
// S(a b c - b c a c a c) => compile/execute block { rot 2dup over }
// $S(_ x y _ - y x) => string "drop pop(2)"
// .$S(x1 x2 - 17 x1) => print string "drop 17 swap"
// simplify/transform sequences of stack manipulation operations
LIST(. [a b c d e f g h i j]) constant std-stack
{ stk-start std-stack explode drop stk-to std-stack explode drop
} : simplify<{
{ build-stk-effect generate-reorder ops>$ } : }>stack
// simplify<{ drop drop over over -13 }>stack => string "2drop 2dup -13"
// simplify<{ 17 rot }>stack => string "swap 17 swap"
// simplify<{ 5 1 reverse }>stack => string "xchg(1,5) xchg(2,4)"

125
crypto/fift/lib/TonUtil.fif Normal file
View file

@ -0,0 +1,125 @@
library TonUtil // TON Blockchain Fift Library
"Lists.fif" include
-1 constant Masterchain
0 constant Basechain
// parse workchain id
// ( S -- workchain )
{ (number) 1- abort"workchain id must be an integer"
dup 32 fits not abort"workchain id must fit in 32 bits"
} : parse-workchain-id
{ (number) 1- abort"integer expected" } : parse-int
// Private key load/generate
// ( fname -- pubkey privkey )
{ dup ."Loading private key from file " type cr
file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long"
dup priv>pub swap
} : load-keypair
// ( fname -- pubkey privkey )
{ dup file-exists?
{ load-keypair }
{ dup newkeypair swap rot over swap B>file
rot ."Saved new private key to file " type cr
} cond
} : load-generate-keypair
// Parse smart-contract address
// ( S -- workchain addr bounce? )
{ $>smca not abort"invalid smart-contract address"
1 and 0=
} : parse-smc-addr
// ( wc addr -- ) Show address in <workchain>:<account> form
{ swap ._ .":" x. } : .addr
// ( wc addr flags -- ) Show address in base64url form
{ smca>$ type } : .Addr
// ( wc addr fname -- ) Save address to file in 36-byte format
{ -rot 256 u>B swap 32 i>B B+ swap B>file } : save-address
// ( wc addr fname -- ) Save address and print message
{ dup ."(Saving address to file " type .")" cr save-address
} : save-address-verbose
// ( fname -- wc addr ) Load address from file
{ file>B 32 B|
dup Blen { 32 B>i@ } { drop Basechain } cond
swap 256 B>u@
} : load-address
// ( fname -- wc addr ) Load address from file and print message
{ dup ."(Loading address from file " type .")" cr load-address
} : load-address-verbose
// Parse string as address or load address from file (if string is prefixed by @)
// ( S default-bounce -- workchain addr bounce? )
{ over $len 0= abort"empty smart-contract address"
swap dup 1 $| swap "@" $=
{ nip load-address rot } { drop nip parse-smc-addr } cond
} : parse-load-address
// ( hex-str -- addr ) Parses ADNL address
{ dup $len 64 <> abort"ADNL address must consist of exactly 64 hexadecimal characters"
(hex-number) 1 <> abort"ADNL address must consist of 64 hexadecimal characters"
dup 256 ufits not abort"invalid ADNL address"
} : parse-adnl-address
// ( b wc addr -- b' ) Serializes address into Builder b
{ -rot 8 i, swap 256 u, } : addr,
// Gram utilities
1000000000 constant Gram
{ Gram swap */r } : Gram*/
{ Gram * } : Gram*
// ( S -- nanograms )
{ (number) ?dup 0= abort"not a valid Gram amount"
1- ' Gram*/ ' Gram* cond
} : $>GR
{ bl word $>GR 1 'nop } ::_ GR$
// ( nanograms -- S )
{ dup abs <# ' # 9 times char . hold #s rot sign #>
nip -trailing0 } : (.GR)
{ (.GR) ."GR$" type space } : .GR
// b x -- b' ( serializes a Gram amount )
{ -1 { 1+ 2dup 8 * ufits } until
rot over 4 u, -rot 8 * u, } : Gram,
// s -- x s' ( deserializes a Gram amount )
{ 4 u@+ swap 8 * u@+ } : Gram@+
// s -- x
{ 4 u@+ swap 8 * u@ } : Gram@
// currency collections
// b x --> b' ( serializes a VarUInteger32 )
{ -1 { 1+ 2dup 8 * ufits } until
rot over 5 u, -rot 8 * u, } : VarUInt32,
// s --> x ( deserializes a VarUInteger32 )
{ 5 u@+ swap 8 * u@ } : VarUInt32@
32 constant cc-key-bits
' VarUInt32, : val,
' VarUInt32@ : val@
// d k v -- d'
{ <b swap val, b> <s swap rot cc-key-bits idict! not abort"cannot add key-value to CurrencyCollection" } : +ccpair
dictnew constant cc0 // zero currency collection
// ( v k -- d ) Creates currency collection representing v units of currency k
{ cc0 swap rot +ccpair } : of-cc
{ dictnew { over null? not } { swap uncons -rot unpair +ccpair } while nip } : list>cc
{ dup null? { ."(null) " drop } { val@ . } cond } dup : .maybeVarUInt32 : .val
{ cc-key-bits { swap 32 1<< rmod . ."-> " .val ."; " true } dictforeach drop cr } : .cc
{ cc-key-bits { rot . ."-> " swap .val .val ."; " true } dictdiff drop cr } : show-cc-diff
{ cc-key-bits { val@ swap val@ + val, true } dictmerge } : cc+
{ null swap cc-key-bits { val@ pair swap cons true } dictforeach drop } : cc>list-rev
{ cc>list-rev list-reverse } : cc>list
forget val, forget val@ forget .val
// Libraries
// ( -- D ) New empty library collection
' dictnew : Libs{
// ( D -- D ) Return library collection as dictionary
'nop : }Libs
// ( D c x -- D' ) Add a public/private library c to collection D
{ <b swap 1 u, over ref, b> <s swap hash rot 256 udict!+
0= abort"duplicate library in collection" } : lib+
// ( D c -- D' ) Add private library c to collection D
{ 0 lib+ } : private_lib
// ( D c -- D' ) Add public library c to collection D
{ 1 lib+ } : public_lib

175
crypto/fift/utils.cpp Normal file
View file

@ -0,0 +1,175 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "utils.h"
#include "words.h"
#include "td/utils/PathView.h"
#include "td/utils/filesystem.h"
#include "td/utils/port/path.h"
namespace fift {
namespace {
std::string fift_dir(std::string dir) {
return dir.size() > 0 ? dir : td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str() + "lib/";
}
td::Result<std::string> load_source(std::string name, std::string dir = "") {
return td::read_file_str(fift_dir(dir) + name);
}
td::Result<std::string> load_Fift_fif(std::string dir = "") {
return load_source("Fift.fif", dir);
}
td::Result<std::string> load_Asm_fif(std::string dir = "") {
return load_source("Asm.fif", dir);
}
td::Result<std::string> load_TonUtil_fif(std::string dir = "") {
return load_source("TonUtil.fif", dir);
}
td::Result<std::string> load_Lists_fif(std::string dir = "") {
return load_source("Lists.fif", dir);
}
class MemoryFileLoader : public fift::FileLoader {
public:
td::Result<fift::FileLoader::File> read_file(td::CSlice filename) override {
auto it = files_.find(filename);
if (it == files_.end()) {
return td::Status::Error("File not found");
}
fift::FileLoader::File res;
res.data = it->second;
res.path = it->first;
return std::move(res);
}
td::Status write_file(td::CSlice filename, td::Slice data) override {
files_[filename.str()] = data.str();
return td::Status::OK();
}
void add_file(std::string path, std::string data) {
files_[path] = std::move(data);
}
td::Result<File> read_file_part(td::CSlice filename, td::int64 size, td::int64 offset) override {
auto it = files_.find(filename);
if (it == files_.end()) {
return td::Status::Error("File not found");
}
fift::FileLoader::File res;
if (static_cast<td::int64>(it->second.size()) < offset) {
return td::Status::Error("Offset too large");
}
if (size > static_cast<td::int64>(it->second.size())) {
size = static_cast<td::int64>(it->second.size());
}
res.data = td::Slice(it->second).substr(td::narrow_cast<size_t>(offset), td::narrow_cast<size_t>(size)).str();
res.path = it->first;
return std::move(res);
}
bool is_file_exists(td::CSlice filename) override {
return files_.count(filename) != 0;
}
private:
std::map<std::string, std::string, std::less<>> files_;
};
td::Result<fift::SourceLookup> create_source_lookup(std::string main, bool need_preamble = true, bool need_asm = true,
bool need_ton_util = true, std::string dir = "") {
auto loader = std::make_unique<MemoryFileLoader>();
loader->add_file("/main.fif", std::move(main));
if (need_preamble) {
TRY_RESULT(f, load_Fift_fif(dir));
loader->add_file("/Fift.fif", std::move(f));
}
if (need_asm) {
TRY_RESULT(f, load_Asm_fif(dir));
loader->add_file("/Asm.fif", std::move(f));
}
if (need_ton_util) {
{
TRY_RESULT(f, load_Lists_fif(dir));
loader->add_file("/Lists.fif", std::move(f));
}
{
TRY_RESULT(f, load_TonUtil_fif(dir));
loader->add_file("/TonUtil.fif", std::move(f));
}
}
auto res = fift::SourceLookup(std::move(loader));
res.add_include_path("/");
return std::move(res);
}
td::Result<fift::SourceLookup> run_fift(fift::SourceLookup source_lookup, std::ostream *stream,
bool preload_fift = true, std::vector<std::string> args = {}) {
fift::Fift::Config config;
config.source_lookup = std::move(source_lookup);
fift::init_words_common(config.dictionary);
fift::init_words_vm(config.dictionary);
fift::init_words_ton(config.dictionary);
config.error_stream = stream;
config.output_stream = stream;
if (args.size() != 0) {
std::vector<const char *> argv;
for (auto &arg : args) {
argv.push_back(arg.c_str());
}
fift::import_cmdline_args(config.dictionary, argv[0], td::narrow_cast<int>(argv.size() - 1), argv.data() + 1);
}
fift::Fift fift{std::move(config)};
if (preload_fift) {
TRY_STATUS(fift.interpret_file("Fift.fif", ""));
}
TRY_STATUS(fift.interpret_file("main.fif", ""));
return std::move(fift.config().source_lookup);
}
} // namespace
td::Result<FiftOutput> mem_run_fift(std::string source, std::vector<std::string> args, std::string fift_dir) {
std::stringstream ss;
TRY_RESULT(source_lookup, create_source_lookup(source, true, true, true, fift_dir));
TRY_RESULT_ASSIGN(source_lookup, run_fift(std::move(source_lookup), &ss, true, std::move(args)));
FiftOutput res;
res.source_lookup = std::move(source_lookup);
res.output = ss.str();
return std::move(res);
}
td::Result<FiftOutput> mem_run_fift(SourceLookup source_lookup, std::vector<std::string> args) {
std::stringstream ss;
TRY_RESULT_ASSIGN(source_lookup, run_fift(std::move(source_lookup), &ss, true, std::move(args)));
FiftOutput res;
res.source_lookup = std::move(source_lookup);
res.output = ss.str();
return std::move(res);
}
td::Result<fift::SourceLookup> create_mem_source_lookup(std::string main, std::string fift_dir, bool need_preamble,
bool need_asm, bool need_ton_util) {
return create_source_lookup(main, need_preamble, need_asm, need_ton_util, fift_dir);
}
td::Result<td::Ref<vm::Cell>> compile_asm(td::Slice asm_code, std::string fift_dir) {
std::stringstream ss;
TRY_RESULT(source_lookup,
create_source_lookup(PSTRING() << "\"Asm.fif\" include\n<{ " << asm_code << "\n}>c boc>B \"res\" B>file",
true, true, true, fift_dir));
TRY_RESULT(res, run_fift(std::move(source_lookup), &ss));
TRY_RESULT(boc, res.read_file("res"));
return vm::std_boc_deserialize(std::move(boc.data));
}
} // namespace fift

35
crypto/fift/utils.h Normal file
View file

@ -0,0 +1,35 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "Fift.h"
#include <vector>
namespace fift {
struct FiftOutput {
SourceLookup source_lookup;
std::string output;
};
td::Result<fift::SourceLookup> create_mem_source_lookup(std::string main, std::string fift_dir = "",
bool need_preamble = true, bool need_asm = true,
bool need_ton_util = true);
td::Result<FiftOutput> mem_run_fift(std::string source, std::vector<std::string> args = {}, std::string fift_dir = "");
td::Result<FiftOutput> mem_run_fift(SourceLookup source_lookup, std::vector<std::string> args);
td::Result<td::Ref<vm::Cell>> compile_asm(td::Slice asm_code, std::string fift_dir = "");
} // namespace fift

2747
crypto/fift/words.cpp Normal file

File diff suppressed because it is too large Load diff

43
crypto/fift/words.h Normal file
View file

@ -0,0 +1,43 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "Dictionary.h"
namespace fift {
// thrown by 'quit', 'bye' and 'halt' for exiting to top level
struct Quit {
int res;
Quit() : res(0) {
}
Quit(int _res) : res(_res) {
}
};
struct SkipToEof {};
void init_words_common(Dictionary& dictionary);
void init_words_vm(Dictionary& dictionary);
void init_words_ton(Dictionary& dictionary);
void import_cmdline_args(Dictionary& d, std::string arg0, int n, const char* const argv[]);
int funny_interpret_loop(IntCtx& ctx);
} // namespace fift