diff --git a/CMakeLists.txt b/CMakeLists.txt index e00db57..039e403 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,5 +70,4 @@ add_subdirectory(example) if(NOT MSVC) add_subdirectory(lint) add_subdirectory(language/pl0) -add_subdirectory(language/culebra) endif() diff --git a/language/culebra/CMakeLists.txt b/language/culebra/CMakeLists.txt deleted file mode 100644 index fc25488..0000000 --- a/language/culebra/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 2.8) -include_directories(../..) - -if(MSVC) - add_compile_options(${cxx11_options} /W3) - add_definitions(-DUNICODE) -else() - add_compile_options(${cxx11_options}) -endif() - -add_executable(culebra main.cc) diff --git a/language/culebra/cul.vim b/language/culebra/cul.vim deleted file mode 100644 index 3fa37ee..0000000 --- a/language/culebra/cul.vim +++ /dev/null @@ -1,41 +0,0 @@ - -syn match culOperator "\%(+\|-\|/\|*\|=\|\^\|&\||\|!\|>\|<\|%\)=\?" -syn match culDecNumber "\<[0-9][0-9_]*" -syn match culFuncCall "\w\(\w\)*("he=e-1,me=e-1 -syn match culError ";" -syn match culError "\s*$" -syn match culLineComment "\(\/\/\|#\).*" contains=@Spell,javaScriptCommentTodo - -syn keyword culFunction fn -syn keyword culSelf self -syn keyword culConditional if else -syn keyword culRepeat while -syn keyword culReturn return -syn keyword culDebugger debugger -syn keyword culBoolean true false -syn keyword culCommentTodo TODO FIXME XXX TBD contained -syn keyword culStorage mut - -syn region culStringS start=+'+ skip=+\\\\\|\\'+ end=+'\|$+ -syn region culStringD start=+"+ skip=+\\\\\|\\"+ end=+"\|$+ -syn region culComment start="/\*" end="\*/" contains=@Spell,javaScriptCommentTodo - -hi def link culBoolean Boolean -hi def link culComment Comment -hi def link culCommentTodo Todo -hi def link culConditional Conditional -hi def link culDecNumber Number -hi def link culFuncCall Function -hi def link culFunction Type -hi def link culLineComment Comment -hi def link culOperator Operator -hi def link culRepeat Repeat -hi def link culReturn Statement -hi def link culDebugger Debug -hi def link culSelf Constant -hi def link culStorage StorageClass -hi def link culStringD String -hi def link culStringS String -hi def link culError Error - -let b:current_syntax = "cul" diff --git a/language/culebra/culebra.h b/language/culebra/culebra.h deleted file mode 100644 index 7393e49..0000000 --- a/language/culebra/culebra.h +++ /dev/null @@ -1,1005 +0,0 @@ -#include -#include -#include -#include - -namespace culebra { - -const auto grammar_ = R"( - - PROGRAM <- _ STATEMENTS _ - STATEMENTS <- (STATEMENT (_sp_ (';' / _nl_) (_ STATEMENT)?)*)? - STATEMENT <- DEBUGGER / RETURN / LEXICAL_SCOPE / EXPRESSION - - DEBUGGER <- debugger - RETURN <- return (_sp_ !_nl_ EXPRESSION)? - LEXICAL_SCOPE <- BLOCK - - EXPRESSION <- ASSIGNMENT / LOGICAL_OR - - ASSIGNMENT <- LET _ MUTABLE _ PRIMARY (_ (ARGUMENTS / INDEX / DOT))* _ '=' _ EXPRESSION - - LOGICAL_OR <- LOGICAL_AND (_ '||' _ LOGICAL_AND)* - LOGICAL_AND <- CONDITION (_ '&&' _ CONDITION)* - CONDITION <- ADDITIVE (_ CONDITION_OPERATOR _ ADDITIVE)* - ADDITIVE <- UNARY_PLUS (_ ADDITIVE_OPERATOR _ UNARY_PLUS)* - UNARY_PLUS <- UNARY_PLUS_OPERATOR? UNARY_MINUS - UNARY_MINUS <- UNARY_MINUS_OPERATOR? UNARY_NOT - UNARY_NOT <- UNARY_NOT_OPERATOR? MULTIPLICATIVE - MULTIPLICATIVE <- CALL (_ MULTIPLICATIVE_OPERATOR _ CALL)* - - CALL <- PRIMARY (_ (ARGUMENTS / INDEX / DOT))* - ARGUMENTS <- '(' _ SEQUENCE _ ')' - INDEX <- '[' _ EXPRESSION _ ']' - DOT <- '.' _ IDENTIFIER - - SEQUENCE <- (EXPRESSION (_ ',' _ EXPRESSION)*)? - - WHILE <- while _ EXPRESSION _ BLOCK - IF <- if _ EXPRESSION _ BLOCK (_ else _ if _ EXPRESSION _ BLOCK)* (_ else _ BLOCK)? - - PRIMARY <- WHILE / IF / FUNCTION / OBJECT / ARRAY / NIL / BOOLEAN / NUMBER / IDENTIFIER / STRING / INTERPOLATED_STRING / '(' _ EXPRESSION _ ')' - - FUNCTION <- fn _ PARAMETERS _ BLOCK - PARAMETERS <- '(' _ (PARAMETER (_ ',' _ PARAMETER)*)? _ ')' - PARAMETER <- MUTABLE _ IDENTIFIER - - BLOCK <- '{' _ STATEMENTS _ '}' - - CONDITION_OPERATOR <- '==' / '!=' / '<=' / '<' / '>=' / '>' - ADDITIVE_OPERATOR <- [-+] - UNARY_PLUS_OPERATOR <- '+' - UNARY_MINUS_OPERATOR <- '-' - UNARY_NOT_OPERATOR <- '!' - MULTIPLICATIVE_OPERATOR <- [*/%] - - LET <- < ('let' _wd_)? > - MUTABLE <- < ('mut' _wd_)? > - - IDENTIFIER <- < IdentInitChar IdentChar* > - - OBJECT <- '{' _ (OBJECT_PROPERTY (_ ',' _ OBJECT_PROPERTY)*)? _ '}' - OBJECT_PROPERTY <- MUTABLE _ IDENTIFIER _ ':' _ EXPRESSION - - ARRAY <- '[' _ SEQUENCE _ ']' (_ '(' _ EXPRESSION (_ ',' _ EXPRESSION)? _ ')')? - - NIL <- < 'nil' _wd_ > - BOOLEAN <- < ('true' / 'false') _wd_ > - - NUMBER <- < [0-9]+ > - STRING <- ['] < (!['] .)* > ['] - - INTERPOLATED_STRING <- '"' ('{' _ EXPRESSION _ '}' / INTERPOLATED_CONTENT)* '"' - INTERPOLATED_CONTENT <- (!["{] .) (!["{] .)* - - ~debugger <- 'debugger' _wd_ - ~while <- 'while' _wd_ - ~if <- 'if' _wd_ - ~else <- 'else' _wd_ - ~fn <- 'fn' _wd_ - ~return <- 'return' _wd_ - - ~_ <- (WhiteSpace / End)* - ~_sp_ <- SpaceChar* - ~_nl_ <- LineComment? End - ~_wd_ <- !IdentInitChar - - WhiteSpace <- SpaceChar / Comment - End <- EndOfLine / EndOfFile - Comment <- BlockComment / LineComment - - SpaceChar <- ' ' / '\t' - EndOfLine <- '\r\n' / '\n' / '\r' - EndOfFile <- !. - IdentInitChar <- [a-zA-Z_] - IdentChar <- [a-zA-Z0-9_] - BlockComment <- '/*' (!'*/' .)* '*/' - LineComment <- ('#' / '//') (!End .)* &End - -)"; - -inline peg::parser& get_parser() -{ - static peg::parser parser; - static bool initialized = false; - - if (!initialized) { - initialized = true; - - parser.log = [&](size_t ln, size_t col, const std::string& msg) { - std::cerr << ln << ":" << col << ": " << msg << std::endl; - }; - - if (!parser.load_grammar(grammar_)) { - throw std::logic_error("invalid peg grammar"); - } - - parser.enable_ast(); - } - - return parser; -} - -struct Value; -struct Symbol; -struct Environment; - -struct FunctionValue { - struct Parameter { - std::string name; - bool mut; - }; - - FunctionValue( - const std::vector& params, - const std::function env)>& eval) - : params(std::make_shared>(params)) - , eval(eval) {} - - std::shared_ptr> params; - std::function env)> eval; -}; - -struct ObjectValue { - ObjectValue() : properties(std::make_shared>()) {} - bool has(const std::string& name) const; - const Value& get(const std::string& name) const; - void assign(const std::string& name, const Value& val); - void initialize(const std::string& name, const Value& val, bool mut); - virtual std::map& builtins(); - - std::shared_ptr> properties; -}; - -struct ArrayValue : public ObjectValue { - ArrayValue() : values(std::make_shared>()) {} - std::map& builtins() override; - - std::shared_ptr> values; -}; - -struct Value -{ - enum Type { Nil, Bool, Long, String, Object, Array, Function }; - - Value() : type(Nil) {} - Value(const Value& rhs) : type(rhs.type), v(rhs.v) {} - Value(Value&& rhs) : type(rhs.type), v(rhs.v) {} - - Value& operator=(const Value& rhs) { - if (this != &rhs) { - type = rhs.type; - v = rhs.v; - } - return *this; - } - - Value& operator=(Value&& rhs) { - type = rhs.type; - v = rhs.v; - return *this; - } - - explicit Value(bool b) : type(Bool), v(b) {} - explicit Value(long l) : type(Long), v(l) {} - explicit Value(std::string&& s) : type(String), v(s) {} - explicit Value(ObjectValue&& o) : type(Object), v(o) {} - explicit Value(ArrayValue&& a) : type(Array), v(a) {} - explicit Value(FunctionValue&& f) : type(Function), v(f) {} - - bool to_bool() const { - switch (type) { - case Bool: return v.get(); - case Long: return v.get() != 0; - default: throw std::runtime_error("type error."); - } - } - - long to_long() const { - switch (type) { - //case Bool: return v.get(); - case Long: return v.get(); - default: throw std::runtime_error("type error."); - } - } - - std::string to_string() const { - switch (type) { - case String: return v.get(); - default: throw std::runtime_error("type error."); - } - } - - FunctionValue to_function() const { - switch (type) { - case Function: return v.get(); - default: throw std::runtime_error("type error."); - } - } - - const ObjectValue& to_object() const { - switch (type) { - case Object: return v.get(); - case Array: return v.get(); - default: throw std::runtime_error("type error."); - } - } - - ObjectValue& to_object() { - switch (type) { - case Object: return v.get(); - default: throw std::runtime_error("type error."); - } - } - - const ArrayValue& to_array() const { - switch (type) { - case Array: return v.get(); - default: throw std::runtime_error("type error."); - } - } - - std::string str_object() const; - - std::string str_array() const { - const auto& values = *to_array().values; - std::string s = "["; - for (auto i = 0u; i < values.size(); i++) { - if (i != 0) { - s += ", "; - } - s += values[i].str(); - } - s += "]"; - return s; - } - - std::string str() const { - switch (type) { - case Nil: return "nil"; - case Bool: return to_bool() ? "true" : "false"; - case Long: return std::to_string(to_long()); - case String: return "'" + to_string() + "'"; - case Object: return str_object(); - case Array: return str_array(); - case Function: return "[function]"; - default: throw std::logic_error("invalid internal condition."); - } - // NOTREACHED - } - - std::ostream& out(std::ostream& os) const { - os << str(); - return os; - } - - bool operator==(const Value& rhs) const { - switch (type) { - case Nil: return rhs.type == Nil; - case Bool: return to_bool() == rhs.to_bool(); - case Long: return to_long() == rhs.to_long(); - case String: return to_string() == rhs.to_string(); - // TODO: Object and Array support - default: throw std::logic_error("invalid internal condition."); - } - // NOTREACHED - } - - bool operator!=(const Value& rhs) const { - return !operator==(rhs); - } - - bool operator<=(const Value& rhs) const { - switch (type) { - case Nil: return false; - case Bool: return to_bool() <= rhs.to_bool(); - case Long: return to_long() <= rhs.to_long(); - case String: return to_string() <= rhs.to_string(); - // TODO: Object and Array support - default: throw std::logic_error("invalid internal condition."); - } - // NOTREACHED - } - - bool operator<(const Value& rhs) const { - switch (type) { - case Nil: return false; - case Bool: return to_bool() < rhs.to_bool(); - case Long: return to_long() < rhs.to_long(); - case String: return to_string() < rhs.to_string(); - // TODO: Object and Array support - default: throw std::logic_error("invalid internal condition."); - } - // NOTREACHED - } - - bool operator>=(const Value& rhs) const { - switch (type) { - case Nil: return false; - case Bool: return to_bool() >= rhs.to_bool(); - case Long: return to_long() >= rhs.to_long(); - case String: return to_string() >= rhs.to_string(); - // TODO: Object and Array support - default: throw std::logic_error("invalid internal condition."); - } - // NOTREACHED - } - - bool operator>(const Value& rhs) const { - switch (type) { - case Nil: return false; - case Bool: return to_bool() > rhs.to_bool(); - case Long: return to_long() > rhs.to_long(); - case String: return to_string() > rhs.to_string(); - // TODO: Object and Array support - default: throw std::logic_error("invalid internal condition."); - } - // NOTREACHED - } - - Type type; - peg::any v; -}; - -struct Symbol { - Value val; - bool mut; -}; - -inline std::ostream& operator<<(std::ostream& os, const Value& val) -{ - return val.out(os); -} - -struct Environment -{ - Environment(std::shared_ptr parent = nullptr) - : level(parent ? parent->level + 1 : 0) { - } - - void append_outer(std::shared_ptr outer) { - if (this->outer) { - this->outer->append_outer(outer); - } else { - this->outer = outer; - } - } - - bool has(const std::string& s) const { - if (dictionary.find(s) != dictionary.end()) { - return true; - } - return outer && outer->has(s); - } - - const Value& get(const std::string& s) const { - if (dictionary.find(s) != dictionary.end()) { - return dictionary.at(s).val; - } else if (outer) { - return outer->get(s); - } - std::string msg = "undefined variable '" + s + "'..."; - throw std::runtime_error(msg); - } - - void assign(const std::string& s, const Value& val) { - assert(has(s)); - if (dictionary.find(s) != dictionary.end()) { - auto& sym = dictionary[s]; - if (!sym.mut) { - std::string msg = "immutable variable '" + s + "'..."; - throw std::runtime_error(msg); - } - sym.val = val; - return; - } - outer->assign(s, val); - return; - } - - void initialize(const std::string& s, const Value& val, bool mut) { - dictionary[s] = Symbol{ val, mut }; - } - - void initialize(const std::string& s, Value&& val, bool mut) { - dictionary[s] = Symbol{ std::move(val), mut }; - } - - size_t level; - std::shared_ptr outer; - std::map dictionary; -}; - -typedef std::function Debugger; - -inline bool ObjectValue::has(const std::string& name) const { - if (properties->find(name) == properties->end()) { - const auto& props = const_cast(this)->builtins(); - return props.find(name) != props.end(); - } - return true; -} - -inline const Value& ObjectValue::get(const std::string& name) const { - if (properties->find(name) == properties->end()) { - const auto& props = const_cast(this)->builtins(); - return props.at(name); - } - return properties->at(name).val; -} - -inline void ObjectValue::assign(const std::string& name, const Value& val) { - assert(has(name)); - auto& sym = properties->at(name); - if (!sym.mut) { - std::string msg = "immutable property '" + name + "'..."; - throw std::runtime_error(msg); - } - sym.val = val; - return; -} - -inline void ObjectValue::initialize(const std::string& name, const Value& val, bool mut) { - (*properties)[name] = Symbol{ val, mut }; -} - -inline std::map& ObjectValue::builtins() { - static std::map props_ = { - { - "size", - Value(FunctionValue( - {}, - [](std::shared_ptr callEnv) { - const auto& val = callEnv->get("this"); - long n = val.to_object().properties->size(); - return Value(n); - } - )) - } - }; - return props_; -} - -inline std::map& ArrayValue::builtins() { - static std::map props_ = { - { - "size", - Value(FunctionValue( - {}, - [](std::shared_ptr callEnv) { - const auto& val = callEnv->get("this"); - long n = val.to_array().values->size(); - return Value(n); - } - )) - }, - { - "push", - Value(FunctionValue { - { {"arg", false} }, - [](std::shared_ptr callEnv) { - const auto& val = callEnv->get("this"); - const auto& arg = callEnv->get("arg"); - val.to_array().values->push_back(arg); - return Value(); - } - }) - } - }; - return props_; -} - -inline std::string Value::str_object() const { - const auto& properties = *to_object().properties; - std::string s = "{"; - auto it = properties.begin(); - for (; it != properties.end(); ++it) { - if (it != properties.begin()) { - s += ", "; - } - const auto& name = it->first; - const auto& sym = it->second; - if (sym.mut) { - s += "mut "; - } - s += name; - s += ": "; - s += sym.val.str(); - } - s += "}"; - return s; -} - -inline void setup_built_in_functions(Environment& env) { - env.initialize( - "puts", - Value(FunctionValue( - { {"arg", true} }, - [](std::shared_ptr env) { - std::cout << env->get("arg").str() << std::endl; - return Value(); - } - )), - false); - - env.initialize( - "assert", - Value(FunctionValue( - { {"arg", true} }, - [](std::shared_ptr env) { - auto cond = env->get("arg").to_bool(); - if (!cond) { - auto line = env->get("__LINE__").to_long(); - auto column = env->get("__COLUMN__").to_long(); - std::string msg = "assert failed at " + std::to_string(line) + ":" + std::to_string(column) + "."; - throw std::runtime_error(msg); - } - return Value(); - } - )), - false); -} - -struct Interpreter -{ - Interpreter(Debugger debugger = nullptr) - : debugger_(debugger) { - } - - Value eval(const peg::Ast& ast, std::shared_ptr env) { - using namespace peg::udl; - - if (debugger_) { - if (ast.original_tag == "STATEMENT"_) { - auto force_to_break = ast.tag == "DEBUGGER"_; - debugger_(ast, *env, force_to_break); - } - } - - switch (ast.tag) { - case "STATEMENTS"_: return eval_statements(ast, env); - case "WHILE"_: return eval_while(ast, env); - case "IF"_: return eval_if(ast, env); - case "FUNCTION"_: return eval_function(ast, env); - case "CALL"_: return eval_call(ast, env); - case "LEXICAL_SCOPE"_: return eval_lexical_scope(ast, env); - case "ASSIGNMENT"_: return eval_assignment(ast, env); - case "LOGICAL_OR"_: return eval_logical_or(ast, env); - case "LOGICAL_AND"_: return eval_logical_and(ast, env); - case "CONDITION"_: return eval_condition(ast, env); - case "UNARY_PLUS"_: return eval_unary_plus(ast, env); - case "UNARY_MINUS"_: return eval_unary_minus(ast, env); - case "UNARY_NOT"_: return eval_unary_not(ast, env); - case "ADDITIVE"_: - case "MULTIPLICATIVE"_: return eval_bin_expression(ast, env); - case "IDENTIFIER"_: return eval_identifier(ast, env); - case "OBJECT"_: return eval_object(ast, env); - case "ARRAY"_: return eval_array(ast, env); - case "NIL"_: return eval_nil(ast, env); - case "BOOLEAN"_: return eval_bool(ast, env); - case "NUMBER"_: return eval_number(ast, env); - case "INTERPOLATED_STRING"_: return eval_interpolated_string(ast, env); - case "DEBUGGER"_: return Value(); - case "RETURN"_: eval_return(ast, env); - } - - if (ast.is_token) { - return Value(std::string(ast.token)); - } - - // NOTREACHED - throw std::logic_error("invalid Ast type"); - } - -private: - Value eval_statements(const peg::Ast& ast, std::shared_ptr env) { - if (ast.is_token) { - return eval(ast, env); - } else if (ast.nodes.empty()) { - return Value(); - } - auto it = ast.nodes.begin(); - while (it != ast.nodes.end() - 1) { - eval(**it, env); - ++it; - } - return eval(**it, env); - } - - Value eval_while(const peg::Ast& ast, std::shared_ptr env) { - for (;;) { - auto cond = eval(*ast.nodes[0], env); - if (!cond.to_bool()) { - break; - } - eval(*ast.nodes[1], env); - } - return Value(); - } - - Value eval_if(const peg::Ast& ast, std::shared_ptr env) { - const auto& nodes = ast.nodes; - - for (auto i = 0u; i < nodes.size(); i += 2) { - if (i + 1 == nodes.size()) { - return eval(*nodes[i], env); - } else { - auto cond = eval(*nodes[i], env); - if (cond.to_bool()) { - return eval(*nodes[i + 1], env); - } - } - } - - return Value(); - } - - Value eval_function(const peg::Ast& ast, std::shared_ptr env) { - std::vector params; - for (auto node: ast.nodes[0]->nodes) { - auto mut = node->nodes[0]->token == "mut"; - const auto& name = node->nodes[1]->token; - params.push_back({ name, mut }); - } - - auto body = ast.nodes[1]; - - return Value(FunctionValue( - params, - [=](std::shared_ptr callEnv) { - callEnv->append_outer(env); - return eval(*body, callEnv); - } - )); - }; - - Value eval_function_call(const peg::Ast& ast, std::shared_ptr env, const Value& val) { - const auto& f = val.to_function(); - const auto& params = *f.params; - const auto& args = ast.nodes; - - if (params.size() <= args.size()) { - auto callEnv = std::make_shared(env); - callEnv->initialize("self", val, false); - for (auto iprm = 0u; iprm < params.size(); iprm++) { - auto param = params[iprm]; - auto arg = args[iprm]; - auto val = eval(*arg, env); - callEnv->initialize(param.name, val, param.mut); - } - callEnv->initialize("__LINE__", Value((long)ast.line), false); - callEnv->initialize("__COLUMN__", Value((long)ast.column), false); - try { - return f.eval(callEnv); - } catch (const Value& e) { - return e; - } - } - - std::string msg = "arguments error..."; - throw std::runtime_error(msg); - } - - Value eval_array_reference(const peg::Ast& ast, std::shared_ptr env, const Value& val) { - const auto& arr = val.to_array(); - auto idx = eval(ast, env).to_long(); - if (idx < 0) { - idx = arr.values->size() + idx; - } - if (0 <= idx && idx < static_cast(arr.values->size())) { - return arr.values->at(idx); - } else { - throw std::logic_error("index out of range."); - } - return val; - } - - Value eval_property(const peg::Ast& ast, std::shared_ptr env, const Value& val) { - const auto& obj = val.to_object(); - auto name = ast.token; - if (!obj.has(name)) { - return Value(); - } - const auto& prop = obj.get(name); - if (prop.type == Value::Function) { - const auto& pf = prop.to_function(); - return Value(FunctionValue( - *pf.params, - [=](std::shared_ptr callEnv) { - callEnv->initialize("this", val, false); - return pf.eval(callEnv); - } - )); - } - return prop; - } - - Value eval_call(const peg::Ast& ast, std::shared_ptr env) { - using namespace peg::udl; - - Value val = eval(*ast.nodes[0], env); - - for (auto i = 1u; i < ast.nodes.size(); i++) { - const auto& postfix = *ast.nodes[i]; - - switch (postfix.original_tag) { - case "ARGUMENTS"_: val = eval_function_call(postfix, env, val); break; - case "INDEX"_: val = eval_array_reference(postfix, env, val); break; - case "DOT"_: val = eval_property(postfix, env, val); break; - default: throw std::logic_error("invalid internal condition."); - } - } - - return val; - } - - Value eval_lexical_scope(const peg::Ast& ast, std::shared_ptr env) { - auto scopeEnv = std::make_shared(); - scopeEnv->append_outer(env); - for (auto node: ast.nodes) { - eval(*node, scopeEnv); - } - return Value(); - } - - Value eval_logical_or(const peg::Ast& ast, std::shared_ptr env) { - assert(ast.nodes.size() > 1); // if the size is 1, thes node will be hoisted. - Value val; - for (auto node: ast.nodes) { - val = eval(*node, env); - if (val.to_bool()) { - return val; - } - } - return val; - } - - Value eval_logical_and(const peg::Ast& ast, std::shared_ptr env) { - Value val; - for (auto node: ast.nodes) { - val = eval(*node, env); - if (!val.to_bool()) { - return val; - } - } - return val; - } - - Value eval_condition(const peg::Ast& ast, std::shared_ptr env) { - auto lhs = eval(*ast.nodes[0], env); - auto ope = eval(*ast.nodes[1], env).to_string(); - auto rhs = eval(*ast.nodes[2], env); - - if (ope == "==") { return Value(lhs == rhs); } - else if (ope == "!=") { return Value(lhs != rhs); } - else if (ope == "<=") { return Value(lhs <= rhs); } - else if (ope == "<") { return Value(lhs < rhs); } - else if (ope == ">=") { return Value(lhs >= rhs); } - else if (ope == ">") { return Value(lhs > rhs); } - else { throw std::logic_error("invalid internal condition."); } - } - - Value eval_unary_plus(const peg::Ast& ast, std::shared_ptr env) { - return eval(*ast.nodes[1], env); - } - - Value eval_unary_minus(const peg::Ast& ast, std::shared_ptr env) { - return Value(eval(*ast.nodes[1], env).to_long() * -1); - } - - Value eval_unary_not(const peg::Ast& ast, std::shared_ptr env) { - return Value(!eval(*ast.nodes[1], env).to_bool()); - } - - Value eval_bin_expression(const peg::Ast& ast, std::shared_ptr env) { - auto ret = eval(*ast.nodes[0], env).to_long(); - for (auto i = 1u; i < ast.nodes.size(); i += 2) { - auto val = eval(*ast.nodes[i + 1], env).to_long(); - auto ope = eval(*ast.nodes[i], env).to_string()[0]; - switch (ope) { - case '+': ret += val; break; - case '-': ret -= val; break; - case '*': ret *= val; break; - case '%': ret %= val; break; - case '/': - if (val == 0) { - throw std::runtime_error("divide by 0 error"); - } - ret /= val; - break; - } - } - return Value(ret); - } - - bool is_keyword(const std::string& ident) const { - static std::set keywords = { "nil", "true", "false", "mut", "debugger", "return", "while", "if", "else", "fn" }; - return keywords.find(ident) != keywords.end(); - } - - Value eval_assignment(const peg::Ast& ast, std::shared_ptr env) { - auto lvaloff = 2; - auto lvalcnt = ast.nodes.size() - 3; - - auto let = ast.nodes[0]->token == "let"; - auto mut = ast.nodes[1]->token == "mut"; - auto rval = eval(*ast.nodes.back(), env); - - if (lvalcnt == 1) { - const auto& ident = ast.nodes[lvaloff]->token; - if (!let && env->has(ident)) { - env->assign(ident, rval); - } else if (is_keyword(ident)) { - throw std::runtime_error("left-hand side is invalid variable name."); - } else { - env->initialize(ident, rval, mut); - } - return rval; - } else { - using namespace peg::udl; - - Value lval = eval(*ast.nodes[lvaloff], env); - - auto end = lvaloff + lvalcnt - 1; - for (auto i = lvaloff + 1; i < end; i++) { - const auto& postfix = *ast.nodes[i]; - - switch (postfix.original_tag) { - case "ARGUMENTS"_: lval = eval_function_call(postfix, env, lval); break; - case "INDEX"_: lval = eval_array_reference(postfix, env, lval); break; - case "DOT"_: lval = eval_property(postfix, env, lval); break; - default: throw std::logic_error("invalid internal condition."); - } - } - - const auto& postfix = *ast.nodes[end]; - - switch (postfix.original_tag) { - case "INDEX"_: { - const auto& arr = lval.to_array(); - auto idx = eval(postfix, env).to_long(); - if (0 <= idx && idx < static_cast(arr.values->size())) { - arr.values->at(idx) = rval; - } else { - throw std::logic_error("index out of range."); - } - return rval; - } - case "DOT"_: { - auto& obj = lval.to_object(); - auto name = postfix.token; - if (obj.has(name)) { - obj.assign(name, rval); - } else { - obj.initialize(name, rval, mut); - } - return rval; - } - default: - throw std::logic_error("invalid internal condition."); - } - } - }; - - Value eval_identifier(const peg::Ast& ast, std::shared_ptr env) { - return env->get(ast.token); - }; - - Value eval_object(const peg::Ast& ast, std::shared_ptr env) { - ObjectValue obj; - for (auto i = 0u; i < ast.nodes.size(); i++) { - const auto& prop = *ast.nodes[i]; - auto mut = prop.nodes[0]->token == "mut"; - const auto& name = prop.nodes[1]->token; - auto val = eval(*prop.nodes[2], env); - obj.properties->emplace(name, Symbol{ std::move(val), mut }); - } - return Value(std::move(obj)); - } - - Value eval_array(const peg::Ast& ast, std::shared_ptr env) { - ArrayValue arr; - - if (ast.nodes.size() >= 2) { - auto count = eval(*ast.nodes[1], env).to_long(); - if (ast.nodes.size() == 3) { - auto val = eval(*ast.nodes[2], env); - arr.values->resize(count, std::move(val)); - } else { - arr.values->resize(count); - } - } - - const auto& nodes = ast.nodes[0]->nodes; - for (auto i = 0u; i < nodes.size(); i++) { - auto expr = nodes[i]; - auto val = eval(*expr, env); - if (i < arr.values->size()) { - arr.values->at(i) = std::move(val); - } else { - arr.values->push_back(std::move(val)); - } - } - - return Value(std::move(arr)); - } - - Value eval_nil(const peg::Ast& ast, std::shared_ptr env) { - return Value(); - }; - - Value eval_bool(const peg::Ast& ast, std::shared_ptr env) { - return Value(ast.token == "true"); - }; - - Value eval_number(const peg::Ast& ast, std::shared_ptr env) { - return Value(stol(ast.token)); - }; - - Value eval_interpolated_string(const peg::Ast& ast, std::shared_ptr env) { - std::string s; - for (auto node: ast.nodes) { - const auto& val = eval(*node, env); - if (val.type == Value::String) { - s += val.to_string(); - } else { - s += val.str(); - } - } - return Value(std::move(s)); - }; - - void eval_return(const peg::Ast& ast, std::shared_ptr env) { - if (ast.nodes.empty()) { - throw Value(); - } else { - throw eval(*ast.nodes[0], env); - } - } - - Debugger debugger_; -}; - -inline std::shared_ptr parse( - const std::string& path, - const char* expr, - size_t len, - std::vector& msgs) -{ - auto& parser = get_parser(); - - parser.log = [&](size_t ln, size_t col, const std::string& err_msg) { - std::stringstream ss; - ss << path << ":" << ln << ":" << col << ": " << err_msg << std::endl; - msgs.push_back(ss.str()); - }; - - std::shared_ptr ast; - if (parser.parse_n(expr, len, ast, path.c_str())) { - return peg::AstOptimizer(true, { "PARAMETERS", "SEQUENCE", "OBJECT", "ARRAY", "RETURN", "LEXICAL_SCOPE" }).optimize(ast); - } - - return nullptr; -} - -inline bool interpret( - const std::shared_ptr& ast, - std::shared_ptr env, - Value& val, - std::vector& msgs, - Debugger debugger = nullptr) -{ - try { - val = Interpreter(debugger).eval(*ast, env); - return true; - } catch (const Value& e) { - val = e; - return true; - } catch (std::runtime_error& e) { - msgs.push_back(e.what()); - } - - return false; -} - -} // namespace culebra diff --git a/language/culebra/culebra.sln b/language/culebra/culebra.sln deleted file mode 100644 index ced9fb7..0000000 --- a/language/culebra/culebra.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "culebra", "culebra.vcxproj", "{F85B641A-7538-4809-8175-C528FF632CF6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F85B641A-7538-4809-8175-C528FF632CF6}.Debug|Win32.ActiveCfg = Debug|Win32 - {F85B641A-7538-4809-8175-C528FF632CF6}.Debug|Win32.Build.0 = Debug|Win32 - {F85B641A-7538-4809-8175-C528FF632CF6}.Release|Win32.ActiveCfg = Release|Win32 - {F85B641A-7538-4809-8175-C528FF632CF6}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/language/culebra/culebra.vcxproj b/language/culebra/culebra.vcxproj deleted file mode 100644 index dcb7333..0000000 --- a/language/culebra/culebra.vcxproj +++ /dev/null @@ -1,93 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - - - - - - - - - {F85B641A-7538-4809-8175-C528FF632CF6} - Win32Proj - culebra - - - - Application - true - Unicode - v140 - - - Application - false - true - Unicode - v140 - - - - - - - - - - - - - true - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - ../.. - - - Console - true - Ws2_32.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ../.. - - - Console - true - true - true - Ws2_32.lib;%(AdditionalDependencies) - - - - - - \ No newline at end of file diff --git a/language/culebra/linenoise.hpp b/language/culebra/linenoise.hpp deleted file mode 100644 index bacd245..0000000 --- a/language/culebra/linenoise.hpp +++ /dev/null @@ -1,1970 +0,0 @@ -/* - * linenoise.hpp -- Multi-platfrom C++ header-only linenoise library. - * - * All credits and commendations have to go to the authors of the - * following excellent libraries. - * - * - linenoise.h and linenose.c (https://github.com/antirez/linenoise) - * - ANSI.c (https://github.com/adoxa/ansicon) - * - Win32_ANSI.h and Win32_ANSI.c (https://github.com/MSOpenTech/redis) - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2015 yhirose - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* linenoise.h -- guerrilla line editing library against the idea that a - * line editing lib needs to be 20,000 lines of C code. - * - * See linenoise.c for more information. - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2010, Salvatore Sanfilippo - * Copyright (c) 2010, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * ANSI.c - ANSI escape sequence console driver. - * - * Copyright (C) 2005-2014 Jason Hood - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the author be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * Jason Hood - * jadoxa@yahoo.com.au - */ - -/* - * Win32_ANSI.h and Win32_ANSI.c - * - * Derived from ANSI.c by Jason Hood, from his ansicon project (https://github.com/adoxa/ansicon), with modifications. - * - * Copyright (c), Microsoft Open Technologies, Inc. - * All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __LINENOISE_HPP -#define __LINENOISE_HPP - -#ifndef _WIN32 -#include -#include -#include -#else -#define NOMINMAX -#include -#include -#ifndef STDIN_FILENO -#define STDIN_FILENO (_fileno(stdin)) -#endif -#ifndef STDOUT_FILENO -#define STDOUT_FILENO 1 -#endif -#define isatty _isatty -#define write win32_write -#define read _read -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace linenoise { - -typedef std::function&)> CompletionCallback; - -#ifdef _WIN32 - -namespace ansi { - -#define lenof(array) (sizeof(array)/sizeof(*(array))) - -typedef struct -{ - BYTE foreground; // ANSI base color (0 to 7; add 30) - BYTE background; // ANSI base color (0 to 7; add 40) - BYTE bold; // console FOREGROUND_INTENSITY bit - BYTE underline; // console BACKGROUND_INTENSITY bit - BYTE rvideo; // swap foreground/bold & background/underline - BYTE concealed; // set foreground/bold to background/underline - BYTE reverse; // swap console foreground & background attributes -} GRM, *PGRM; // Graphic Rendition Mode - - -inline bool is_digit(char c) { return '0' <= c && c <= '9'; } - -// ========== Global variables and constants - -HANDLE hConOut; // handle to CONOUT$ - -const char ESC = '\x1B'; // ESCape character -const char BEL = '\x07'; -const char SO = '\x0E'; // Shift Out -const char SI = '\x0F'; // Shift In - -const size_t MAX_ARG = 16; // max number of args in an escape sequence -int state; // automata state -TCHAR prefix; // escape sequence prefix ( '[', ']' or '(' ); -TCHAR prefix2; // secondary prefix ( '?' or '>' ); -TCHAR suffix; // escape sequence suffix -int es_argc; // escape sequence args count -int es_argv[MAX_ARG]; // escape sequence args -TCHAR Pt_arg[MAX_PATH * 2]; // text parameter for Operating System Command -int Pt_len; -BOOL shifted; - - -// DEC Special Graphics Character Set from -// http://vt100.net/docs/vt220-rm/table2-4.html -// Some of these may not look right, depending on the font and code page (in -// particular, the Control Pictures probably won't work at all). -const WCHAR G1[] = -{ - ' ', // _ - blank - L'\x2666', // ` - Black Diamond Suit - L'\x2592', // a - Medium Shade - L'\x2409', // b - HT - L'\x240c', // c - FF - L'\x240d', // d - CR - L'\x240a', // e - LF - L'\x00b0', // f - Degree Sign - L'\x00b1', // g - Plus-Minus Sign - L'\x2424', // h - NL - L'\x240b', // i - VT - L'\x2518', // j - Box Drawings Light Up And Left - L'\x2510', // k - Box Drawings Light Down And Left - L'\x250c', // l - Box Drawings Light Down And Right - L'\x2514', // m - Box Drawings Light Up And Right - L'\x253c', // n - Box Drawings Light Vertical And Horizontal - L'\x00af', // o - SCAN 1 - Macron - L'\x25ac', // p - SCAN 3 - Black Rectangle - L'\x2500', // q - SCAN 5 - Box Drawings Light Horizontal - L'_', // r - SCAN 7 - Low Line - L'_', // s - SCAN 9 - Low Line - L'\x251c', // t - Box Drawings Light Vertical And Right - L'\x2524', // u - Box Drawings Light Vertical And Left - L'\x2534', // v - Box Drawings Light Up And Horizontal - L'\x252c', // w - Box Drawings Light Down And Horizontal - L'\x2502', // x - Box Drawings Light Vertical - L'\x2264', // y - Less-Than Or Equal To - L'\x2265', // z - Greater-Than Or Equal To - L'\x03c0', // { - Greek Small Letter Pi - L'\x2260', // | - Not Equal To - L'\x00a3', // } - Pound Sign - L'\x00b7', // ~ - Middle Dot -}; - -#define FIRST_G1 '_' -#define LAST_G1 '~' - - -// color constants - -#define FOREGROUND_BLACK 0 -#define FOREGROUND_WHITE FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE - -#define BACKGROUND_BLACK 0 -#define BACKGROUND_WHITE BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE - -const BYTE foregroundcolor[8] = - { - FOREGROUND_BLACK, // black foreground - FOREGROUND_RED, // red foreground - FOREGROUND_GREEN, // green foreground - FOREGROUND_RED | FOREGROUND_GREEN, // yellow foreground - FOREGROUND_BLUE, // blue foreground - FOREGROUND_BLUE | FOREGROUND_RED, // magenta foreground - FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan foreground - FOREGROUND_WHITE // white foreground - }; - -const BYTE backgroundcolor[8] = - { - BACKGROUND_BLACK, // black background - BACKGROUND_RED, // red background - BACKGROUND_GREEN, // green background - BACKGROUND_RED | BACKGROUND_GREEN, // yellow background - BACKGROUND_BLUE, // blue background - BACKGROUND_BLUE | BACKGROUND_RED, // magenta background - BACKGROUND_BLUE | BACKGROUND_GREEN, // cyan background - BACKGROUND_WHITE, // white background - }; - -const BYTE attr2ansi[8] = // map console attribute to ANSI number -{ - 0, // black - 4, // blue - 2, // green - 6, // cyan - 1, // red - 5, // magenta - 3, // yellow - 7 // white -}; - -GRM grm; - -// saved cursor position -COORD SavePos; - -// ========== Print Buffer functions - -#define BUFFER_SIZE 2048 - -int nCharInBuffer; -WCHAR ChBuffer[BUFFER_SIZE]; - -//----------------------------------------------------------------------------- -// FlushBuffer() -// Writes the buffer to the console and empties it. -//----------------------------------------------------------------------------- - -inline void FlushBuffer(void) -{ - DWORD nWritten; - if (nCharInBuffer <= 0) return; - WriteConsole(hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL); - nCharInBuffer = 0; -} - -//----------------------------------------------------------------------------- -// PushBuffer( WCHAR c ) -// Adds a character in the buffer. -//----------------------------------------------------------------------------- - -inline void PushBuffer(WCHAR c) -{ - if (shifted && c >= FIRST_G1 && c <= LAST_G1) - c = G1[c - FIRST_G1]; - ChBuffer[nCharInBuffer] = c; - if (++nCharInBuffer == BUFFER_SIZE) - FlushBuffer(); -} - -//----------------------------------------------------------------------------- -// SendSequence( LPTSTR seq ) -// Send the string to the input buffer. -//----------------------------------------------------------------------------- - -inline void SendSequence(LPTSTR seq) -{ - DWORD out; - INPUT_RECORD in; - HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); - - in.EventType = KEY_EVENT; - in.Event.KeyEvent.bKeyDown = TRUE; - in.Event.KeyEvent.wRepeatCount = 1; - in.Event.KeyEvent.wVirtualKeyCode = 0; - in.Event.KeyEvent.wVirtualScanCode = 0; - in.Event.KeyEvent.dwControlKeyState = 0; - for (; *seq; ++seq) - { - in.Event.KeyEvent.uChar.UnicodeChar = *seq; - WriteConsoleInput(hStdIn, &in, 1, &out); - } -} - -// ========== Print functions - -//----------------------------------------------------------------------------- -// InterpretEscSeq() -// Interprets the last escape sequence scanned by ParseAndPrintANSIString -// prefix escape sequence prefix -// es_argc escape sequence args count -// es_argv[] escape sequence args array -// suffix escape sequence suffix -// -// for instance, with \e[33;45;1m we have -// prefix = '[', -// es_argc = 3, es_argv[0] = 33, es_argv[1] = 45, es_argv[2] = 1 -// suffix = 'm' -//----------------------------------------------------------------------------- - -inline void InterpretEscSeq(void) -{ - int i; - WORD attribut; - CONSOLE_SCREEN_BUFFER_INFO Info; - CONSOLE_CURSOR_INFO CursInfo; - DWORD len, NumberOfCharsWritten; - COORD Pos; - SMALL_RECT Rect; - CHAR_INFO CharInfo; - - if (prefix == '[') - { - if (prefix2 == '?' && (suffix == 'h' || suffix == 'l')) - { - if (es_argc == 1 && es_argv[0] == 25) - { - GetConsoleCursorInfo(hConOut, &CursInfo); - CursInfo.bVisible = (suffix == 'h'); - SetConsoleCursorInfo(hConOut, &CursInfo); - return; - } - } - // Ignore any other \e[? or \e[> sequences. - if (prefix2 != 0) - return; - - GetConsoleScreenBufferInfo(hConOut, &Info); - switch (suffix) - { - case 'm': - if (es_argc == 0) es_argv[es_argc++] = 0; - for (i = 0; i < es_argc; i++) - { - if (30 <= es_argv[i] && es_argv[i] <= 37) - grm.foreground = es_argv[i] - 30; - else if (40 <= es_argv[i] && es_argv[i] <= 47) - grm.background = es_argv[i] - 40; - else switch (es_argv[i]) - { - case 0: - case 39: - case 49: - { - TCHAR def[4]; - int a; - *def = '7'; def[1] = '\0'; - GetEnvironmentVariable(L"ANSICON_DEF", def, lenof(def)); - a = wcstol(def, NULL, 16); - grm.reverse = FALSE; - if (a < 0) - { - grm.reverse = TRUE; - a = -a; - } - if (es_argv[i] != 49) - grm.foreground = attr2ansi[a & 7]; - if (es_argv[i] != 39) - grm.background = attr2ansi[(a >> 4) & 7]; - if (es_argv[i] == 0) - { - if (es_argc == 1) - { - grm.bold = a & FOREGROUND_INTENSITY; - grm.underline = a & BACKGROUND_INTENSITY; - } - else - { - grm.bold = 0; - grm.underline = 0; - } - grm.rvideo = 0; - grm.concealed = 0; - } - } - break; - - case 1: grm.bold = FOREGROUND_INTENSITY; break; - case 5: // blink - case 4: grm.underline = BACKGROUND_INTENSITY; break; - case 7: grm.rvideo = 1; break; - case 8: grm.concealed = 1; break; - case 21: // oops, this actually turns on double underline - case 22: grm.bold = 0; break; - case 25: - case 24: grm.underline = 0; break; - case 27: grm.rvideo = 0; break; - case 28: grm.concealed = 0; break; - } - } - if (grm.concealed) - { - if (grm.rvideo) - { - attribut = foregroundcolor[grm.foreground] - | backgroundcolor[grm.foreground]; - if (grm.bold) - attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - else - { - attribut = foregroundcolor[grm.background] - | backgroundcolor[grm.background]; - if (grm.underline) - attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - } - else if (grm.rvideo) - { - attribut = foregroundcolor[grm.background] - | backgroundcolor[grm.foreground]; - if (grm.bold) - attribut |= BACKGROUND_INTENSITY; - if (grm.underline) - attribut |= FOREGROUND_INTENSITY; - } - else - attribut = foregroundcolor[grm.foreground] | grm.bold - | backgroundcolor[grm.background] | grm.underline; - if (grm.reverse) - attribut = ((attribut >> 4) & 15) | ((attribut & 15) << 4); - SetConsoleTextAttribute(hConOut, attribut); - return; - - case 'J': - if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[J == ESC[0J - if (es_argc != 1) return; - switch (es_argv[0]) - { - case 0: // ESC[0J erase from cursor to end of display - len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X - + Info.dwSize.X - Info.dwCursorPosition.X - 1; - FillConsoleOutputCharacter(hConOut, ' ', len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1J erase from start to cursor. - Pos.X = 0; - Pos.Y = 0; - len = Info.dwCursorPosition.Y * Info.dwSize.X - + Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2J Clear screen and home cursor - Pos.X = 0; - Pos.Y = 0; - len = Info.dwSize.X * Info.dwSize.Y; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - SetConsoleCursorPosition(hConOut, Pos); - return; - - default: - return; - } - - case 'K': - if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[K == ESC[0K - if (es_argc != 1) return; - switch (es_argv[0]) - { - case 0: // ESC[0K Clear to end of line - len = Info.dwSize.X - Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1K Clear from start of line to cursor - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', - Info.dwCursorPosition.X + 1, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, - Info.dwCursorPosition.X + 1, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2K Clear whole line. - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', Info.dwSize.X, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, - Info.dwSize.X, Pos, - &NumberOfCharsWritten); - return; - - default: - return; - } - - case 'X': // ESC[#X Erase # characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[X == ESC[1X - if (es_argc != 1) return; - FillConsoleOutputCharacter(hConOut, ' ', es_argv[0], - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, es_argv[0], - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 'L': // ESC[#L Insert # blank lines. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[L == ESC[1L - if (es_argc != 1) return; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'M': // ESC[#M Delete # lines. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[M == ESC[1M - if (es_argc != 1) return; - if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y) - es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y + es_argv[0]; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'P': // ESC[#P Delete # characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[P == ESC[1P - if (es_argc != 1) return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X + es_argv[0]; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Info.dwCursorPosition, - &CharInfo); - return; - - case '@': // ESC[#@ Insert # blank characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ - if (es_argc != 1) return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1 - es_argv[0]; - Rect.Bottom = Info.dwCursorPosition.Y; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'k': // ESC[#k - case 'A': // ESC[#A Moves cursor up # lines - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[A == ESC[1A - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) Pos.Y = 0; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'e': // ESC[#e - case 'B': // ESC[#B Moves cursor down # lines - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[B == ESC[1B - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'a': // ESC[#a - case 'C': // ESC[#C Moves cursor forward # spaces - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[C == ESC[1C - if (es_argc != 1) return; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'j': // ESC[#j - case 'D': // ESC[#D Moves cursor back # spaces - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[D == ESC[1D - if (es_argc != 1) return; - Pos.X = Info.dwCursorPosition.X - es_argv[0]; - if (Pos.X < 0) Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'E': // ESC[#E Moves cursor down # lines, column 1. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[E == ESC[1E - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'F': // ESC[#F Moves cursor up # lines, column 1. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[F == ESC[1F - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) Pos.Y = 0; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case '`': // ESC[#` - case 'G': // ESC[#G Moves cursor column # in current row. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[G == ESC[1G - if (es_argc != 1) return; - Pos.X = es_argv[0] - 1; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - if (Pos.X < 0) Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'd': // ESC[#d Moves cursor row #, current column. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[d == ESC[1d - if (es_argc != 1) return; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'f': // ESC[#;#f - case 'H': // ESC[#;#H Moves cursor to line #, column # - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H - if (es_argc == 1) - es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H - if (es_argc > 2) return; - Pos.X = es_argv[1] - 1; - if (Pos.X < 0) Pos.X = 0; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 's': // ESC[s Saves cursor position for recall later - if (es_argc != 0) return; - SavePos = Info.dwCursorPosition; - return; - - case 'u': // ESC[u Return to saved cursor position - if (es_argc != 0) return; - SetConsoleCursorPosition(hConOut, SavePos); - return; - - case 'n': // ESC[#n Device status report - if (es_argc != 1) return; // ESC[n == ESC[0n -> ignored - switch (es_argv[0]) - { - case 5: // ESC[5n Report status - SendSequence(L"\33[0n"); // "OK" - return; - - case 6: // ESC[6n Report cursor position - { - TCHAR buf[32]; - wsprintf(buf, L"\33[%d;%dR", Info.dwCursorPosition.Y + 1, - Info.dwCursorPosition.X + 1); - SendSequence(buf); - } - return; - - default: - return; - } - - case 't': // ESC[#t Window manipulation - if (es_argc != 1) return; - if (es_argv[0] == 21) // ESC[21t Report xterm window's title - { - TCHAR buf[MAX_PATH * 2]; - DWORD len = GetConsoleTitle(buf + 3, lenof(buf) - 3 - 2); - // Too bad if it's too big or fails. - buf[0] = ESC; - buf[1] = ']'; - buf[2] = 'l'; - buf[3 + len] = ESC; - buf[3 + len + 1] = '\\'; - buf[3 + len + 2] = '\0'; - SendSequence(buf); - } - return; - - default: - return; - } - } - else // (prefix == ']') - { - // Ignore any \e]? or \e]> sequences. - if (prefix2 != 0) - return; - - if (es_argc == 1 && es_argv[0] == 0) // ESC]0;titleST - { - SetConsoleTitle(Pt_arg); - } - } -} - -//----------------------------------------------------------------------------- -// ParseAndPrintANSIString(hDev, lpBuffer, nNumberOfBytesToWrite) -// Parses the string lpBuffer, interprets the escapes sequences and prints the -// characters in the device hDev (console). -// The lexer is a three states automata. -// If the number of arguments es_argc > MAX_ARG, only the MAX_ARG-1 firsts and -// the last arguments are processed (no es_argv[] overflow). -//----------------------------------------------------------------------------- - -inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten) -{ - DWORD i; - LPCSTR s; - - if (hDev != hConOut) // reinit if device has changed - { - hConOut = hDev; - state = 1; - shifted = FALSE; - } - for (i = nNumberOfBytesToWrite, s = (LPCSTR)lpBuffer; i > 0; i--, s++) - { - if (state == 1) - { - if (*s == ESC) state = 2; - else if (*s == SO) shifted = TRUE; - else if (*s == SI) shifted = FALSE; - else PushBuffer(*s); - } - else if (state == 2) - { - if (*s == ESC); // \e\e...\e == \e - else if ((*s == '[') || (*s == ']')) - { - FlushBuffer(); - prefix = *s; - prefix2 = 0; - state = 3; - Pt_len = 0; - *Pt_arg = '\0'; - } - else if (*s == ')' || *s == '(') state = 6; - else state = 1; - } - else if (state == 3) - { - if (is_digit(*s)) - { - es_argc = 0; - es_argv[0] = *s - '0'; - state = 4; - } - else if (*s == ';') - { - es_argc = 1; - es_argv[0] = 0; - es_argv[1] = 0; - state = 4; - } - else if (*s == '?' || *s == '>') - { - prefix2 = *s; - } - else - { - es_argc = 0; - suffix = *s; - InterpretEscSeq(); - state = 1; - } - } - else if (state == 4) - { - if (is_digit(*s)) - { - es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0'); - } - else if (*s == ';') - { - if (es_argc < MAX_ARG - 1) es_argc++; - es_argv[es_argc] = 0; - if (prefix == ']') - state = 5; - } - else - { - es_argc++; - suffix = *s; - InterpretEscSeq(); - state = 1; - } - } - else if (state == 5) - { - if (*s == BEL) - { - Pt_arg[Pt_len] = '\0'; - InterpretEscSeq(); - state = 1; - } - else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len - 1] == ESC) - { - Pt_arg[--Pt_len] = '\0'; - InterpretEscSeq(); - state = 1; - } - else if (Pt_len < lenof(Pt_arg) - 1) - Pt_arg[Pt_len++] = *s; - } - else if (state == 6) - { - // Ignore it (ESC ) 0 is implicit; nothing else is supported). - state = 1; - } - } - FlushBuffer(); - if (lpNumberOfBytesWritten != NULL) - *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; - return (i == 0); -} - -} // namespace ansi - -HANDLE hOut; -HANDLE hIn; -DWORD consolemode; - -inline int win32read(char *c) { - - DWORD foo; - INPUT_RECORD b; - KEY_EVENT_RECORD e; - BOOL altgr; - - while (1) { - if (!ReadConsoleInput(hIn, &b, 1, &foo)) return 0; - if (!foo) return 0; - - if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { - - e = b.Event.KeyEvent; - *c = b.Event.KeyEvent.uChar.AsciiChar; - - altgr = e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); - - if (e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) && !altgr) { - - /* Ctrl+Key */ - switch (*c) { - case 'D': - *c = 4; - return 1; - case 'C': - *c = 3; - return 1; - case 'H': - *c = 8; - return 1; - case 'T': - *c = 20; - return 1; - case 'B': /* ctrl-b, left_arrow */ - *c = 2; - return 1; - case 'F': /* ctrl-f right_arrow*/ - *c = 6; - return 1; - case 'P': /* ctrl-p up_arrow*/ - *c = 16; - return 1; - case 'N': /* ctrl-n down_arrow*/ - *c = 14; - return 1; - case 'U': /* Ctrl+u, delete the whole line. */ - *c = 21; - return 1; - case 'K': /* Ctrl+k, delete from current to end of line. */ - *c = 11; - return 1; - case 'A': /* Ctrl+a, go to the start of the line */ - *c = 1; - return 1; - case 'E': /* ctrl+e, go to the end of the line */ - *c = 5; - return 1; - } - - /* Other Ctrl+KEYs ignored */ - } else { - - switch (e.wVirtualKeyCode) { - - case VK_ESCAPE: /* ignore - send ctrl-c, will return -1 */ - *c = 3; - return 1; - case VK_RETURN: /* enter */ - *c = 13; - return 1; - case VK_LEFT: /* left */ - *c = 2; - return 1; - case VK_RIGHT: /* right */ - *c = 6; - return 1; - case VK_UP: /* up */ - *c = 16; - return 1; - case VK_DOWN: /* down */ - *c = 14; - return 1; - case VK_HOME: - *c = 1; - return 1; - case VK_END: - *c = 5; - return 1; - case VK_BACK: - *c = 8; - return 1; - case VK_DELETE: - *c = 127; - return 1; - default: - if (*c) return 1; - } - } - } - } - - return -1; /* Makes compiler happy */ -} - -inline int win32_write(int fd, const void *buffer, unsigned int count) { - if (fd == _fileno(stdout)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_OUTPUT_HANDLE), buffer, (DWORD)count, &bytesWritten)) { - return (int)bytesWritten; - } else { - errno = GetLastError(); - return 0; - } - } else if (fd == _fileno(stderr)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_ERROR_HANDLE), buffer, (DWORD)count, &bytesWritten)) { - return (int)bytesWritten; - } else { - errno = GetLastError(); - return 0; - } - } else { - return _write(fd, buffer, count); - } -} -#endif // _WIN32 - -#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 -#define LINENOISE_MAX_LINE 4096 -static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; -static CompletionCallback completionCallback; - -#ifndef _WIN32 -static struct termios orig_termios; /* In order to restore at exit.*/ -#endif -static bool rawmode = false; /* For atexit() function to check if restore is needed*/ -static bool mlmode = false; /* Multi line mode. Default is single line. */ -static bool atexit_registered = false; /* Register atexit just 1 time. */ -static size_t history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; -static std::vector history; - -/* The linenoiseState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ -struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - size_t buflen; /* Edited line buffer size. */ - std::string prompt; /* Prompt to display. */ - size_t pos; /* Current cursor position. */ - size_t oldpos; /* Previous refresh cursor position. */ - size_t len; /* Current edited line length. */ - size_t cols; /* Number of columns in terminal. */ - size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ -}; - -enum KEY_ACTION { - KEY_NULL = 0, /* NULL */ - CTRL_A = 1, /* Ctrl+a */ - CTRL_B = 2, /* Ctrl-b */ - CTRL_C = 3, /* Ctrl-c */ - CTRL_D = 4, /* Ctrl-d */ - CTRL_E = 5, /* Ctrl-e */ - CTRL_F = 6, /* Ctrl-f */ - CTRL_H = 8, /* Ctrl-h */ - TAB = 9, /* Tab */ - CTRL_K = 11, /* Ctrl+k */ - CTRL_L = 12, /* Ctrl+l */ - ENTER = 13, /* Enter */ - CTRL_N = 14, /* Ctrl-n */ - CTRL_P = 16, /* Ctrl-p */ - CTRL_T = 20, /* Ctrl-t */ - CTRL_U = 21, /* Ctrl+u */ - CTRL_W = 23, /* Ctrl+w */ - ESC = 27, /* Escape */ - BACKSPACE = 127 /* Backspace */ -}; - -void linenoiseAtExit(void); -bool AddHistory(const char *line); -void refreshLine(struct linenoiseState *l); - -/* ======================= Low level terminal handling ====================== */ - -/* Set if to use or not the multi line mode. */ -inline void SetMultiLine(bool ml) { - mlmode = ml; -} - -/* Return true if the terminal name is in the list of terminals we know are - * not able to understand basic escape sequences. */ -inline bool isUnsupportedTerm(void) { -#ifndef _WIN32 - char *term = getenv("TERM"); - int j; - - if (term == NULL) return false; - for (j = 0; unsupported_term[j]; j++) - if (!strcasecmp(term,unsupported_term[j])) return true; -#endif - return false; -} - -/* Raw mode: 1960 magic shit. */ -inline bool enableRawMode(int fd) { -#ifndef _WIN32 - struct termios raw; - - if (!isatty(STDIN_FILENO)) goto fatal; - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = true; - } - if (tcgetattr(fd,&orig_termios) == -1) goto fatal; - - raw = orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - choing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. - * We want read to return every single byte, without timeout. */ - raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ - - /* put terminal in raw mode after flushing */ - if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; - rawmode = true; -#else - if (!atexit_registered) { - /* Init windows console handles only once */ - hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut==INVALID_HANDLE_VALUE) goto fatal; - - if (!GetConsoleMode(hOut, &consolemode)) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - }; - - hIn = GetStdHandle(STD_INPUT_HANDLE); - if (hIn == INVALID_HANDLE_VALUE) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - } - - GetConsoleMode(hIn, &consolemode); - SetConsoleMode(hIn, ENABLE_PROCESSED_INPUT); - - /* Cleanup them at exit */ - atexit(linenoiseAtExit); - atexit_registered = true; - } - - rawmode = true; -#endif - return true; - -fatal: - errno = ENOTTY; - return false; -} - -inline void disableRawMode(int fd) { -#ifdef _WIN32 - rawmode = false; -#else - /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) - rawmode = false; -#endif -} - -/* Use the ESC [6n escape sequence to query the horizontal cursor position - * and return it. On error -1 is returned, on success the position of the - * cursor. */ -inline int getCursorPosition(int ifd, int ofd) { - char buf[32]; - int cols, rows; - unsigned int i = 0; - - /* Report cursor location */ - if (write(ofd, "\x1b[6n", 4) != 4) return -1; - - /* Read the response: ESC [ rows ; cols R */ - while (i < sizeof(buf)-1) { - if (read(ifd,buf+i,1) != 1) break; - if (buf[i] == 'R') break; - i++; - } - buf[i] = '\0'; - - /* Parse it. */ - if (buf[0] != ESC || buf[1] != '[') return -1; -#ifdef _WIN32 - if (sscanf_s(buf+2,"%d;%d",&rows,&cols) != 2) return -1; -#else - if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2) return -1; -#endif - return cols; -} - -/* Try to get the number of columns in the current terminal, or assume 80 - * if it fails. */ -inline int getColumns(int ifd, int ofd) { -#ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO b; - - if (!GetConsoleScreenBufferInfo(hOut, &b)) return 80; - return b.srWindow.Right - b.srWindow.Left; -#else - struct winsize ws; - - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - /* ioctl() failed. Try to query the terminal itself. */ - int start, cols; - - /* Get the initial position so we can restore it later. */ - start = getCursorPosition(ifd,ofd); - if (start == -1) goto failed; - - /* Go to right margin and get position. */ - if (write(ofd,"\x1b[999C",6) != 6) goto failed; - cols = getCursorPosition(ifd,ofd); - if (cols == -1) goto failed; - - /* Restore position. */ - if (cols > start) { - char seq[32]; - snprintf(seq,32,"\x1b[%dD",cols-start); - if (write(ofd,seq,strlen(seq)) == -1) { - /* Can't recover... */ - } - } - return cols; - } else { - return ws.ws_col; - } - -failed: - return 80; -#endif -} - -/* Clear the screen. Used to handle ctrl+l */ -inline void linenoiseClearScreen(void) { - if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { - /* nothing to do, just to avoid warning. */ - } -} - -/* Beep, used for completion when there is nothing to complete or when all - * the choices were already shown. */ -inline void linenoiseBeep(void) { - fprintf(stderr, "\x7"); - fflush(stderr); -} - -/* ============================== Completion ================================ */ - -/* This is an helper function for linenoiseEdit() and is called when the - * user types the key in order to complete the string currently in the - * input. - * - * The state of the editing is encapsulated into the pointed linenoiseState - * structure as described in the structure definition. */ -inline int completeLine(struct linenoiseState *ls) { - std::vector lc; - int nread, nwritten; - char c = 0; - - completionCallback(ls->buf,lc); - if (lc.empty()) { - linenoiseBeep(); - } else { - size_t stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.size()) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = lc[i].size(); - ls->buf = &lc[i][0]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - nread = read(ls->ifd,&c,1); - if (nread <= 0) { - return -1; - } - - switch(c) { - case 9: /* tab */ - i = (i+1) % (lc.size()+1); - if (i == lc.size()) linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < lc.size()) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.size()) { -#ifdef _WIN32 - nwritten = _snprintf_s(ls->buf, ls->buflen, _TRUNCATE,"%s", &lc[i][0]); -#else - nwritten = snprintf(ls->buf, ls->buflen, "%s", &lc[i][0]); -#endif - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } - } - } - - return c; /* Return last read character */ -} - -/* Register a callback function to be called for tab-completion. */ -void SetCompletionCallback(CompletionCallback fn) { - completionCallback = fn; -} - -/* =========================== Line editing ================================= */ - -/* Single line low level line refresh. - * - * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -inline void refreshSingleLine(struct linenoiseState *l) { - char seq[64]; - size_t plen = l->prompt.length(); - int fd = l->ofd; - char *buf = l->buf; - size_t len = l->len; - size_t pos = l->pos; - std::string ab; - - while((plen+pos) >= l->cols) { - buf++; - len--; - pos--; - } - while (plen+len > l->cols) { - len--; - } - - /* Cursor to left edge */ -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\r"); -#else - snprintf(seq, 64, "\r"); -#endif - ab += seq; - /* Write the prompt and the current buffer content */ - ab += l->prompt; - ab.append(buf, len); - /* Erase to right */ -#ifdef _WIN32 - _snprintf_s(seq,64,_TRUNCATE,"\x1b[0K"); -#else - snprintf(seq, 64, "\x1b[0K"); -#endif - ab += seq; - /* Move cursor to original position. */ -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\r\x1b[%dC", (int)(pos + plen)); -#else - snprintf(seq, 64, "\r\x1b[%dC", (int)(pos + plen)); -#endif - ab += seq; - if (write(fd,ab.c_str(),ab.length()) == -1) {} /* Can't recover from write error. */ -} - -/* Multi line low level line refresh. - * - * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -inline void refreshMultiLine(struct linenoiseState *l) { - char seq[64]; - auto plen = l->prompt.length(); - int rows = (int)((plen+l->len+l->cols-1)/l->cols); /* rows used by current buf. */ - int rpos = (int)((plen+l->oldpos+l->cols)/l->cols); /* cursor relative row. */ - int rpos2; /* rpos after refresh. */ - int col; /* colum position, zero-based. */ - int old_rows = (int)l->maxrows; - int fd = l->ofd, j; - std::string ab; - - /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) l->maxrows = rows; - - /* First step: clear all the lines used before. To do so start by - * going to the last row. */ - if (old_rows-rpos > 0) { -#ifdef _WIN32 - _snprintf_s(seq,64,_TRUNCATE,"\x1b[%dB", old_rows-rpos); -#else - snprintf(seq, 64, "\x1b[%dB", old_rows - rpos); -#endif - ab += seq; - } - - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows-1; j++) { -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\r\x1b[0K\x1b[1A"); -#else - snprintf(seq, 64, "\r\x1b[0K\x1b[1A"); -#endif - ab += seq; - } - - /* Clean the top line. */ -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\r\x1b[0K"); -#else - snprintf(seq, 64, "\r\x1b[0K"); -#endif - ab += seq; - - /* Write the prompt and the current buffer content */ - ab += l->prompt; - ab.append(l->buf, l->len); - - /* If we are at the very end of the screen with our prompt, we need to - * emit a newline and move the prompt to the first column. */ - if (l->pos && - l->pos == l->len && - (l->pos+plen) % l->cols == 0) - { - ab += "\n"; -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\r"); -#else - snprintf(seq, 64, "\r"); -#endif - ab += seq; - rows++; - if (rows > (int)l->maxrows) l->maxrows = rows; - } - - /* Move cursor to right position. */ - rpos2 = (int)((plen+l->pos+l->cols)/l->cols); /* current cursor relative row. */ - - /* Go up till we reach the expected positon. */ - if (rows-rpos2 > 0) { -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\x1b[%dA", rows - rpos2); -#else - snprintf(seq, 64, "\x1b[%dA", rows - rpos2); -#endif - ab += seq; - } - - /* Set column. */ - col = (plen+(int)l->pos) % (int)l->cols; - if (col) -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\r\x1b[%dC", col); -#else - snprintf(seq, 64, "\r\x1b[%dC", col); -#endif - else -#ifdef _WIN32 - _snprintf_s(seq, 64, _TRUNCATE, "\r"); -#else - snprintf(seq, 64, "\r"); -#endif - ab += seq; - - l->oldpos = l->pos; - - if (write(fd,ab.c_str(),ab.length()) == -1) {} /* Can't recover from write error. */ -} - -/* Calls the two low level functions refreshSingleLine() or - * refreshMultiLine() according to the selected mode. */ -inline void refreshLine(struct linenoiseState *l) { - if (mlmode) - refreshMultiLine(l); - else - refreshSingleLine(l); -} - -/* Insert the character 'c' at cursor current position. - * - * On error writing to the terminal -1 is returned, otherwise 0. */ -int linenoiseEditInsert(struct linenoiseState *l, char c) { - if (l->len < l->buflen) { - if (l->len == l->pos) { - l->buf[l->pos] = c; - l->pos++; - l->len++; - l->buf[l->len] = '\0'; - if ((!mlmode && l->prompt.length()+l->len < l->cols) /* || mlmode */) { - /* Avoid a full update of the line in the - * trivial case. */ - if (write(l->ofd,&c,1) == -1) return -1; - } else { - refreshLine(l); - } - } else { - memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); - l->buf[l->pos] = c; - l->len++; - l->pos++; - l->buf[l->len] = '\0'; - refreshLine(l); - } - } - return 0; -} - -/* Move cursor on the left. */ -void linenoiseEditMoveLeft(struct linenoiseState *l) { - if (l->pos > 0) { - l->pos--; - refreshLine(l); - } -} - -/* Move cursor on the right. */ -void linenoiseEditMoveRight(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos++; - refreshLine(l); - } -} - -/* Move cursor to the start of the line. */ -inline void linenoiseEditMoveHome(struct linenoiseState *l) { - if (l->pos != 0) { - l->pos = 0; - refreshLine(l); - } -} - -/* Move cursor to the end of the line. */ -inline void linenoiseEditMoveEnd(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos = l->len; - refreshLine(l); - } -} - -/* Substitute the currently edited line with the next or previous history - * entry as specified by 'dir'. */ -#define LINENOISE_HISTORY_NEXT 0 -#define LINENOISE_HISTORY_PREV 1 -inline void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { - if (history.size() > 1) { - /* Update the current history entry before to - * overwrite it with the next one. */ - history[history.size() - 1 - l->history_index] = l->buf; - /* Show the new entry */ - l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (l->history_index < 0) { - l->history_index = 0; - return; - } else if (l->history_index >= (int)history.size()) { - l->history_index = history.size()-1; - return; - } - memset(l->buf, 0, l->buflen); -#ifdef _WIN32 - strcpy_s(l->buf, l->buflen, history[history.size() - 1 - l->history_index].c_str()); -#else - strcpy(l->buf, history[history.size() - 1 - l->history_index].c_str()); -#endif - l->len = l->pos = strlen(l->buf); - refreshLine(l); - } -} - -/* Delete the character at the right of the cursor without altering the cursor - * position. Basically this is what happens with the "Delete" keyboard key. */ -inline void linenoiseEditDelete(struct linenoiseState *l) { - if (l->len > 0 && l->pos < l->len) { - memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); - l->len--; - l->buf[l->len] = '\0'; - refreshLine(l); - } -} - -/* Backspace implementation. */ -inline void linenoiseEditBackspace(struct linenoiseState *l) { - if (l->pos > 0 && l->len > 0) { - memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); - l->pos--; - l->len--; - l->buf[l->len] = '\0'; - refreshLine(l); - } -} - -/* Delete the previosu word, maintaining the cursor at the start of the - * current word. */ -inline void linenoiseEditDeletePrevWord(struct linenoiseState *l) { - size_t old_pos = l->pos; - size_t diff; - - while (l->pos > 0 && l->buf[l->pos-1] == ' ') - l->pos--; - while (l->pos > 0 && l->buf[l->pos-1] != ' ') - l->pos--; - diff = old_pos - l->pos; - memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); - l->len -= diff; - refreshLine(l); -} - -/* This function is the core of the line editing capability of linenoise. - * It expects 'fd' to be already in "raw mode" so that every key pressed - * will be returned ASAP to read(). - * - * The resulting string is put into 'buf' when the user type enter, or - * when ctrl+d is typed. - * - * The function returns the length of the current buffer. */ -inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) -{ - struct linenoiseState l; - - /* Populate the linenoise state that we pass to functions implementing - * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.oldpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; - - /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.buflen--; /* Make sure there is always space for the nulterm */ - - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - AddHistory(""); - - if (write(l.ofd,prompt,l.prompt.length()) == -1) return -1; - while(1) { - char c; - int nread; - char seq[3]; - -#ifdef _WIN32 - nread = win32read(&c); -#else - nread = read(l.ifd,&c,1); -#endif - if (nread <= 0) return (int)l.len; - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - c = completeLine(&l); - /* Return on errors */ - if (c < 0) return (int)l.len; - /* Read next character when 0 */ - if (c == 0) continue; - } - - switch(c) { - case ENTER: /* enter */ - history.pop_back(); - if (mlmode) linenoiseEditMoveEnd(&l); - return (int)l.len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history.pop_back(); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - int aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(l.ifd,seq,1) == -1) break; - if (read(l.ifd,seq+1,1) == -1) break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(l.ifd,seq+2,1) == -1) break; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { - switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { - switch(seq[1]) { - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - break; - default: - if (linenoiseEditInsert(&l,c)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; - } - } - return (int)l.len; -} - -/* This function calls the line editing function linenoiseEdit() using - * the STDIN file descriptor set in raw mode. */ -inline std::string linenoiseRaw(const char *prompt) { - std::string line; - - if (!isatty(STDIN_FILENO)) { - /* Not a tty: read from file / pipe. */ - std::getline(std::cin, line); - } else { - /* Interactive editing. */ - if (enableRawMode(STDIN_FILENO) == false) return line; - - char buf[LINENOISE_MAX_LINE]; - auto count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, LINENOISE_MAX_LINE, prompt); - if (count != -1) { - line.assign(buf, count); - } - - disableRawMode(STDIN_FILENO); - printf("\n"); - } - return line; -} - -/* The high level function that is the main API of the linenoise library. - * This function checks if the terminal has basic capabilities, just checking - * for a blacklist of stupid terminals, and later either calls the line - * editing function or uses dummy fgets() so that you will be able to type - * something even in the most desperate of the conditions. */ -inline std::string Readline(const char *prompt) { - if (isUnsupportedTerm()) { - printf("%s",prompt); - fflush(stdout); - std::string line; - std::getline(std::cin, line); - return line; - } else { - return linenoiseRaw(prompt); - } -} - -/* ================================ History ================================= */ - -/* At exit we'll try to fix the terminal to the initial conditions. */ -inline void linenoiseAtExit(void) { - disableRawMode(STDIN_FILENO); -} - -/* This is the API call to add a new entry in the linenoise history. - * It uses a fixed array of char pointers that are shifted (memmoved) - * when the history max length is reached in order to remove the older - * entry and make room for the new one, so it is not exactly suitable for huge - * histories, but will work well for a few hundred of entries. - * - * Using a circular buffer is smarter, but a bit more complex to handle. */ -inline bool AddHistory(const char* line) { - if (history_max_len == 0) return false; - - /* Don't add duplicated lines. */ - if (!history.empty() && history.back() == line) return false; - - /* If we reached the max length, remove the older line. */ - if (history.size() == history_max_len) { - history.erase(history.begin()); - } - history.push_back(line); - - return true; -} - -/* Set the maximum length for the history. This function can be called even - * if there is already some history, the function will make sure to retain - * just the latest 'len' elements if the new history length value is smaller - * than the amount of items already inside the history. */ -inline bool SetHistoryMaxLen(size_t len) { - if (len < 1) return false; - history_max_len = len; - if (len < history.size()) { - history.resize(len); - } - return true; -} - -/* Save the history in the specified file. On success *true* is returned - * otherwise *false* is returned. */ -inline bool SaveHistory(const char* path) { - std::ofstream f(path); // TODO: need 'std::ios::binary'? - if (!f) return false; - for (const auto& h: history) { - f << h << std::endl; - } - return true; -} - -/* Load the history from the specified file. If the file does not exist - * zero is returned and no operation is performed. - * - * If the file exists and the operation succeeded *true* is returned, otherwise - * on error *false* is returned. */ -inline bool LoadHistory(const char* path) { - std::ifstream f(path); - if (!f) return false; - std::string line; - while (std::getline(f, line)) { - AddHistory(line.c_str()); - } - return true; -} - -inline const std::vector& GetHistory() { - return history; -} - -} // namespace linenoise - -#ifdef _WIN32 -#undef snprintf -#undef isatty -#undef write -#undef read -#endif - -#endif /* __LINENOISE_HPP */ diff --git a/language/culebra/main.cc b/language/culebra/main.cc deleted file mode 100644 index e7c49f8..0000000 --- a/language/culebra/main.cc +++ /dev/null @@ -1,316 +0,0 @@ -#include "culebra.h" -#include "linenoise.hpp" -#include -#include -#include -#include - -using namespace peg; -using namespace peg::udl; -using namespace std; - -bool read_file(const char* path, vector& buff) -{ - ifstream ifs(path, ios::in|ios::binary); - if (ifs.fail()) { - return false; - } - - auto size = static_cast(ifs.seekg(0, ios::end).tellg()); - - if (size > 0) { - buff.resize(size); - ifs.seekg(0, ios::beg).read(&buff[0], static_cast(buff.size())); - } - - return true; -} - -struct CommandLineDebugger -{ - void operator()(const Ast& ast, culebra::Environment& env, bool force_to_break) { - if (quit) { - return; - } - - if ((command_ == "n" && env.level <= level_) || - (command_ == "s") || - (command_ == "o" && env.level < level_)) { - force_to_break = true; - } - - if (force_to_break) { - static auto show_initial_usage = true; - if (show_initial_usage) { - show_initial_usage = false; - usage(); - } - - show_lines(ast); - - for (;;) { - cout << endl << "debug> "; - - string s; - std::getline(cin, s); - - istringstream is(s); - is >> command_; - - if (command_ == "h") { - usage(); - } else if (command_ == "l") { - is >> display_lines_; - show_lines(ast); - } else if (command_ == "p") { - string symbol; - is >> symbol; - print(ast, env, symbol); - } else if (command_ == "c") { - break; - } else if (command_ == "n") { - break; - } else if (command_ == "s") { - break; - } else if (command_ == "o") { - break; - } else if (command_ == "q") { - quit = true; - break; - } - } - level_ = env.level;; - } - } - - void show_lines(const Ast& ast) { - prepare_cache(ast.path); - - cout << endl << "Break in " << ast.path << ":" << ast.line << endl; - - auto count = get_line_count(ast.path); - - auto lines_ahead = (size_t)((display_lines_ - .5) / 2); - auto start = (size_t)max((int)ast.line - (int)lines_ahead, 1); - auto end = min(start + display_lines_, count); - - auto needed_digits = to_string(count).length(); - - for (auto l = start; l < end; l++) { - auto s = get_line(ast.path, l); - if (l == ast.line) { - cout << "> "; - } else { - cout << " "; - } - cout << setw(needed_digits) << l << " " << s << endl; - } - } - - shared_ptr find_function_node(const Ast& ast) { - auto node = ast.parent; - while (node->parent && node->tag != "FUNCTION"_) { - node = node->parent; - } - return node; - } - - void enum_identifiers(const Ast& ast, set& references) { - for (auto node: ast.nodes) { - switch (node->tag) { - case "IDENTIFIER"_: - references.insert(node->token); - break; - case "FUNCTION"_: - break; - default: - enum_identifiers(*node, references); - break; - } - } - } - - void print(const Ast& ast, culebra::Environment& env, const string& symbol) { - if (symbol.empty()) { - print_all(ast, env); - } else if (env.has(symbol)) { - cout << symbol << ": " << env.get(symbol).str() << endl; - } else { - cout << "'" << symbol << "'" << "is not undefined." << endl; - } - } - - void print_all(const Ast& ast, culebra::Environment& env) { - auto node = find_function_node(ast); - set references; - enum_identifiers(*node, references); - for (const auto& symbol: references) { - if (env.has(symbol)) { - const auto& val = env.get(symbol); - if (val.type != culebra::Value::Function) { - cout << symbol << ": " << val.str() << endl; - } - } - } - } - - size_t get_line_count(const string& path) { - return sources_[path].size(); - } - - string get_line(const string& path, size_t line) { - const auto& positions = sources_[path]; - auto idx = line - 1; - auto first = idx > 0 ? positions[idx - 1] : 0; - auto last = positions[idx]; - auto size = last - first; - - string s(size, 0); - ifstream ifs(path, ios::in | ios::binary); - ifs.seekg(first, ios::beg).read((char*)s.data(), static_cast(s.size())); - - size_t count = 0; - auto rit = s.rbegin(); - while (rit != s.rend()) { - if (*rit == '\n') { - count++; - } - ++rit; - } - - s = s.substr(0, s.size() - count); - - return s; - } - - void prepare_cache(const string& path) { - auto it = sources_.find(path); - if (it == sources_.end()) { - vector buff; - read_file(path.c_str(), buff); - - auto& positions = sources_[path]; - - auto i = 0u; - for (; i < buff.size(); i++) { - if (buff[i] == '\n') { - positions.push_back(i + 1); - } - } - positions.push_back(i); - } - } - - void usage() { - cout << "Usage: (c)ontinue, (n)ext, (s)tep in, step (o)out, (p)ring, (l)ist, (q)uit" << endl; - } - - bool quit = false; - string command_; - size_t level_ = 0; - size_t display_lines_ = 4; - map> sources_; -}; - -int repl(shared_ptr env, bool print_ast) -{ - for (;;) { - auto line = linenoise::Readline("cul> "); - - if (line == "exit" || line == "quit") { - break; - } - - if (!line.empty()) { - vector msgs; - auto ast = culebra::parse("(repl)", line.data(), line.size(), msgs); - if (ast) { - if (print_ast) { - cout << peg::ast_to_s(ast); - } - - culebra::Value val; - if (interpret(ast, env, val, msgs)) { - cout << val << endl; - linenoise::AddHistory(line.c_str()); - continue; - } - } - - for (const auto& msg : msgs) { - cout << msg << endl;; - } - } - } - - return 0; -} - -int main(int argc, const char** argv) -{ - auto print_ast = false; - auto shell = false; - auto debug = false; - vector path_list; - - int argi = 1; - while (argi < argc) { - auto arg = argv[argi++]; - if (string("--shell") == arg) { - shell = true; - } else if (string("--ast") == arg) { - print_ast = true; - } else if (string("--debug") == arg) { - debug = true; - } else { - path_list.push_back(arg); - } - } - - if (!shell) { - shell = path_list.empty(); - } - - try { - auto env = make_shared(); - setup_built_in_functions(*env); - - for (auto path: path_list) { - vector buff; - if (!read_file(path, buff)) { - cerr << "can't open '" << path << "'." << endl; - return -1; - } - - vector msgs; - auto ast = culebra::parse(path, buff.data(), buff.size(), msgs); - if (ast) { - if (print_ast) { - cout << peg::ast_to_s(ast); - } - - culebra::Value val; - auto dbg = debug ? CommandLineDebugger() : culebra::Debugger(); - if (interpret(ast, env, val, msgs, dbg)) { - return 0; - } - } - - for (const auto& msg : msgs) { - cerr << msg << endl; - } - return -1; - } - - if (shell) { - repl(env, print_ast); - } - } catch (exception& e) { - cerr << e.what() << endl; - return -1; - } - - return 0; -} - -// vim: et ts=4 sw=4 cin cino={1s ff=unix diff --git a/language/culebra/samples/closure.cul b/language/culebra/samples/closure.cul deleted file mode 100644 index bfebe15..0000000 --- a/language/culebra/samples/closure.cul +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Closure test - */ - -make_func = fn (mut x) { - mut n = 100 - fn () { - n = n + 1 - x = x + 1 + n - } -} - -f = make_func(10) - -puts("1: { f() }") -puts("2: { f() }") -puts("3: { f() }") diff --git a/language/culebra/samples/fib.cul b/language/culebra/samples/fib.cul deleted file mode 100644 index c3e12e2..0000000 --- a/language/culebra/samples/fib.cul +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Fibonacci - */ - -fib = fn (x) { - if x < 2 { - x - } else { - self(x - 2) + self(x -1) - } -} - -mut i = 0 -while i < 30 { - puts("{i}: {fib(i)}") - i = i + 1 -} diff --git a/language/culebra/samples/fizzbuzz.cul b/language/culebra/samples/fizzbuzz.cul deleted file mode 100644 index 95c57e0..0000000 --- a/language/culebra/samples/fizzbuzz.cul +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Fizz Buzz - */ - -mut i = 1 -while i < 24 { - if i % 15 == 0 { - puts('FizzBuzz') - } else if i % 5 == 0 { - puts('Buzz') - } else if i % 3 == 0 { - puts('Fizz') - } else { - puts(i) - } - i = i + 1 -} diff --git a/language/culebra/samples/test.cul b/language/culebra/samples/test.cul deleted file mode 100644 index d4d06ea..0000000 --- a/language/culebra/samples/test.cul +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Unit tests - */ - -test_call = fn () { - ret = fn(){[1,fn(){[4,5,6]},3]}()[1]()[1] - assert(ret == 5) -} - -test_return = fn () { - f = fn (x) { - if x % 2 { - return 'odd' - } - 'even' - } - assert(f(3) == 'odd') - assert(f(4) == 'even') - - mut val = 0 - f2 = fn () { - val = 1 - return // comment - val = 2 - } - f2() - assert(val == 1) -} - -test_nil = fn () { - assert(nil == nil) - assert(!(nil != nil)) - - a = nil - assert(a == nil) - assert(!(a != nil)) - assert(!(a <= nil)) - assert(!(a < nil)) - assert(!(a >= nil)) - assert(!(a > nil)) - assert(nil == a) - assert(!(nil != a)) - assert(!(nil <= a)) - assert(!(nil < a)) - assert(!(nil >= a)) - assert(!(nil > a)) -} - -test_closure = fn () { - make_func = fn (mut x) { - mut n = 100 - fn () { - n = n + 1 - x = x + 1 + n - } - } - - f = make_func(10) - f() - f() - ret = f() - - assert(ret == 319) -} - -test_array = fn () { - a = [1,2,3] - assert(a.size() == 3) - - a.push(4) - assert(a.size() == 4) - - b = [] - assert(b.size() == 0) - - c = [1] - assert(c.size() == 1) - - d = [1,2,3](5, 0) - assert(d.size() == 5 && d[-1] == 0) - - e = [1,2,3](2) - assert(e.size() == 3 && e[-1] == 3) - - f = [1,2,3](5) - assert(f.size() == 5 && f[-1] == nil) -} - -g_ = 1 - -test_function = fn () { - a = 1 - make = fn () { - b = 1 - fn (c) { - g_ + a + b + c - } - } - f = make() - assert(f(1) == 4) -} - -test_object = fn () { - n = 1 - o = { - n: 123, - s: 'str', - f1: fn (x) { x + this.n }, - f2: fn (x) { x + n } - } - assert(o.size() == 4) - assert(o.f1(10) == 133) - assert(o.f2(10) == 11) - - a = {} - a.b = 1 - assert(a.a == nil) - assert(a.b == 1) - assert(a.size() == 1) -} - -test_object_factory = fn () { - ctor = fn (init) { - mut n = init - - { - add: fn (x) { - n = n + x - }, - sub: fn (x) { - n = n - x - }, - val: fn () { - n - } - } - } - - calc = ctor(10) - - assert(calc.val() == 10) - assert(calc.add(1) == 11) - assert(calc.sub(1) == 10) -} - -test_class = fn () { - // TODO: support 'prototype' property - Car = { - new: fn(miles_per_run) { - mut total_miles = 0 - - { - run: fn (times) { - total_miles = total_miles + miles_per_run * times - }, - total: fn () { - total_miles - } - } - } - } - - car = Car.new(5) - car.run(1) - car.run(2) - - assert(car.total() == 15) -} - -test_sum = fn () { - mut i = 1 - mut ret = 0 - while i <= 10 { - ret = ret + i - i = i + 1 - } - - assert(ret == 55) -} - -test_fib = fn () { - fib = fn (x) { - if x < 2 { - x - } else { - self(x - 2) + self(x -1) - } - } - - ret = fib(15) - - assert(ret == 610) -} - -test_interpolated_string = fn () { - hello = "Hello" - world = "World!" - ret = "{hello} {world}" - assert(ret == 'Hello World!') -} - -test_lexical_scope = fn () { - a = 0 - { - let a = 1; - assert(a == 1) - } - assert(a == 0) - - mut b = 0 - { - b = 1; - assert(b == 1) - } - assert(b == 1) - - c = 0 - { - let mut c = 0; - c = 1 - assert(c == 1) - } - assert(c == 0) - - obj = { - name: 'object' - } - - assert(obj.name == 'object') -} - -debugger -test_call() -test_return() -test_closure() -test_nil() -test_array() -test_function() -test_object() -test_object_factory() -test_class() -debugger -test_sum() -test_fib() -test_interpolated_string() -test_lexical_scope() - -return // end