Skip to content

Commit

Permalink
Reuse some CLI related code in GUI
Browse files Browse the repository at this point in the history
Instead of just copypasting functions, attempt to reuse them.
  • Loading branch information
RauliL committed Aug 2, 2018
1 parent d9f74c3 commit 232e1b7
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 343 deletions.
2 changes: 2 additions & 0 deletions cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ CONFIGURE_FILE(
ADD_EXECUTABLE(
plorth-cli
ext/linenoise/linenoise.c
src/api.cpp
src/main.cpp
src/repl.cpp
src/utils.cpp
)

TARGET_COMPILE_OPTIONS(
Expand Down
87 changes: 87 additions & 0 deletions cli/src/api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2017-2018, Rauli Laine
* 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.
*/
#include <plorth/context.hpp>

#include "../../libplorth/src/utils.hpp"

namespace plorth
{
namespace cli
{
/**
* .q ( -- )
*
* Exits the interpreter.
*/
static void w_quit(const std::shared_ptr<context>&)
{
std::exit(EXIT_SUCCESS);
}

/**
* .s ( -- )
*
* Displays ten of the top-most values from the data stack.
*/
static void w_stack(const std::shared_ptr<context>& ctx)
{
const auto& runtime = ctx->runtime();
const auto& stack = ctx->data();
const std::size_t size = stack.size();

if (!size)
{
runtime->println(U"Stack is empty.");
return;
}

for (std::size_t i = 0; i < size && i < 10; ++i)
{
const auto& value = stack[size - i - 1];

runtime->print(
to_unistring(static_cast<number::int_type>(size - i)) + U": "
);
runtime->print(value ? value->to_source() : U"null");
runtime->println();
}
}

void initialize_repl_api(const std::shared_ptr<runtime>& runtime)
{
auto& dictionary = runtime->dictionary();

dictionary.insert(runtime->word(
runtime->symbol(U".q"),
runtime->native_quote(w_quit)
));
dictionary.insert(runtime->word(
runtime->symbol(U".s"),
runtime->native_quote(w_stack)
));
}
}
}
202 changes: 10 additions & 192 deletions cli/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

#include <cstring>
#include <fstream>
#include <stack>
#if PLORTH_ENABLE_MODULES
# include <unordered_set>
#endif
Expand All @@ -41,6 +40,7 @@
#endif

#include <linenoise.h>
#include "./utils.hpp"

#if !defined(EX_USAGE)
# define EX_USAGE 64
Expand All @@ -57,17 +57,19 @@ static std::unordered_set<unistring> imported_modules;
#endif

static void scan_arguments(const std::shared_ptr<runtime>&, int, char**);
#if PLORTH_ENABLE_MODULES
static void scan_module_path(const std::shared_ptr<runtime>&);
#endif
static inline bool is_console_interactive();
static void compile_and_run(const std::shared_ptr<context>&,
const std::string&,
const unistring&);
static void console_loop(const std::shared_ptr<context>&);
static void handle_error(const std::shared_ptr<context>&);

void initialize_repl_api(const std::shared_ptr<runtime>&);
namespace plorth
{
namespace cli
{
void repl_loop(const std::shared_ptr<context>&);
}
}

int main(int argc, char** argv)
{
Expand All @@ -76,7 +78,7 @@ int main(int argc, char** argv)
auto context = context::make(runtime);

#if PLORTH_ENABLE_MODULES
scan_module_path(runtime);
plorth::cli::utils::scan_module_path(runtime);
#endif

scan_arguments(runtime, argc, argv);
Expand Down Expand Up @@ -124,7 +126,7 @@ int main(int argc, char** argv)
}
else if (is_console_interactive())
{
console_loop(context);
plorth::cli::repl_loop(context);
} else {
compile_and_run(
context,
Expand Down Expand Up @@ -281,49 +283,6 @@ static void scan_arguments(const std::shared_ptr<class runtime>& runtime,
}
}

#if PLORTH_ENABLE_MODULES
static void scan_module_path(const std::shared_ptr<class runtime>& runtime)
{
#if defined(_WIN32)
static const unichar path_separator = ';';
#else
static const unichar path_separator = ':';
#endif
auto& module_paths = runtime->module_paths();
const char* begin = std::getenv("PLORTHPATH");
const char* end = begin;

if (end)
{
for (; *end; ++end)
{
if (*end != path_separator)
{
continue;
}

if (end - begin > 0)
{
module_paths.push_back(utf8_decode(std::string(begin, end - begin)));
}
begin = end + 1;
}

if (end - begin > 0)
{
module_paths.push_back(utf8_decode(std::string(begin, end - begin)));
}
}

#if defined(PLORTH_RUNTIME_LIBRARY_PATH)
if (module_paths.empty())
{
module_paths.push_back(PLORTH_RUNTIME_LIBRARY_PATH);
}
#endif
}
#endif

static inline bool is_console_interactive()
{
#if defined(HAVE_ISATTY)
Expand Down Expand Up @@ -397,144 +356,3 @@ static void compile_and_run(const std::shared_ptr<context>& ctx,
handle_error(ctx);
}
}

static void count_open_braces(const char* input, std::stack<char>& open_braces)
{
const auto length = std::strlen(input);

for (std::size_t i = 0; i < length; ++i)
{
switch (input[i])
{
case '#':
return;

case '(':
open_braces.push(')');
break;

case '[':
open_braces.push(']');
break;

case '{':
open_braces.push('}');
break;

case ')':
case ']':
case '}':
if (!open_braces.empty() && open_braces.top() == input[i])
{
open_braces.pop();
}
break;

case '"':
while (i < length)
{
if (input[i] == '"')
{
break;
}
else if (input[i] == '\\' && i + 1 < length)
{
i += 2;
} else {
++i;
}
}
}
}
}

static void console_loop(const std::shared_ptr<class context>& context)
{
int line_counter = 0;
unistring source;
std::stack<char> open_braces;
char prompt[BUFSIZ];

initialize_repl_api(context->runtime());

for (;;)
{
char* line;

// First construct the prompt which is shown to the user. It contains text
// "plorth", current line number, size of the execution context and
// visual indication on whether the source code still contains open braces
// or not.
std::snprintf(
prompt,
BUFSIZ,
"plorth:%d:%ld%c ",
++line_counter,
context->size(),
open_braces.empty() ? '>' : '*'
);

// Read line from the user.
if (!(line = linenoise(prompt)))
{
break;
}

// Skip empty lines.
if (!*line)
{
linenoiseFree(line);
continue;
}

// Add the line into history.
linenoiseHistoryAdd(line);

// And attempt to decode the input as UTF-8.
if (!utf8_decode_test(line, source))
{
std::cout << "Unable to decode given input as UTF-8." << std::endl;
linenoiseFree(line);
continue;
}

// Insert new line into the source code so that the line counter
source.append(1, '\n');

// See whether the line contains special characters such as open braces and
// so on.
count_open_braces(line, open_braces);

// We no longer need the original line, so free it.
linenoiseFree(line);

// Do not attempt to compile the source code when it still has unclosed
// braces.
if (!open_braces.empty())
{
continue;
}

// Attempt to compile the source code into a quote and execute it unless
// syntax errors were encountered.
if (auto script = context->compile(source, U"<repl>", line_counter))
{
script->call(context);
}

// Clear the source code buffer so that we can use it again.
source.clear();

// If the execution context has any error present, display it. Also reset
// the error status so that the execution context can be reused.
if (const auto& error = context->error())
{
if (auto position = error->position())
{
std::cout << *position << ':';
}
std::cout << error << std::endl;
context->clear_error();
}
}
}
Loading

0 comments on commit 232e1b7

Please sign in to comment.