commit
56ab8c5c84
22
res/layouts/console.xml
Normal file
22
res/layouts/console.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<container color='#00000080' size='400' size-func="unpack(gui.get_viewport())">
|
||||
<container size-func="gui.get_viewport()[1],gui.get_viewport()[2]-40">
|
||||
<textbox
|
||||
id='log'
|
||||
color='0'
|
||||
autoresize='true'
|
||||
margin='0'
|
||||
editable='false'
|
||||
multiline='true'
|
||||
size-func="gui.get_viewport()[1],40"
|
||||
gravity="bottom-left"
|
||||
></textbox>
|
||||
</container>
|
||||
<textbox id='prompt'
|
||||
consumer='submit'
|
||||
margin='0'
|
||||
gravity='bottom-left'
|
||||
size-func="gui.get_viewport()[1],40"
|
||||
onup="on_history_up()"
|
||||
ondown="on_history_down()">
|
||||
</textbox>
|
||||
</container>
|
||||
53
res/layouts/console.xml.lua
Normal file
53
res/layouts/console.xml.lua
Normal file
@ -0,0 +1,53 @@
|
||||
history = session.get_entry("commands_history")
|
||||
history_pointer = #history
|
||||
|
||||
function setup_variables()
|
||||
local x,y,z = player.get_pos(hud.get_player())
|
||||
console.set('pos.x', x)
|
||||
console.set('pos.y', y)
|
||||
console.set('pos.z', z)
|
||||
end
|
||||
|
||||
function on_history_up()
|
||||
if history_pointer == 0 then
|
||||
return
|
||||
end
|
||||
document.prompt.text = history[history_pointer]
|
||||
document.prompt.caret = -1
|
||||
history_pointer = history_pointer - 1
|
||||
end
|
||||
|
||||
function on_history_down()
|
||||
if history_pointer == #history-1 then
|
||||
return
|
||||
end
|
||||
history_pointer = history_pointer + 1
|
||||
document.prompt.text = history[history_pointer + 1]
|
||||
document.prompt.caret = -1
|
||||
end
|
||||
|
||||
function add_to_history(text)
|
||||
table.insert(history, text)
|
||||
history_pointer = #history
|
||||
end
|
||||
|
||||
function submit(text)
|
||||
add_to_history(text)
|
||||
setup_variables()
|
||||
|
||||
local status, result = pcall(function() return console.execute(text) end)
|
||||
if result ~= nil then
|
||||
local prevtext = document.log.text
|
||||
if #prevtext == 0 then
|
||||
document.log:paste(tostring(result))
|
||||
else
|
||||
document.log:paste('\n'..tostring(result))
|
||||
end
|
||||
end
|
||||
document.prompt.text = ""
|
||||
document.prompt.focused = true
|
||||
end
|
||||
|
||||
function on_open()
|
||||
document.prompt.focused = true
|
||||
end
|
||||
89
res/scripts/stdcmd.lua
Normal file
89
res/scripts/stdcmd.lua
Normal file
@ -0,0 +1,89 @@
|
||||
local SEPARATOR = "________________"
|
||||
SEPARATOR = SEPARATOR..SEPARATOR..SEPARATOR
|
||||
|
||||
function build_scheme(command)
|
||||
local str = command.name.." "
|
||||
for i,arg in ipairs(command.args) do
|
||||
if arg.optional then
|
||||
str = str.."["..arg.name.."] "
|
||||
else
|
||||
str = str.."<"..arg.name.."> "
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
console.add_command(
|
||||
"help name:str=''",
|
||||
"Show help infomation for the specified command",
|
||||
function (args, kwargs)
|
||||
local name = args[1]
|
||||
if #name == 0 then
|
||||
local commands = console.get_commands_list()
|
||||
table.sort(commands)
|
||||
local str = "Available commands:"
|
||||
for i,k in ipairs(commands) do
|
||||
str = str.."\n "..build_scheme(console.get_command_info(k))
|
||||
end
|
||||
return str.."\nuse 'help <command>'"
|
||||
end
|
||||
local command = console.get_command_info(name)
|
||||
if command == nil then
|
||||
return string.format("command %q not found", name)
|
||||
end
|
||||
local where = ""
|
||||
local str = SEPARATOR.."\n"..command.description.."\n"..name.." "
|
||||
for i,arg in ipairs(command.args) do
|
||||
where = where.."\n "..arg.name.." - "..arg.type
|
||||
if arg.optional then
|
||||
str = str.."["..arg.name.."] "
|
||||
where = where.." (optional)"
|
||||
else
|
||||
str = str.."<"..arg.name.."> "
|
||||
end
|
||||
end
|
||||
if #command.args then
|
||||
str = str.."\nwhere"..where
|
||||
end
|
||||
|
||||
return str.."\n"..SEPARATOR
|
||||
end
|
||||
)
|
||||
|
||||
console.add_command(
|
||||
"obj.tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z",
|
||||
"Teleport object",
|
||||
function (args, kwargs)
|
||||
player.set_pos(unpack(args))
|
||||
end
|
||||
)
|
||||
console.add_command(
|
||||
"echo value:str",
|
||||
"Print value to the console",
|
||||
function (args, kwargs)
|
||||
return args[1]
|
||||
end
|
||||
)
|
||||
console.add_command(
|
||||
"time.set value:num",
|
||||
"Set day time [0..1] where 0 is midnight, 0.5 is noon",
|
||||
function (args, kwargs)
|
||||
return world.set_day_time(args[1])
|
||||
end
|
||||
)
|
||||
console.add_command(
|
||||
"blocks.fill id:str x:num~pos.x y:num~pos.y z:num~pos.z w:int h:int d:int",
|
||||
"Fill specified zone with blocks",
|
||||
function (args, kwargs)
|
||||
local name, x, y, z, w, h, d = unpack(args)
|
||||
local id = block.index(name)
|
||||
for ly=0,h-1 do
|
||||
for lz=0,d-1 do
|
||||
for lx=0,w-1 do
|
||||
block.set(x+lx, y+ly, z+lz, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
return tostring(w*h*d).." blocks set"
|
||||
end
|
||||
)
|
||||
@ -9,6 +9,7 @@ world.generators.default=Default
|
||||
world.generators.flat=Flat
|
||||
|
||||
# Bindings
|
||||
devtools.console=Console
|
||||
movement.forward=Forward
|
||||
movement.back=Back
|
||||
movement.left=Left
|
||||
|
||||
@ -56,6 +56,7 @@ settings.UI Sounds=Звуки Интерфейса
|
||||
settings.V-Sync=Вертикальная Синхронизация
|
||||
|
||||
# Управление
|
||||
devtools.console=Консоль
|
||||
movement.forward=Вперёд
|
||||
movement.back=Назад
|
||||
movement.left=Влево
|
||||
|
||||
@ -154,8 +154,13 @@ void BasicParser::expectNewLine() {
|
||||
}
|
||||
}
|
||||
|
||||
void BasicParser::goBack() {
|
||||
if (pos) pos--;
|
||||
void BasicParser::goBack(size_t count) {
|
||||
if (pos < count) {
|
||||
throw std::runtime_error("pos < jump");
|
||||
}
|
||||
if (pos) {
|
||||
pos -= count;
|
||||
}
|
||||
}
|
||||
|
||||
char BasicParser::peek() {
|
||||
@ -166,6 +171,13 @@ char BasicParser::peek() {
|
||||
return source[pos];
|
||||
}
|
||||
|
||||
char BasicParser::peekNoJump() {
|
||||
if (pos >= source.length()) {
|
||||
throw error("unexpected end");
|
||||
}
|
||||
return source[pos];
|
||||
}
|
||||
|
||||
std::string_view BasicParser::readUntil(char c) {
|
||||
int start = pos;
|
||||
while (hasNext() && source[pos] != c) {
|
||||
|
||||
@ -31,11 +31,11 @@ inline bool is_whitespace(int c) {
|
||||
}
|
||||
|
||||
inline bool is_identifier_start(int c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '.';
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '.';
|
||||
}
|
||||
|
||||
inline bool is_identifier_part(int c) {
|
||||
return is_identifier_start(c) || is_digit(c);
|
||||
return is_identifier_start(c) || is_digit(c) || c == '-';
|
||||
}
|
||||
|
||||
inline int hexchar2int(int c) {
|
||||
@ -86,7 +86,7 @@ protected:
|
||||
void expect(const std::string& substring);
|
||||
bool isNext(const std::string& substring);
|
||||
void expectNewLine();
|
||||
void goBack();
|
||||
void goBack(size_t count=1);
|
||||
|
||||
int64_t parseSimpleInt(int base);
|
||||
dynamic::Value parseNumber(int sign);
|
||||
@ -99,6 +99,7 @@ public:
|
||||
std::string parseName();
|
||||
bool hasNext();
|
||||
char peek();
|
||||
char peekNoJump();
|
||||
char nextChar();
|
||||
|
||||
BasicParser(std::string_view file, std::string_view source);
|
||||
|
||||
@ -36,14 +36,6 @@ inline void newline(
|
||||
}
|
||||
}
|
||||
|
||||
void stringify(
|
||||
const Value& value,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice
|
||||
);
|
||||
|
||||
void stringifyObj(
|
||||
const Map* obj,
|
||||
std::stringstream& ss,
|
||||
@ -91,6 +83,8 @@ void stringifyValue(
|
||||
ss << *num;
|
||||
} else if (auto str = std::get_if<std::string>(&value)) {
|
||||
ss << util::escape(*str);
|
||||
} else {
|
||||
ss << "null";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ void corecontent::setup(ContentBuilder* builder) {
|
||||
}
|
||||
|
||||
void corecontent::setup_bindings() {
|
||||
Events::bind(BIND_DEVTOOLS_CONSOLE, inputtype::keyboard, keycode::GRAVE_ACCENT);
|
||||
Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W);
|
||||
Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S);
|
||||
Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D);
|
||||
|
||||
@ -9,6 +9,7 @@ inline const std::string CORE_AIR = "core:air";
|
||||
inline const std::string TEXTURE_NOTFOUND = "notfound";
|
||||
|
||||
// built-in bindings
|
||||
inline const std::string BIND_DEVTOOLS_CONSOLE = "devtools.console";
|
||||
inline const std::string BIND_MOVE_FORWARD = "movement.forward";
|
||||
inline const std::string BIND_MOVE_BACK = "movement.back";
|
||||
inline const std::string BIND_MOVE_LEFT = "movement.left";
|
||||
|
||||
@ -242,6 +242,20 @@ size_t Map::size() const {
|
||||
return values.size();
|
||||
}
|
||||
|
||||
static const std::string TYPE_NAMES[] {
|
||||
"none",
|
||||
"map",
|
||||
"list",
|
||||
"string",
|
||||
"number",
|
||||
"bool",
|
||||
"integer",
|
||||
};
|
||||
|
||||
const std::string& dynamic::type_name(const Value& value) {
|
||||
return TYPE_NAMES[value.index()];
|
||||
}
|
||||
|
||||
List_sptr dynamic::create_list(std::initializer_list<Value> values) {
|
||||
return std::make_shared<List>(values);
|
||||
}
|
||||
@ -249,3 +263,19 @@ List_sptr dynamic::create_list(std::initializer_list<Value> values) {
|
||||
Map_sptr dynamic::create_map(std::initializer_list<std::pair<const std::string, Value>> entries) {
|
||||
return std::make_shared<Map>(entries);
|
||||
}
|
||||
|
||||
number_t dynamic::get_number(const Value& value) {
|
||||
if (auto num = std::get_if<number_t>(&value)) {
|
||||
return *num;
|
||||
} else if (auto num = std::get_if<integer_t>(&value)) {
|
||||
return *num;
|
||||
}
|
||||
throw std::runtime_error("cannot cast "+type_name(value)+" to number");
|
||||
}
|
||||
|
||||
integer_t dynamic::get_integer(const Value& value) {
|
||||
if (auto num = std::get_if<integer_t>(&value)) {
|
||||
return *num;
|
||||
}
|
||||
throw std::runtime_error("cannot cast "+type_name(value)+" to integer");
|
||||
}
|
||||
|
||||
@ -35,8 +35,16 @@ namespace dynamic {
|
||||
integer_t
|
||||
>;
|
||||
|
||||
const std::string& type_name(const Value& value);
|
||||
List_sptr create_list(std::initializer_list<Value> values={});
|
||||
Map_sptr create_map(std::initializer_list<std::pair<const std::string, Value>> entries={});
|
||||
number_t get_number(const Value& value);
|
||||
integer_t get_integer(const Value& value);
|
||||
|
||||
inline bool is_numeric(const Value& value) {
|
||||
return std::holds_alternative<number_t>(value) ||
|
||||
std::holds_alternative<integer_t>(value);
|
||||
}
|
||||
|
||||
class List {
|
||||
public:
|
||||
@ -125,6 +133,24 @@ namespace dynamic {
|
||||
Map& put(std::string key, std::unique_ptr<List> value) {
|
||||
return put(key, List_sptr(value.release()));
|
||||
}
|
||||
Map& put(std::string key, int value) {
|
||||
return put(key, Value(static_cast<integer_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, unsigned int value) {
|
||||
return put(key, Value(static_cast<integer_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, int64_t value) {
|
||||
return put(key, Value(static_cast<integer_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, float value) {
|
||||
return put(key, Value(static_cast<number_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, double value) {
|
||||
return put(key, Value(static_cast<number_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, bool value) {
|
||||
return put(key, Value(static_cast<bool>(value)));
|
||||
}
|
||||
Map& put(std::string key, const Value& value);
|
||||
|
||||
void remove(const std::string& key);
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "logic/EngineController.hpp"
|
||||
#include "logic/CommandsInterpreter.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
#include "util/platform.hpp"
|
||||
@ -59,7 +60,8 @@ inline void create_channel(Engine* engine, std::string name, NumberSetting& sett
|
||||
}
|
||||
|
||||
Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths)
|
||||
: settings(settings), settingsHandler(settingsHandler), paths(paths)
|
||||
: settings(settings), settingsHandler(settingsHandler), paths(paths),
|
||||
interpreter(std::make_unique<cmd::CommandsInterpreter>())
|
||||
{
|
||||
corecontent::setup_bindings();
|
||||
loadSettings();
|
||||
@ -198,6 +200,7 @@ Engine::~Engine() {
|
||||
}
|
||||
content.reset();
|
||||
assets.reset();
|
||||
interpreter.reset();
|
||||
gui.reset();
|
||||
logger.info() << "gui finished";
|
||||
audio::close();
|
||||
@ -211,6 +214,10 @@ EngineController* Engine::getController() {
|
||||
return controller.get();
|
||||
}
|
||||
|
||||
cmd::CommandsInterpreter* Engine::getCommandsInterpreter() {
|
||||
return interpreter.get();
|
||||
}
|
||||
|
||||
PacksManager Engine::createPacksManager(const fs::path& worldFolder) {
|
||||
PacksManager manager;
|
||||
manager.setSources({
|
||||
|
||||
@ -34,6 +34,10 @@ namespace gui {
|
||||
class GUI;
|
||||
}
|
||||
|
||||
namespace cmd {
|
||||
class CommandsInterpreter;
|
||||
}
|
||||
|
||||
class initialize_error : public std::runtime_error {
|
||||
public:
|
||||
initialize_error(const std::string& message) : std::runtime_error(message) {}
|
||||
@ -52,6 +56,7 @@ class Engine : public util::ObjectsKeeper {
|
||||
std::queue<runnable> postRunnables;
|
||||
std::recursive_mutex postRunnablesMutex;
|
||||
std::unique_ptr<EngineController> controller;
|
||||
std::unique_ptr<cmd::CommandsInterpreter> interpreter;
|
||||
std::vector<std::string> basePacks {"base"};
|
||||
|
||||
uint64_t frame = 0;
|
||||
@ -136,6 +141,7 @@ public:
|
||||
void saveScreenshot();
|
||||
|
||||
EngineController* getController();
|
||||
cmd::CommandsInterpreter* getCommandsInterpreter();
|
||||
|
||||
PacksManager createPacksManager(const fs::path& worldFolder);
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ std::unique_ptr<UiDocument> UiDocument::read(scriptenv penv, std::string name, f
|
||||
auto xmldoc = xml::parse(file.u8string(), text);
|
||||
|
||||
auto env = penv == nullptr
|
||||
? scripting::get_root_environment()
|
||||
? scripting::create_doc_environment(scripting::get_root_environment(), name)
|
||||
: scripting::create_doc_environment(penv, name);
|
||||
|
||||
gui::UiXmlReader reader(env);
|
||||
|
||||
@ -204,6 +204,9 @@ void Hud::processInput(bool visible) {
|
||||
setPause(true);
|
||||
}
|
||||
}
|
||||
if (!pause && Events::active(BIND_DEVTOOLS_CONSOLE)) {
|
||||
showOverlay(assets->getLayout("core:console"), false);
|
||||
}
|
||||
if (!Window::isFocused() && !pause && !isInventoryOpen()) {
|
||||
setPause(true);
|
||||
}
|
||||
@ -501,7 +504,7 @@ void Hud::updateElementsPosition(const Viewport& viewport) {
|
||||
));
|
||||
}
|
||||
secondUI->setPos(glm::vec2(
|
||||
glm::min(width/2-invwidth/2, width-caWidth-10-invwidth),
|
||||
glm::min(width/2-invwidth/2, width-caWidth-(inventoryView ? 10 : 0)-invwidth),
|
||||
height/2-totalHeight/2
|
||||
));
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ void menus::create_version_label(Engine* engine) {
|
||||
auto gui = engine->getGUI();
|
||||
auto text = ENGINE_VERSION_STRING+" debug build";
|
||||
gui->add(guiutil::create(
|
||||
"<label z-index='1000' color='#FFFFFF80' gravity='bottom-left' margin='4'>"
|
||||
"<label z-index='1000' color='#FFFFFF80' gravity='top-right' margin='4'>"
|
||||
+text+
|
||||
"</label>"
|
||||
));
|
||||
|
||||
@ -12,15 +12,19 @@
|
||||
using namespace gui;
|
||||
|
||||
TextBox::TextBox(std::wstring placeholder, glm::vec4 padding)
|
||||
: Panel(glm::vec2(200,32), padding, 0),
|
||||
input(L""),
|
||||
placeholder(placeholder) {
|
||||
: Panel(glm::vec2(200,32), padding, 0),
|
||||
input(L""),
|
||||
placeholder(placeholder)
|
||||
{
|
||||
setOnUpPressed(nullptr);
|
||||
setOnDownPressed(nullptr);
|
||||
label = std::make_shared<Label>(L"");
|
||||
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
|
||||
add(label);
|
||||
setHoverColor(glm::vec4(0.05f, 0.1f, 0.2f, 0.75f));
|
||||
|
||||
textInitX = label->getPos().x;
|
||||
scrollable = true;
|
||||
}
|
||||
|
||||
void TextBox::draw(const DrawContext* pctx, Assets* assets) {
|
||||
@ -28,9 +32,9 @@ void TextBox::draw(const DrawContext* pctx, Assets* assets) {
|
||||
|
||||
font = assets->getFont(label->getFontName());
|
||||
|
||||
if (!isFocused())
|
||||
if (!isFocused()) {
|
||||
return;
|
||||
|
||||
}
|
||||
glm::vec2 pos = calcPos();
|
||||
glm::vec2 size = getSize();
|
||||
|
||||
@ -78,7 +82,7 @@ void TextBox::drawBackground(const DrawContext* pctx, Assets*) {
|
||||
batch->texture(nullptr);
|
||||
|
||||
auto subctx = pctx->sub();
|
||||
subctx.setScissors(glm::vec4(pos.x, pos.y, size.x, size.y));
|
||||
subctx.setScissors(glm::vec4(pos.x, pos.y-0.5, size.x, size.y+1));
|
||||
|
||||
if (valid) {
|
||||
if (isFocused() && !multiline) {
|
||||
@ -115,9 +119,30 @@ void TextBox::drawBackground(const DrawContext* pctx, Assets*) {
|
||||
line++;
|
||||
} while (line < label->getLinesNumber() && label->isFakeLine(line));
|
||||
}
|
||||
refreshLabel();
|
||||
}
|
||||
|
||||
void TextBox::refreshLabel() {
|
||||
label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f));
|
||||
label->setText(getText());
|
||||
|
||||
if (autoresize && font) {
|
||||
auto size = getSize();
|
||||
int newy = glm::min(static_cast<int>(parent->getSize().y),
|
||||
static_cast<int>(
|
||||
label->getLinesNumber() *
|
||||
label->getLineInterval() *
|
||||
font->getLineHeight()) + 1
|
||||
);
|
||||
if (newy != static_cast<int>(size.y)) {
|
||||
size.y = newy;
|
||||
setSize(size);
|
||||
if (positionfunc) {
|
||||
pos = positionfunc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (multiline && font) {
|
||||
setScrollable(true);
|
||||
uint height = label->getLinesNumber() * font->getLineHeight() * label->getLineInterval();
|
||||
@ -140,9 +165,7 @@ void TextBox::paste(const std::wstring& text) {
|
||||
input = left + text + right;
|
||||
}
|
||||
input.erase(std::remove(input.begin(), input.end(), '\r'), input.end());
|
||||
// refresh label lines configuration for correct setCaret work
|
||||
label->setText(input);
|
||||
|
||||
refreshLabel();
|
||||
setCaret(caret + text.length());
|
||||
validate();
|
||||
}
|
||||
@ -186,8 +209,9 @@ void TextBox::extendSelection(int index) {
|
||||
size_t TextBox::getLineLength(uint line) const {
|
||||
size_t position = label->getTextLineOffset(line);
|
||||
size_t lineLength = label->getTextLineOffset(line+1)-position;
|
||||
if (lineLength == 0)
|
||||
if (lineLength == 0) {
|
||||
lineLength = input.length() - position + 1;
|
||||
}
|
||||
return lineLength;
|
||||
}
|
||||
|
||||
@ -255,6 +279,14 @@ void TextBox::setOnEditStart(runnable oneditstart) {
|
||||
onEditStart = oneditstart;
|
||||
}
|
||||
|
||||
void TextBox::setAutoResize(bool flag) {
|
||||
this->autoresize = flag;
|
||||
}
|
||||
|
||||
bool TextBox::isAutoResize() const {
|
||||
return autoresize;
|
||||
}
|
||||
|
||||
void TextBox::onFocus(GUI* gui) {
|
||||
Panel::onFocus(gui);
|
||||
if (onEditStart){
|
||||
@ -337,7 +369,7 @@ void TextBox::click(GUI*, int x, int y) {
|
||||
}
|
||||
|
||||
void TextBox::mouseMove(GUI*, int x, int y) {
|
||||
int index = calcIndexAt(x, y);
|
||||
ssize_t index = calcIndexAt(x, y);
|
||||
setCaret(index);
|
||||
extendSelection(index);
|
||||
resetMaxLocalCaret();
|
||||
@ -347,10 +379,94 @@ void TextBox::resetMaxLocalCaret() {
|
||||
maxLocalCaret = caret - label->getTextLineOffset(label->getLineByTextIndex(caret));
|
||||
}
|
||||
|
||||
void TextBox::stepLeft(bool shiftPressed, bool breakSelection) {
|
||||
uint previousCaret = this->caret;
|
||||
size_t caret = breakSelection ? selectionStart : this->caret;
|
||||
if (caret > 0) {
|
||||
if (caret > input.length()) {
|
||||
setCaret(input.length()-1);
|
||||
} else {
|
||||
setCaret(caret-1);
|
||||
}
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
} else {
|
||||
setCaret(caret);
|
||||
resetSelection();
|
||||
}
|
||||
resetMaxLocalCaret();
|
||||
}
|
||||
|
||||
void TextBox::stepRight(bool shiftPressed, bool breakSelection) {
|
||||
uint previousCaret = this->caret;
|
||||
size_t caret = breakSelection ? selectionEnd : this->caret;
|
||||
if (caret < input.length()) {
|
||||
setCaret(caret+1);
|
||||
caretLastMove = Window::time();
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
} else {
|
||||
setCaret(caret);
|
||||
resetSelection();
|
||||
}
|
||||
resetMaxLocalCaret();
|
||||
}
|
||||
|
||||
void TextBox::stepDefaultDown(bool shiftPressed, bool breakSelection) {
|
||||
uint previousCaret = this->caret;
|
||||
uint caret = breakSelection ? selectionEnd : this->caret;
|
||||
uint caretLine = label->getLineByTextIndex(caret);
|
||||
if (caretLine < label->getLinesNumber()-1) {
|
||||
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine+1)-1);
|
||||
setCaret(label->getTextLineOffset(caretLine+1) + offset);
|
||||
} else {
|
||||
setCaret(input.length());
|
||||
}
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) {
|
||||
uint previousCaret = this->caret;
|
||||
uint caret = breakSelection ? selectionStart : this->caret;
|
||||
uint caretLine = label->getLineByTextIndex(caret);
|
||||
if (caretLine > 0) {
|
||||
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine-1)-1);
|
||||
setCaret(label->getTextLineOffset(caretLine-1) + offset);
|
||||
} else {
|
||||
setCaret(0UL);
|
||||
}
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::performEditingKeyboardEvents(keycode key) {
|
||||
bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT);
|
||||
bool breakSelection = getSelectionLength() != 0 && !shiftPressed;
|
||||
uint previousCaret = caret;
|
||||
if (key == keycode::BACKSPACE) {
|
||||
if (!eraseSelected() && caret > 0 && input.length() > 0) {
|
||||
if (caret > input.length()) {
|
||||
@ -369,86 +485,21 @@ void TextBox::performEditingKeyboardEvents(keycode key) {
|
||||
if (multiline) {
|
||||
paste(L"\n");
|
||||
} else {
|
||||
defocus();
|
||||
if (validate() && consumer) {
|
||||
consumer(label->getText());
|
||||
}
|
||||
defocus();
|
||||
}
|
||||
} else if (key == keycode::TAB) {
|
||||
paste(L" ");
|
||||
} else if (key == keycode::LEFT) {
|
||||
uint caret = breakSelection ? selectionStart : this->caret;
|
||||
if (caret > 0) {
|
||||
if (caret > input.length()) {
|
||||
setCaret(input.length()-1);
|
||||
} else {
|
||||
setCaret(caret-1);
|
||||
}
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
} else {
|
||||
setCaret(caret);
|
||||
resetSelection();
|
||||
}
|
||||
resetMaxLocalCaret();
|
||||
stepLeft(shiftPressed, breakSelection);
|
||||
} else if (key == keycode::RIGHT) {
|
||||
uint caret = breakSelection ? selectionEnd : this->caret;
|
||||
if (caret < input.length()) {
|
||||
setCaret(caret+1);
|
||||
caretLastMove = Window::time();
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
} else {
|
||||
setCaret(caret);
|
||||
resetSelection();
|
||||
}
|
||||
resetMaxLocalCaret();
|
||||
} else if (key == keycode::UP) {
|
||||
uint caret = breakSelection ? selectionStart : this->caret;
|
||||
uint caretLine = label->getLineByTextIndex(caret);
|
||||
if (caretLine > 0) {
|
||||
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine-1)-1);
|
||||
setCaret(label->getTextLineOffset(caretLine-1) + offset);
|
||||
} else {
|
||||
setCaret(0);
|
||||
}
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
} else if (key == keycode::DOWN) {
|
||||
uint caret = breakSelection ? selectionEnd : this->caret;
|
||||
uint caretLine = label->getLineByTextIndex(caret);
|
||||
if (caretLine < label->getLinesNumber()-1) {
|
||||
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine+1)-1);
|
||||
setCaret(label->getTextLineOffset(caretLine+1) + offset);
|
||||
} else {
|
||||
setCaret(input.length());
|
||||
}
|
||||
if (shiftPressed) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionOrigin = previousCaret;
|
||||
}
|
||||
extendSelection(this->caret);
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
stepRight(shiftPressed, breakSelection);
|
||||
} else if (key == keycode::UP && onUpPressed) {
|
||||
onUpPressed();
|
||||
} else if (key == keycode::DOWN && onDownPressed) {
|
||||
onDownPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,6 +553,30 @@ std::shared_ptr<UINode> TextBox::getAt(glm::vec2 pos, std::shared_ptr<UINode> se
|
||||
return UINode::getAt(pos, self);
|
||||
}
|
||||
|
||||
void TextBox::setOnUpPressed(runnable callback) {
|
||||
if (callback == nullptr) {
|
||||
onUpPressed = [this]() {
|
||||
bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT);
|
||||
bool breakSelection = getSelectionLength() != 0 && !shiftPressed;
|
||||
stepDefaultUp(shiftPressed, breakSelection);
|
||||
};
|
||||
} else {
|
||||
onUpPressed = callback;
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::setOnDownPressed(runnable callback) {
|
||||
if (callback == nullptr) {
|
||||
onDownPressed = [this]() {
|
||||
bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT);
|
||||
bool breakSelection = getSelectionLength() != 0 && !shiftPressed;
|
||||
stepDefaultDown(shiftPressed, breakSelection);
|
||||
};
|
||||
} else {
|
||||
onDownPressed = callback;
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::setTextSupplier(wstringsupplier supplier) {
|
||||
this->supplier = supplier;
|
||||
}
|
||||
@ -553,12 +628,12 @@ std::wstring TextBox::getSelection() const {
|
||||
return input.substr(selectionStart, selectionEnd-selectionStart);
|
||||
}
|
||||
|
||||
uint TextBox::getCaret() const {
|
||||
size_t TextBox::getCaret() const {
|
||||
return caret;
|
||||
}
|
||||
|
||||
void TextBox::setCaret(uint position) {
|
||||
this->caret = std::min(size_t(position), input.length());
|
||||
void TextBox::setCaret(size_t position) {
|
||||
this->caret = std::min(static_cast<size_t>(position), input.length());
|
||||
caretLastMove = Window::time();
|
||||
|
||||
int width = label->getSize().x;
|
||||
@ -569,13 +644,22 @@ void TextBox::setCaret(uint position) {
|
||||
if (offset < 0) {
|
||||
scrolled(1);
|
||||
} else if (offset >= getSize().y) {
|
||||
scrolled(-1);
|
||||
offset -= getSize().y;
|
||||
scrolled(-glm::ceil(offset/static_cast<double>(scrollStep)+0.5f));
|
||||
}
|
||||
uint lcaret = caret - label->getTextLineOffset(line);
|
||||
int realoffset = font->calcWidth(input, lcaret)-int(textOffset)+2;
|
||||
if (realoffset-width > 0) {
|
||||
setTextOffset(textOffset + realoffset-width);
|
||||
} else if (realoffset < 0) {
|
||||
setTextOffset(std::max(textOffset + realoffset, 0U));
|
||||
setTextOffset(std::max(textOffset + realoffset, 0LU));
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::setCaret(ssize_t position) {
|
||||
if (position < 0) {
|
||||
setCaret(static_cast<size_t>(input.length() + position + 1));
|
||||
} else {
|
||||
setCaret(static_cast<size_t>(position));
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,12 +20,14 @@ namespace gui {
|
||||
wstringconsumer consumer = nullptr;
|
||||
wstringchecker validator = nullptr;
|
||||
runnable onEditStart = nullptr;
|
||||
runnable onUpPressed;
|
||||
runnable onDownPressed;
|
||||
bool valid = true;
|
||||
/// @brief text input pointer, value may be greather than text length
|
||||
uint caret = 0;
|
||||
size_t caret = 0;
|
||||
/// @brief actual local (line) position of the caret on vertical move
|
||||
uint maxLocalCaret = 0;
|
||||
uint textOffset = 0;
|
||||
size_t maxLocalCaret = 0;
|
||||
size_t textOffset = 0;
|
||||
int textInitX;
|
||||
/// @brief last time of the caret was moved (used for blink animation)
|
||||
double caretLastMove = 0.0;
|
||||
@ -37,11 +39,16 @@ namespace gui {
|
||||
|
||||
bool multiline = false;
|
||||
bool editable = true;
|
||||
bool autoresize = false;
|
||||
|
||||
void stepLeft(bool shiftPressed, bool breakSelection);
|
||||
void stepRight(bool shiftPressed, bool breakSelection);
|
||||
void stepDefaultDown(bool shiftPressed, bool breakSelection);
|
||||
void stepDefaultUp(bool shiftPressed, bool breakSelection);
|
||||
|
||||
size_t normalizeIndex(int index);
|
||||
|
||||
int calcIndexAt(int x, int y) const;
|
||||
void paste(const std::wstring& text);
|
||||
void setTextOffset(uint x);
|
||||
void erase(size_t start, size_t length);
|
||||
bool eraseSelected();
|
||||
@ -57,12 +64,16 @@ namespace gui {
|
||||
void resetMaxLocalCaret();
|
||||
|
||||
void performEditingKeyboardEvents(keycode key);
|
||||
|
||||
void refreshLabel();
|
||||
public:
|
||||
TextBox(
|
||||
std::wstring placeholder,
|
||||
glm::vec4 padding=glm::vec4(4.0f)
|
||||
);
|
||||
|
||||
|
||||
void paste(const std::wstring& text);
|
||||
|
||||
virtual void setTextSupplier(wstringsupplier supplier);
|
||||
|
||||
/// @brief Consumer called on stop editing text (textbox defocus)
|
||||
@ -101,11 +112,15 @@ namespace gui {
|
||||
|
||||
/// @brief Get current caret position in text
|
||||
/// @return integer in range [0, text.length()]
|
||||
virtual uint getCaret() const;
|
||||
virtual size_t getCaret() const;
|
||||
|
||||
/// @brief Set caret position in the text
|
||||
/// @param position integer in range [0, text.length()]
|
||||
virtual void setCaret(uint position);
|
||||
virtual void setCaret(size_t position);
|
||||
|
||||
/// @brief Set caret position in the text
|
||||
/// @param position integer in range [-text.length(), text.length()]
|
||||
virtual void setCaret(ssize_t position);
|
||||
|
||||
/// @brief Select part of the text
|
||||
/// @param start index of the first selected character
|
||||
@ -140,6 +155,9 @@ namespace gui {
|
||||
/// @brief Set runnable called on textbox focus
|
||||
virtual void setOnEditStart(runnable oneditstart);
|
||||
|
||||
virtual void setAutoResize(bool flag);
|
||||
virtual bool isAutoResize() const;
|
||||
|
||||
virtual void onFocus(GUI*) override;
|
||||
virtual void refresh() override;
|
||||
virtual void doubleClick(GUI*, int x, int y) override;
|
||||
@ -151,6 +169,8 @@ namespace gui {
|
||||
virtual void typed(unsigned int codepoint) override;
|
||||
virtual void keyPressed(keycode key) override;
|
||||
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
|
||||
virtual void setOnUpPressed(runnable callback);
|
||||
virtual void setOnDownPressed(runnable callback);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -60,12 +60,12 @@ UINode* UINode::getParent() const {
|
||||
}
|
||||
|
||||
UINode* UINode::listenAction(onaction action) {
|
||||
actions.push_back(action);
|
||||
actions.listen(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
UINode* UINode::listenDoubleClick(onaction action) {
|
||||
doubleClickCallbacks.push_back(action);
|
||||
doubleClickCallbacks.listen(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -76,18 +76,14 @@ void UINode::click(GUI*, int, int) {
|
||||
void UINode::doubleClick(GUI* gui, int x, int y) {
|
||||
pressed = true;
|
||||
if (isInside(glm::vec2(x, y))) {
|
||||
for (auto callback : doubleClickCallbacks) {
|
||||
callback(gui);
|
||||
}
|
||||
doubleClickCallbacks.notify(gui);
|
||||
}
|
||||
}
|
||||
|
||||
void UINode::mouseRelease(GUI* gui, int x, int y) {
|
||||
pressed = false;
|
||||
if (isInside(glm::vec2(x, y))) {
|
||||
for (auto callback : actions) {
|
||||
callback(gui);
|
||||
}
|
||||
actions.notify(gui);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,25 @@ namespace gui {
|
||||
|
||||
using onaction = std::function<void(GUI*)>;
|
||||
using onnumberchange = std::function<void(GUI*, double)>;
|
||||
|
||||
class ActionsSet {
|
||||
std::unique_ptr<std::vector<onaction>> callbacks;
|
||||
public:
|
||||
void listen(onaction callback) {
|
||||
if (callbacks == nullptr) {
|
||||
callbacks = std::make_unique<std::vector<onaction>>();
|
||||
}
|
||||
callbacks->push_back(callback);
|
||||
}
|
||||
|
||||
void notify(GUI* gui) {
|
||||
if (callbacks) {
|
||||
for (auto& callback : *callbacks) {
|
||||
callback(gui);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum class Align {
|
||||
left, center, right,
|
||||
@ -87,9 +106,9 @@ namespace gui {
|
||||
/// @brief size supplier for the element (called on parent element size update)
|
||||
vec2supplier sizefunc = nullptr;
|
||||
/// @brief 'onclick' callbacks
|
||||
std::vector<onaction> actions;
|
||||
ActionsSet actions;
|
||||
/// @brief 'ondoubleclick' callbacks
|
||||
std::vector<onaction> doubleClickCallbacks;
|
||||
ActionsSet doubleClickCallbacks;
|
||||
|
||||
UINode(glm::vec2 size);
|
||||
public:
|
||||
|
||||
@ -50,6 +50,30 @@ static Gravity gravity_from_string(const std::string& str) {
|
||||
return Gravity::none;
|
||||
}
|
||||
|
||||
static runnable create_runnable(
|
||||
const UiXmlReader& reader,
|
||||
xml::xmlelement element,
|
||||
const std::string& name
|
||||
) {
|
||||
if (element->has(name)) {
|
||||
std::string text = element->attr(name).getText();
|
||||
if (!text.empty()) {
|
||||
return scripting::create_runnable(
|
||||
reader.getEnvironment(), text, reader.getFilename()
|
||||
);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static onaction create_action(UiXmlReader& reader, xml::xmlelement element, const std::string& name) {
|
||||
auto callback = create_runnable(reader, element, name);
|
||||
if (callback == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return [callback](GUI*) {callback();};
|
||||
}
|
||||
|
||||
/* Read basic UINode properties */
|
||||
static void _readUINode(UiXmlReader& reader, xml::xmlelement element, UINode& node) {
|
||||
if (element->has("id")) {
|
||||
@ -119,28 +143,12 @@ static void _readUINode(UiXmlReader& reader, xml::xmlelement element, UINode& no
|
||||
));
|
||||
}
|
||||
|
||||
if (element->has("onclick")) {
|
||||
std::string text = element->attr("onclick").getText();
|
||||
if (!text.empty()) {
|
||||
auto callback = scripting::create_runnable(
|
||||
reader.getEnvironment(), text, reader.getFilename()
|
||||
);
|
||||
node.listenAction([callback](GUI*) {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
if (auto onclick = create_action(reader, element, "onclick")) {
|
||||
node.listenAction(onclick);
|
||||
}
|
||||
|
||||
if (element->has("ondoubleclick")) {
|
||||
std::string text = element->attr("ondoubleclick").getText();
|
||||
if (!text.empty()) {
|
||||
auto callback = scripting::create_runnable(
|
||||
reader.getEnvironment(), text, reader.getFilename()
|
||||
);
|
||||
node.listenDoubleClick([callback](GUI*) {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
if (auto ondoubleclick = create_action(reader, element, "ondoubleclick")) {
|
||||
node.listenDoubleClick(ondoubleclick);
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,11 +331,12 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
||||
if (element->has("text-wrap")) {
|
||||
textbox->setTextWrapping(element->attr("text-wrap").asBool());
|
||||
}
|
||||
|
||||
if (element->has("editable")) {
|
||||
textbox->setEditable(element->attr("editable").asBool());
|
||||
}
|
||||
|
||||
if (element->has("autoresize")) {
|
||||
textbox->setAutoResize(element->attr("autoresize").asBool());
|
||||
}
|
||||
if (element->has("consumer")) {
|
||||
textbox->setTextConsumer(scripting::create_wstring_consumer(
|
||||
reader.getEnvironment(),
|
||||
@ -335,7 +344,6 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
||||
reader.getFilename()
|
||||
));
|
||||
}
|
||||
|
||||
if (element->has("supplier")) {
|
||||
textbox->setTextSupplier(scripting::create_wstring_supplier(
|
||||
reader.getEnvironment(),
|
||||
@ -356,6 +364,12 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
||||
reader.getFilename()
|
||||
));
|
||||
}
|
||||
if (auto onUpPressed = create_runnable(reader, element, "onup")) {
|
||||
textbox->setOnUpPressed(onUpPressed);
|
||||
}
|
||||
if (auto onDownPressed = create_runnable(reader, element, "ondown")) {
|
||||
textbox->setOnDownPressed(onDownPressed);
|
||||
}
|
||||
return textbox;
|
||||
}
|
||||
|
||||
|
||||
405
src/logic/CommandsInterpreter.cpp
Normal file
405
src/logic/CommandsInterpreter.cpp
Normal file
@ -0,0 +1,405 @@
|
||||
#include "CommandsInterpreter.hpp"
|
||||
|
||||
#include "../coders/commons.hpp"
|
||||
#include "../util/stringutil.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace cmd;
|
||||
|
||||
inline bool is_cmd_identifier_part(char c, bool allowColon) {
|
||||
return is_identifier_part(c) || c == '.' || c == '$' ||
|
||||
(allowColon && c == ':');
|
||||
}
|
||||
|
||||
inline bool is_cmd_identifier_start(char c) {
|
||||
return (is_identifier_start(c) || c == '.' || c == '$');
|
||||
}
|
||||
|
||||
class CommandParser : BasicParser {
|
||||
std::string parseIdentifier(bool allowColon) {
|
||||
char c = peek();
|
||||
if (!is_identifier_start(c) && c != '$') {
|
||||
if (c == '"') {
|
||||
pos++;
|
||||
return parseString(c);
|
||||
}
|
||||
throw error("identifier expected");
|
||||
}
|
||||
int start = pos;
|
||||
while (hasNext() && is_cmd_identifier_part(source[pos], allowColon)) {
|
||||
pos++;
|
||||
}
|
||||
return std::string(source.substr(start, pos-start));
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ArgType> types {
|
||||
{"num", ArgType::number},
|
||||
{"int", ArgType::integer},
|
||||
{"str", ArgType::string},
|
||||
{"sel", ArgType::selector},
|
||||
{"enum", ArgType::enumvalue},
|
||||
};
|
||||
public:
|
||||
CommandParser(std::string_view filename, std::string_view source)
|
||||
: BasicParser(filename, source) {
|
||||
}
|
||||
|
||||
ArgType parseType() {
|
||||
if (peek() == '[') {
|
||||
return ArgType::enumvalue;
|
||||
}
|
||||
std::string name = parseIdentifier(false);
|
||||
auto found = types.find(name);
|
||||
if (found != types.end()) {
|
||||
return found->second;
|
||||
} else {
|
||||
throw error("unknown type "+util::quote(name));
|
||||
}
|
||||
}
|
||||
|
||||
dynamic::Value parseValue() {
|
||||
char c = peek();
|
||||
if (is_cmd_identifier_start(c) || c == '@') {
|
||||
auto str = parseIdentifier(true);
|
||||
if (str == "true") {
|
||||
return true;
|
||||
} else if (str == "false") {
|
||||
return false;
|
||||
} else if (str == "none" || str == "nil" || str == "null") {
|
||||
return dynamic::NONE;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
if (c == '"' || c == '\'') {
|
||||
nextChar();
|
||||
return parseString(c);
|
||||
}
|
||||
if (c == '+' || c == '-') {
|
||||
nextChar();
|
||||
return parseNumber(c == '-' ? -1 : 1);
|
||||
}
|
||||
if (is_digit(c)) {
|
||||
return parseNumber(1);
|
||||
}
|
||||
throw error("invalid character '"+std::string({c})+"'");
|
||||
}
|
||||
|
||||
std::string parseEnum() {
|
||||
if (peek() == '[') {
|
||||
nextChar();
|
||||
if (peek() == ']') {
|
||||
throw error("empty enumeration is not allowed");
|
||||
}
|
||||
auto enumvalue = "|"+std::string(readUntil(']'))+"|";
|
||||
size_t offset = enumvalue.find(' ');
|
||||
if (offset != std::string::npos) {
|
||||
goBack(enumvalue.length()-offset);
|
||||
throw error("use '|' as separator, not a space");
|
||||
}
|
||||
nextChar();
|
||||
return enumvalue;
|
||||
} else {
|
||||
expect('$');
|
||||
goBack();
|
||||
return parseIdentifier(false);
|
||||
}
|
||||
}
|
||||
|
||||
Argument parseArgument() {
|
||||
std::string name = parseIdentifier(false);
|
||||
expect(':');
|
||||
ArgType type = parseType();
|
||||
std::string enumname = "";
|
||||
if (type == ArgType::enumvalue) {
|
||||
enumname = parseEnum();
|
||||
}
|
||||
bool optional = false;
|
||||
dynamic::Value def {};
|
||||
dynamic::Value origin {};
|
||||
bool loop = true;
|
||||
while (hasNext() && loop) {
|
||||
char c = peek();
|
||||
switch (c) {
|
||||
case '=':
|
||||
nextChar();
|
||||
optional = true;
|
||||
def = parseValue();
|
||||
break;
|
||||
case '~':
|
||||
nextChar();
|
||||
origin = parseValue();
|
||||
break;
|
||||
default:
|
||||
loop = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Argument {name, type, optional, def, origin, enumname};
|
||||
}
|
||||
|
||||
Command parseScheme(executor_func executor, std::string_view description) {
|
||||
std::string name = parseIdentifier(true);
|
||||
std::vector<Argument> args;
|
||||
std::unordered_map<std::string, Argument> kwargs;
|
||||
while (hasNext()) {
|
||||
if (peek() == '{') {
|
||||
nextChar();
|
||||
while (peek() != '}') {
|
||||
Argument arg = parseArgument();
|
||||
kwargs[arg.name] = arg;
|
||||
}
|
||||
nextChar();
|
||||
} else {
|
||||
args.push_back(parseArgument());
|
||||
}
|
||||
}
|
||||
return Command(
|
||||
name, std::move(args), std::move(kwargs),
|
||||
std::string(description), executor
|
||||
);
|
||||
}
|
||||
|
||||
inline parsing_error argumentError(
|
||||
const std::string& argname,
|
||||
const std::string& message
|
||||
) {
|
||||
return error("argument "+util::quote(argname)+": "+message);
|
||||
}
|
||||
|
||||
inline parsing_error typeError(
|
||||
const std::string& argname,
|
||||
const std::string& expected,
|
||||
const dynamic::Value& value
|
||||
) {
|
||||
return argumentError(
|
||||
argname, expected+" expected, got "+dynamic::type_name(value)
|
||||
);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool typeCheck(Argument* arg, const dynamic::Value& value, const std::string& tname) {
|
||||
if (!std::holds_alternative<T>(value)) {
|
||||
if (arg->optional) {
|
||||
return false;
|
||||
} else {
|
||||
throw typeError(arg->name, tname, value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool selectorCheck(Argument* arg, const dynamic::Value& value) {
|
||||
if (auto string = std::get_if<std::string>(&value)) {
|
||||
if ((*string)[0] == '@') {
|
||||
if (!util::is_integer((*string).substr(1))) {
|
||||
throw argumentError(arg->name, "invalid selector");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (arg->optional) {
|
||||
return false;
|
||||
} else {
|
||||
throw typeError(arg->name, "selector", value);
|
||||
}
|
||||
}
|
||||
|
||||
bool typeCheck(Argument* arg, const dynamic::Value& value) {
|
||||
switch (arg->type) {
|
||||
case ArgType::enumvalue: {
|
||||
if (auto* string = std::get_if<std::string>(&value)) {
|
||||
auto& enumname = arg->enumname;
|
||||
if (enumname.find("|"+*string+"|") == std::string::npos) {
|
||||
throw error("argument "+util::quote(arg->name)+
|
||||
": invalid enumeration value");
|
||||
}
|
||||
} else {
|
||||
if (arg->optional) {
|
||||
return false;
|
||||
}
|
||||
throw typeError(arg->name, "enumeration value", value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ArgType::number:
|
||||
if (!dynamic::is_numeric(value)) {
|
||||
if (arg->optional) {
|
||||
return false;
|
||||
} else {
|
||||
throw typeError(arg->name, "number", value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ArgType::selector:
|
||||
return selectorCheck(arg, value);
|
||||
case ArgType::integer:
|
||||
return typeCheck<integer_t>(arg, value, "integer");
|
||||
case ArgType::string:
|
||||
if (!std::holds_alternative<std::string>(value)) {
|
||||
return !arg->optional;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
dynamic::Value fetchOrigin(CommandsInterpreter* interpreter, Argument* arg) {
|
||||
if (dynamic::is_numeric(arg->origin)) {
|
||||
return arg->origin;
|
||||
} else if (auto string = std::get_if<std::string>(&arg->origin)) {
|
||||
return (*interpreter)[*string];
|
||||
}
|
||||
return dynamic::NONE;
|
||||
}
|
||||
|
||||
dynamic::Value applyRelative(
|
||||
Argument* arg,
|
||||
dynamic::Value value,
|
||||
dynamic::Value origin
|
||||
) {
|
||||
if (origin.index() == 0) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
if (arg->type == ArgType::number) {
|
||||
return dynamic::get_number(origin) + dynamic::get_number(value);
|
||||
} else {
|
||||
return dynamic::get_integer(origin) + dynamic::get_integer(value);
|
||||
}
|
||||
} catch (std::runtime_error& err) {
|
||||
throw argumentError(arg->name, err.what());
|
||||
}
|
||||
}
|
||||
|
||||
dynamic::Value parseRelativeValue(CommandsInterpreter* interpreter, Argument* arg) {
|
||||
if (arg->type != ArgType::number && arg->type != ArgType::integer) {
|
||||
throw error("'~' operator is only allowed for numeric arguments");
|
||||
}
|
||||
nextChar();
|
||||
auto origin = fetchOrigin(interpreter, arg);
|
||||
if (peekNoJump() == ' ' || !hasNext()) {
|
||||
return origin;
|
||||
}
|
||||
auto value = parseValue();
|
||||
if (origin.index() == 0) {
|
||||
return value;
|
||||
}
|
||||
return applyRelative(arg, value, origin);
|
||||
}
|
||||
|
||||
inline dynamic::Value performKeywordArg(
|
||||
CommandsInterpreter* interpreter, Command* command, const std::string& key
|
||||
) {
|
||||
if (auto arg = command->getArgument(key)) {
|
||||
nextChar();
|
||||
auto value = peek() == '~'
|
||||
? parseRelativeValue(interpreter, arg)
|
||||
: parseValue();
|
||||
typeCheck(arg, value);
|
||||
return value;
|
||||
} else {
|
||||
throw error("unknown keyword "+util::quote(key));
|
||||
}
|
||||
}
|
||||
|
||||
Prompt parsePrompt(CommandsInterpreter* interpreter) {
|
||||
auto repo = interpreter->getRepository();
|
||||
std::string name = parseIdentifier(true);
|
||||
auto command = repo->get(name);
|
||||
if (command == nullptr) {
|
||||
throw error("unknown command "+util::quote(name));
|
||||
}
|
||||
auto args = dynamic::create_list();
|
||||
auto kwargs = dynamic::create_map();
|
||||
|
||||
int arg_index = 0;
|
||||
|
||||
while (hasNext()) {
|
||||
bool relative = false;
|
||||
dynamic::Value value = dynamic::NONE;
|
||||
if (peek() == '~') {
|
||||
relative = true;
|
||||
value = 0L;
|
||||
nextChar();
|
||||
}
|
||||
|
||||
if (hasNext() && peekNoJump() != ' ') {
|
||||
value = parseValue();
|
||||
if (auto string = std::get_if<std::string>(&value)) {
|
||||
if ((*string)[0] == '$') {
|
||||
value = (*interpreter)[string->substr(1)];
|
||||
}
|
||||
}
|
||||
|
||||
// keyword argument
|
||||
if (!relative && hasNext() && peek() == '=') {
|
||||
auto key = std::get<std::string>(value);
|
||||
kwargs->put(key, performKeywordArg(interpreter, command, key));
|
||||
}
|
||||
}
|
||||
|
||||
// positional argument
|
||||
Argument* arg = nullptr;
|
||||
do {
|
||||
if (arg) {
|
||||
if (auto string = std::get_if<std::string>(&arg->def)) {
|
||||
if ((*string)[0] == '$') {
|
||||
args->put((*interpreter)[string->substr(1)]);
|
||||
} else {
|
||||
args->put(arg->def);
|
||||
}
|
||||
} else {
|
||||
args->put(arg->def);
|
||||
}
|
||||
}
|
||||
arg = command->getArgument(arg_index++);
|
||||
if (arg == nullptr) {
|
||||
throw error("extra positional argument");
|
||||
}
|
||||
if (arg->origin.index() && relative) {
|
||||
break;
|
||||
}
|
||||
} while (!typeCheck(arg, value));
|
||||
|
||||
if (relative) {
|
||||
value = applyRelative(arg, value, fetchOrigin(interpreter, arg));
|
||||
}
|
||||
args->put(value);
|
||||
}
|
||||
|
||||
while (auto arg = command->getArgument(arg_index++)) {
|
||||
if (!arg->optional) {
|
||||
throw error("missing argument "+util::quote(arg->name));
|
||||
} else {
|
||||
args->put(arg->def);
|
||||
}
|
||||
}
|
||||
return Prompt {command, args, kwargs};
|
||||
}
|
||||
};
|
||||
|
||||
Command Command::create(std::string_view scheme, std::string_view description, executor_func executor) {
|
||||
return CommandParser("<string>", scheme).parseScheme(executor, description);
|
||||
}
|
||||
|
||||
void CommandsRepository::add(
|
||||
std::string_view scheme,
|
||||
std::string_view description,
|
||||
executor_func executor
|
||||
) {
|
||||
Command command = Command::create(scheme, description, executor);
|
||||
commands[command.getName()] = command;
|
||||
}
|
||||
|
||||
Command* CommandsRepository::get(const std::string& name) {
|
||||
auto found = commands.find(name);
|
||||
if (found == commands.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &found->second;
|
||||
}
|
||||
|
||||
Prompt CommandsInterpreter::parse(std::string_view text) {
|
||||
return CommandParser("<string>", text).parsePrompt(this);
|
||||
}
|
||||
159
src/logic/CommandsInterpreter.hpp
Normal file
159
src/logic/CommandsInterpreter.hpp
Normal file
@ -0,0 +1,159 @@
|
||||
#ifndef LOGIC_COMMANDS_INTERPRETER_HPP_
|
||||
#define LOGIC_COMMANDS_INTERPRETER_HPP_
|
||||
|
||||
#include "../data/dynamic.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace cmd {
|
||||
enum class ArgType {
|
||||
number, integer, enumvalue, selector, string
|
||||
};
|
||||
|
||||
inline std::string argtype_name(ArgType type) {
|
||||
switch (type) {
|
||||
case ArgType::number: return "number";
|
||||
case ArgType::integer: return "integer";
|
||||
case ArgType::enumvalue: return "enumeration";
|
||||
case ArgType::selector: return "selector";
|
||||
case ArgType::string: return "string";
|
||||
default: return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
struct Argument {
|
||||
std::string name;
|
||||
ArgType type;
|
||||
bool optional;
|
||||
dynamic::Value def;
|
||||
dynamic::Value origin;
|
||||
std::string enumname;
|
||||
};
|
||||
|
||||
class Command;
|
||||
class CommandsInterpreter;
|
||||
|
||||
struct Prompt {
|
||||
Command* command;
|
||||
dynamic::List_sptr args; // positional arguments list
|
||||
dynamic::Map_sptr kwargs; // keyword arguments table
|
||||
};
|
||||
|
||||
using executor_func = std::function<dynamic::Value(
|
||||
CommandsInterpreter*,
|
||||
dynamic::List_sptr args,
|
||||
dynamic::Map_sptr kwargs
|
||||
)>;
|
||||
|
||||
class Command {
|
||||
std::string name;
|
||||
std::vector<Argument> args;
|
||||
std::unordered_map<std::string, Argument> kwargs;
|
||||
std::string description;
|
||||
executor_func executor;
|
||||
public:
|
||||
Command() {}
|
||||
|
||||
Command(
|
||||
std::string name,
|
||||
std::vector<Argument> args,
|
||||
std::unordered_map<std::string, Argument> kwargs,
|
||||
std::string description,
|
||||
executor_func executor
|
||||
) : name(name),
|
||||
args(std::move(args)),
|
||||
kwargs(std::move(kwargs)),
|
||||
description(description),
|
||||
executor(executor) {}
|
||||
|
||||
Argument* getArgument(size_t index) {
|
||||
if (index >= args.size())
|
||||
return nullptr;
|
||||
return &args[index];
|
||||
}
|
||||
|
||||
Argument* getArgument(const std::string& keyword) {
|
||||
auto found = kwargs.find(keyword);
|
||||
if (found == kwargs.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &found->second;
|
||||
}
|
||||
|
||||
dynamic::Value execute(CommandsInterpreter* interpreter, const Prompt& prompt) {
|
||||
return executor(interpreter, prompt.args, prompt.kwargs);
|
||||
}
|
||||
|
||||
const std::string& getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
const std::vector<Argument>& getArgs() const {
|
||||
return args;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, Argument>& getKwArgs() const {
|
||||
return kwargs;
|
||||
}
|
||||
|
||||
const std::string& getDescription() const {
|
||||
return description;
|
||||
}
|
||||
|
||||
static Command create(
|
||||
std::string_view scheme,
|
||||
std::string_view description,
|
||||
executor_func
|
||||
);
|
||||
};
|
||||
|
||||
class CommandsRepository {
|
||||
std::unordered_map<std::string, Command> commands;
|
||||
public:
|
||||
void add(
|
||||
std::string_view scheme,
|
||||
std::string_view description,
|
||||
executor_func
|
||||
);
|
||||
Command* get(const std::string& name);
|
||||
|
||||
const std::unordered_map<std::string, Command> getCommands() const {
|
||||
return commands;
|
||||
}
|
||||
};
|
||||
|
||||
class CommandsInterpreter {
|
||||
std::unique_ptr<CommandsRepository> repository;
|
||||
std::unordered_map<std::string, dynamic::Value> variables;
|
||||
public:
|
||||
CommandsInterpreter() : repository(std::make_unique<CommandsRepository>()) {}
|
||||
|
||||
CommandsInterpreter(const CommandsInterpreter&) = delete;
|
||||
|
||||
CommandsInterpreter(std::unique_ptr<CommandsRepository> repository)
|
||||
: repository(std::move(repository)){}
|
||||
|
||||
Prompt parse(std::string_view text);
|
||||
|
||||
dynamic::Value execute(std::string_view input) {
|
||||
return execute(parse(input));
|
||||
}
|
||||
|
||||
dynamic::Value execute(const Prompt& prompt) {
|
||||
return prompt.command->execute(this, prompt);
|
||||
}
|
||||
|
||||
dynamic::Value& operator[](const std::string& name) {
|
||||
return variables[name];
|
||||
}
|
||||
|
||||
CommandsRepository* getRepository() const {
|
||||
return repository.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // LOGIC_COMMANDS_INTERPRETER_HPP_
|
||||
@ -12,6 +12,10 @@ inline std::string LAMBDAS_TABLE = "$L";
|
||||
|
||||
static debug::Logger logger("lua-state");
|
||||
|
||||
namespace scripting {
|
||||
extern lua::LuaState* state;
|
||||
}
|
||||
|
||||
lua::luaerror::luaerror(const std::string& message) : std::runtime_error(message) {
|
||||
}
|
||||
|
||||
@ -123,19 +127,20 @@ void lua::LuaState::remove(const std::string& name) {
|
||||
}
|
||||
|
||||
void lua::LuaState::createLibs() {
|
||||
openlib("audio", audiolib, 0);
|
||||
openlib("block", blocklib, 0);
|
||||
openlib("core", corelib, 0);
|
||||
openlib("file", filelib, 0);
|
||||
openlib("gui", guilib, 0);
|
||||
openlib("input", inputlib, 0);
|
||||
openlib("inventory", inventorylib, 0);
|
||||
openlib("item", itemlib, 0);
|
||||
openlib("json", jsonlib, 0);
|
||||
openlib("pack", packlib, 0);
|
||||
openlib("player", playerlib, 0);
|
||||
openlib("time", timelib, 0);
|
||||
openlib("world", worldlib, 0);
|
||||
openlib("audio", audiolib);
|
||||
openlib("block", blocklib);
|
||||
openlib("console", consolelib);
|
||||
openlib("core", corelib);
|
||||
openlib("file", filelib);
|
||||
openlib("gui", guilib);
|
||||
openlib("input", inputlib);
|
||||
openlib("inventory", inventorylib);
|
||||
openlib("item", itemlib);
|
||||
openlib("json", jsonlib);
|
||||
openlib("pack", packlib);
|
||||
openlib("player", playerlib);
|
||||
openlib("time", timelib);
|
||||
openlib("world", worldlib);
|
||||
|
||||
addfunc("print", lua_wrap_errors<l_print>);
|
||||
}
|
||||
@ -149,8 +154,8 @@ void lua::LuaState::loadbuffer(int env, const std::string& src, const std::strin
|
||||
}
|
||||
}
|
||||
|
||||
int lua::LuaState::call(int argc) {
|
||||
if (lua_pcall(L, argc, LUA_MULTRET, 0)) {
|
||||
int lua::LuaState::call(int argc, int nresults) {
|
||||
if (lua_pcall(L, argc, nresults, 0)) {
|
||||
throw lua::luaerror(lua_tostring(L, -1));
|
||||
}
|
||||
return 1;
|
||||
@ -165,7 +170,7 @@ int lua::LuaState::callNoThrow(int argc) {
|
||||
}
|
||||
|
||||
int lua::LuaState::eval(int env, const std::string& src, const std::string& file) {
|
||||
auto srcText = "return ("+src+")";
|
||||
auto srcText = "return "+src;
|
||||
loadbuffer(env, srcText, file);
|
||||
return call(0);
|
||||
}
|
||||
@ -351,7 +356,7 @@ dynamic::Value lua::LuaState::tovalue(int idx) {
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"lua type "+std::to_string(type)+" is not supported"
|
||||
"lua type "+std::string(luaL_typename(L, type))+" is not supported"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -364,9 +369,9 @@ bool lua::LuaState::isfunction(int idx) {
|
||||
return lua_isfunction(L, idx);
|
||||
}
|
||||
|
||||
void lua::LuaState::openlib(const std::string& name, const luaL_Reg* libfuncs, int nup) {
|
||||
void lua::LuaState::openlib(const std::string& name, const luaL_Reg* libfuncs) {
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, libfuncs, nup);
|
||||
luaL_setfuncs(L, libfuncs, 0);
|
||||
lua_setglobal(L, name.c_str());
|
||||
}
|
||||
|
||||
@ -377,7 +382,7 @@ const std::string lua::LuaState::storeAnonymous() {
|
||||
return funcName;
|
||||
}
|
||||
|
||||
runnable lua::LuaState::createRunnable() {
|
||||
std::shared_ptr<std::string> lua::LuaState::createLambdaHandler() {
|
||||
auto ptr = reinterpret_cast<ptrdiff_t>(lua_topointer(L, -1));
|
||||
auto name = util::mangleid(ptr);
|
||||
lua_getglobal(L, LAMBDAS_TABLE.c_str());
|
||||
@ -385,13 +390,17 @@ runnable lua::LuaState::createRunnable() {
|
||||
lua_setfield(L, -2, name.c_str());
|
||||
lua_pop(L, 2);
|
||||
|
||||
std::shared_ptr<std::string> funcptr(new std::string(name), [=](auto* name) {
|
||||
return std::shared_ptr<std::string>(new std::string(name), [=](auto* name) {
|
||||
lua_getglobal(L, LAMBDAS_TABLE.c_str());
|
||||
lua_pushnil(L);
|
||||
lua_setfield(L, -2, name->c_str());
|
||||
lua_pop(L, 1);
|
||||
delete name;
|
||||
});
|
||||
}
|
||||
|
||||
runnable lua::LuaState::createRunnable() {
|
||||
auto funcptr = createLambdaHandler();
|
||||
return [=]() {
|
||||
lua_getglobal(L, LAMBDAS_TABLE.c_str());
|
||||
lua_getfield(L, -1, funcptr->c_str());
|
||||
@ -399,6 +408,23 @@ runnable lua::LuaState::createRunnable() {
|
||||
};
|
||||
}
|
||||
|
||||
scripting::common_func lua::LuaState::createLambda() {
|
||||
auto funcptr = createLambdaHandler();
|
||||
return [=](const std::vector<dynamic::Value>& args) {
|
||||
lua_getglobal(L, LAMBDAS_TABLE.c_str());
|
||||
lua_getfield(L, -1, funcptr->c_str());
|
||||
for (const auto& arg : args) {
|
||||
pushvalue(arg);
|
||||
}
|
||||
if (call(args.size(), 1)) {
|
||||
auto result = tovalue(-1);
|
||||
pop(1);
|
||||
return result;
|
||||
}
|
||||
return dynamic::Value(dynamic::NONE);
|
||||
};
|
||||
}
|
||||
|
||||
int lua::LuaState::createEnvironment(int parent) {
|
||||
int id = nextEnvironment++;
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include "lua_commons.hpp"
|
||||
|
||||
#include "../scripting_functional.hpp"
|
||||
#include "../../../data/dynamic.hpp"
|
||||
#include "../../../delegates.hpp"
|
||||
|
||||
@ -26,6 +27,8 @@ namespace lua {
|
||||
void logError(const std::string& text);
|
||||
void removeLibFuncs(const char* libname, const char* funcs[]);
|
||||
void createLibs();
|
||||
|
||||
std::shared_ptr<std::string> createLambdaHandler();
|
||||
public:
|
||||
LuaState();
|
||||
~LuaState();
|
||||
@ -56,11 +59,11 @@ namespace lua {
|
||||
const char* tostring(int idx);
|
||||
bool isstring(int idx);
|
||||
bool isfunction(int idx);
|
||||
int call(int argc);
|
||||
int call(int argc, int nresults=-1);
|
||||
int callNoThrow(int argc);
|
||||
int execute(int env, const std::string& src, const std::string& file="<string>");
|
||||
int eval(int env, const std::string& src, const std::string& file="<eval>");
|
||||
void openlib(const std::string& name, const luaL_Reg* libfuncs, int nup);
|
||||
void openlib(const std::string& name, const luaL_Reg* libfuncs);
|
||||
void addfunc(const std::string& name, lua_CFunction func);
|
||||
bool getglobal(const std::string& name);
|
||||
void setglobal(const std::string& name);
|
||||
@ -68,6 +71,8 @@ namespace lua {
|
||||
bool rename(const std::string& from, const std::string& to);
|
||||
void remove(const std::string& name);;
|
||||
runnable createRunnable();
|
||||
scripting::common_func createLambda();
|
||||
|
||||
int createEnvironment(int parent);
|
||||
void removeEnvironment(int id);
|
||||
const std::string storeAnonymous();
|
||||
|
||||
@ -20,6 +20,7 @@ extern const luaL_Reg timelib [];
|
||||
extern const luaL_Reg worldlib [];
|
||||
extern const luaL_Reg jsonlib [];
|
||||
extern const luaL_Reg inputlib [];
|
||||
extern const luaL_Reg consolelib [];
|
||||
|
||||
// Lua Overrides
|
||||
extern int l_print(lua_State* L);
|
||||
|
||||
@ -64,6 +64,9 @@ int l_set_block(lua_State* L) {
|
||||
if (id < 0 || size_t(id) >= scripting::indices->countBlockDefs()) {
|
||||
return 0;
|
||||
}
|
||||
if (!scripting::level->chunks->get(x, y, z)) {
|
||||
return 0;
|
||||
}
|
||||
scripting::level->chunks->set(x, y, z, id, states);
|
||||
scripting::level->lighting->onBlockSet(x,y,z, id);
|
||||
if (!noupdate)
|
||||
|
||||
120
src/logic/scripting/lua/libconsole.cpp
Normal file
120
src/logic/scripting/lua/libconsole.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include "api_lua.hpp"
|
||||
#include "lua_commons.hpp"
|
||||
#include "LuaState.hpp"
|
||||
|
||||
#include "../scripting.hpp"
|
||||
#include "../../CommandsInterpreter.hpp"
|
||||
#include "../../../engine.hpp"
|
||||
#include "../../../coders/commons.hpp"
|
||||
|
||||
namespace scripting {
|
||||
extern lua::LuaState* state;
|
||||
}
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static int l_add_command(lua_State* L) {
|
||||
if (!lua_isstring(L, 1) || !lua_isstring(L, 2) || !lua_isfunction(L, 3)) {
|
||||
throw std::runtime_error("invalid argument type");
|
||||
}
|
||||
auto scheme = lua_tostring(L, 1);
|
||||
auto description = lua_tostring(L, 2);
|
||||
lua_pushvalue(L, 3);
|
||||
auto func = state->createLambda();
|
||||
try {
|
||||
engine->getCommandsInterpreter()->getRepository()->add(
|
||||
scheme, description, [func](auto, auto args, auto kwargs) {
|
||||
return func({args, kwargs});
|
||||
}
|
||||
);
|
||||
} catch (const parsing_error& err) {
|
||||
throw std::runtime_error(("command scheme error:\n"+err.errorLog()).c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_execute(lua_State* L) {
|
||||
auto prompt = lua_tostring(L, 1);
|
||||
auto result = engine->getCommandsInterpreter()->execute(prompt);
|
||||
state->pushvalue(result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_set(lua_State* L) {
|
||||
auto name = lua_tostring(L, 1);
|
||||
auto value = state->tovalue(2);
|
||||
(*engine->getCommandsInterpreter())[name] = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_commands_list(lua_State* L) {
|
||||
auto interpreter = engine->getCommandsInterpreter();
|
||||
auto repo = interpreter->getRepository();
|
||||
const auto& commands = repo->getCommands();
|
||||
|
||||
lua_createtable(L, commands.size(), 0);
|
||||
size_t index = 1;
|
||||
for (const auto& entry : commands) {
|
||||
lua_pushstring(L, entry.first.c_str());
|
||||
lua_rawseti(L, -2, index++);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_get_command_info(lua_State* L) {
|
||||
auto name = lua_tostring(L, 1);
|
||||
auto interpreter = engine->getCommandsInterpreter();
|
||||
auto repo = interpreter->getRepository();
|
||||
auto command = repo->get(name);
|
||||
const auto& args = command->getArgs();
|
||||
const auto& kwargs = command->getKwArgs();
|
||||
|
||||
lua_createtable(L, 0, 4);
|
||||
|
||||
lua_pushstring(L, name);
|
||||
lua_setfield(L, -2, "name");
|
||||
|
||||
lua_pushstring(L, command->getDescription().c_str());
|
||||
lua_setfield(L, -2, "description");
|
||||
|
||||
lua_createtable(L, args.size(), 0);
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
auto& arg = args.at(i);
|
||||
lua_createtable(L, 0, 2);
|
||||
|
||||
lua_pushstring(L, arg.name.c_str());
|
||||
lua_setfield(L, -2, "name");
|
||||
|
||||
lua_pushstring(L, cmd::argtype_name(arg.type).c_str());
|
||||
lua_setfield(L, -2, "type");
|
||||
|
||||
if (arg.optional) {
|
||||
lua_pushboolean(L, true);
|
||||
lua_setfield(L, -2, "optional");
|
||||
}
|
||||
lua_rawseti(L, -2, i+1);
|
||||
}
|
||||
lua_setfield(L, -2, "args");
|
||||
|
||||
lua_createtable(L, 0, kwargs.size());
|
||||
for (auto& entry : kwargs) {
|
||||
auto& arg = entry.second;
|
||||
lua_createtable(L, 0, 1);
|
||||
|
||||
lua_pushstring(L, cmd::argtype_name(arg.type).c_str());
|
||||
lua_setfield(L, -2, "type");
|
||||
|
||||
lua_setfield(L, -2, arg.name.c_str());
|
||||
}
|
||||
lua_setfield(L, -2, "kwargs");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg consolelib [] = {
|
||||
{"add_command", lua_wrap_errors<l_add_command>},
|
||||
{"execute", lua_wrap_errors<l_execute>},
|
||||
{"set", lua_wrap_errors<l_set>},
|
||||
{"get_commands_list", lua_wrap_errors<l_get_commands_list>},
|
||||
{"get_command_info", lua_wrap_errors<l_get_command_info>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
@ -70,6 +70,14 @@ static int l_menu_reset(lua_State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_textbox_paste(lua_State* L) {
|
||||
auto node = getDocumentNode(L);
|
||||
auto box = dynamic_cast<TextBox*>(node.node.get());
|
||||
auto text = lua_tostring(L, 2);
|
||||
box->paste(util::str2wstr_utf8(text));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_container_add(lua_State* L) {
|
||||
auto docnode = getDocumentNode(L);
|
||||
auto node = dynamic_cast<Container*>(docnode.node.get());
|
||||
@ -121,6 +129,13 @@ static int p_get_back(UINode* node) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int p_get_paste(UINode* node) {
|
||||
if (dynamic_cast<TextBox*>(node)) {
|
||||
return state->pushcfunction(l_textbox_paste);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int p_get_page(UINode* node) {
|
||||
if (auto menu = dynamic_cast<Menu*>(node)) {
|
||||
return state->pushstring(menu->getCurrent().name);
|
||||
@ -186,6 +201,13 @@ static int p_is_valid(UINode* node) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int p_get_caret(UINode* node) {
|
||||
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||
return state->pushinteger(static_cast<integer_t>(box->getCaret()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int p_get_placeholder(UINode* node) {
|
||||
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||
return state->pushstring(util::wstr2str_utf8(box->getPlaceholder()));
|
||||
@ -277,6 +299,7 @@ static int l_gui_getattr(lua_State* L) {
|
||||
{"clear", p_get_clear},
|
||||
{"placeholder", p_get_placeholder},
|
||||
{"valid", p_is_valid},
|
||||
{"caret", p_get_caret},
|
||||
{"text", p_get_text},
|
||||
{"editable", p_get_editable},
|
||||
{"value", p_get_value},
|
||||
@ -289,6 +312,7 @@ static int l_gui_getattr(lua_State* L) {
|
||||
{"page", p_get_page},
|
||||
{"back", p_get_back},
|
||||
{"reset", p_get_reset},
|
||||
{"paste", p_get_paste},
|
||||
{"inventory", p_get_inventory},
|
||||
{"focused", p_get_focused},
|
||||
};
|
||||
@ -337,6 +361,11 @@ static void p_set_text(UINode* node, int idx) {
|
||||
box->setText(util::str2wstr_utf8(state->tostring(idx)));
|
||||
}
|
||||
}
|
||||
static void p_set_caret(UINode* node, int idx) {
|
||||
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||
box->setCaret(static_cast<ssize_t>(state->tointeger(idx)));
|
||||
}
|
||||
}
|
||||
static void p_set_editable(UINode* node, int idx) {
|
||||
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||
box->setEditable(state->toboolean(idx));
|
||||
@ -422,6 +451,7 @@ static int l_gui_setattr(lua_State* L) {
|
||||
{"placeholder", p_set_placeholder},
|
||||
{"text", p_set_text},
|
||||
{"editable", p_set_editable},
|
||||
{"caret", p_set_caret},
|
||||
{"value", p_set_value},
|
||||
{"min", p_set_min},
|
||||
{"max", p_set_max},
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "../../../graphics/ui/elements/InventoryView.hpp"
|
||||
#include "../../../items/Inventories.hpp"
|
||||
#include "../../../logic/BlocksController.hpp"
|
||||
#include "../../../objects/Player.hpp"
|
||||
#include "../../../util/stringutil.hpp"
|
||||
#include "../../../voxels/Block.hpp"
|
||||
#include "../../../voxels/Chunks.hpp"
|
||||
@ -121,6 +122,12 @@ static int l_hud_get_block_inventory(lua_State* L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_hud_get_player(lua_State* L) {
|
||||
auto player = scripting::hud->getPlayer();
|
||||
lua_pushinteger(L, player->getId());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg hudlib [] = {
|
||||
{"open_inventory", lua_wrap_errors<l_hud_open_inventory>},
|
||||
{"close_inventory", lua_wrap_errors<l_hud_close_inventory>},
|
||||
@ -131,5 +138,6 @@ const luaL_Reg hudlib [] = {
|
||||
{"close", lua_wrap_errors<l_hud_close>},
|
||||
{"pause", lua_wrap_errors<l_hud_pause>},
|
||||
{"resume", lua_wrap_errors<l_hud_resume>},
|
||||
{"get_player", lua_wrap_errors<l_hud_get_player>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#else
|
||||
#include <lua.hpp>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
namespace lua {
|
||||
|
||||
@ -51,6 +51,7 @@ void scripting::initialize(Engine* engine) {
|
||||
state = new lua::LuaState();
|
||||
|
||||
load_script(fs::path("stdlib.lua"));
|
||||
load_script(fs::path("stdcmd.lua"));
|
||||
}
|
||||
|
||||
scriptenv scripting::get_root_environment() {
|
||||
|
||||
@ -3,10 +3,16 @@
|
||||
|
||||
#include "../../typedefs.hpp"
|
||||
#include "../../delegates.hpp"
|
||||
#include "../../data/dynamic.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace scripting {
|
||||
using common_func = std::function<dynamic::Value(const std::vector<dynamic::Value>&)>;
|
||||
|
||||
runnable create_runnable(
|
||||
const scriptenv& env,
|
||||
const std::string& src,
|
||||
|
||||
@ -20,7 +20,7 @@ Hud* scripting::hud = nullptr;
|
||||
|
||||
void scripting::on_frontend_init(Hud* hud) {
|
||||
scripting::hud = hud;
|
||||
scripting::state->openlib("hud", hudlib, 0);
|
||||
scripting::state->openlib("hud", hudlib);
|
||||
|
||||
for (auto& pack : scripting::engine->getContentPacks()) {
|
||||
state->emit_event(pack.id + ".hudopen", [&] (lua::LuaState* state) {
|
||||
|
||||
@ -151,7 +151,7 @@ std::wstring util::str2wstr_utf8(const std::string s) {
|
||||
return std::wstring(chars.data(), chars.size());
|
||||
}
|
||||
|
||||
bool util::is_integer(std::string text) {
|
||||
bool util::is_integer(const std::string& text) {
|
||||
for (char c : text) {
|
||||
if (c < '0' || c > '9')
|
||||
return false;
|
||||
@ -159,7 +159,7 @@ bool util::is_integer(std::string text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool util::is_integer(std::wstring text) {
|
||||
bool util::is_integer(const std::wstring& text) {
|
||||
for (wchar_t c : text) {
|
||||
if (c < L'0' || c > L'9')
|
||||
return false;
|
||||
|
||||
@ -20,8 +20,8 @@ namespace util {
|
||||
uint32_t decode_utf8(uint& size, const char* bytes);
|
||||
std::string wstr2str_utf8(const std::wstring ws);
|
||||
std::wstring str2wstr_utf8(const std::string s);
|
||||
bool is_integer(std::string text);
|
||||
bool is_integer(std::wstring text);
|
||||
bool is_integer(const std::string& text);
|
||||
bool is_integer(const std::wstring& text);
|
||||
bool is_valid_filename(std::wstring name);
|
||||
|
||||
void ltrim(std::string &s);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user