Create a new interactive_t helper class

The purpose of this class is much like Emacs' (interactive) form: it
allows a value expression function to declare exactly how many
arguments, and of what type, it intends to receive.  It then offers
type-safe access to theese arguments in a consistent manner.

An example value expression function definition in C++:

    value_t fn_foo(call_scope_t& scope) {
      // We expect a string, an integer, and an optional date
      interactive_t args(scope, "sl&d");

      std::cout << "String  = " << args.get<string>(0)
                << "Integer = " << args.get<long>(1) << std::endl;

      if (args.has(2)) // was a date provided?
        std::cout << "Date    = " << args.get<date_t>(2) << std::endl;

      return NULL_VALUE;
    }

There is also an in_context_t<T> template, which finds the context type
T in the current scope hierarchy.  The in_context_t then also acts as a
smart pointer to reference this context object, in addition to serving
the same duty as interactive_t.  This combination of intent is solely
for the sake of brevity.

    value_t fn_bar(call_scope_t& scope) {
      in_context_t<account_t> env(scope, "sl&d");
      std::cout << "Account name = " << env->fullname()
                << "String arg   = " << env.get<string>(0)
                << std::endl;
      return NULL_VALUE;
    }

As you can see here, 'env' acts as a smart pointer to the required
context, and an object to extract the typed arguments.
This commit is contained in:
John Wiegley 2009-02-21 18:49:43 -04:00
parent 45e41b023a
commit 1f39d4148e
11 changed files with 367 additions and 152 deletions

View file

@ -37,6 +37,7 @@ libledger_expr_la_SOURCES = \
src/format.cc \
src/predicate.cc \
src/scope.cc \
src/interactive.cc \
src/expr.cc \
src/op.cc \
src/parser.cc \
@ -96,6 +97,7 @@ pkginclude_HEADERS = \
src/op.h \
src/expr.h \
src/scope.h \
src/interactive.h \
src/predicate.h \
src/format.h \
src/option.h \

View file

@ -30,6 +30,7 @@
*/
#include "account.h"
#include "interactive.h"
namespace ledger {
@ -152,11 +153,9 @@ std::ostream& operator<<(std::ostream& out, const account_t& account)
namespace {
value_t get_partial_name(call_scope_t& scope)
{
account_t& account(find_scope<account_t>(scope));
var_t<bool> flatten(scope, 0);
return string_value(account.partial_name(flatten ? *flatten : false));
in_context_t<account_t> env(scope, "&b");
return string_value(env->partial_name(env.has(0) ?
env.get<bool>(0) : false));
}
value_t get_account(account_t& account) { // this gets the name

184
src/interactive.cc Normal file
View file

@ -0,0 +1,184 @@
/*
* Copyright (c) 2003-2009, John Wiegley. 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.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* 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 "interactive.h"
namespace ledger {
void interactive_t::verify_arguments() const
{
value_t::sequence_t::const_iterator i;
const char * p = spec.c_str();
const char * label = "unknown";
bool wrong_arg = false;
bool dont_skip = false;
bool optional = *p == '&';
bool exit_loop = *p == '*';
std::size_t offset = 1;
bool is_seq = args.value().is_sequence();
const value_t * next_arg = NULL;
string vlabel;
if (is_seq) {
i = args.begin();
if (i != args.end())
next_arg = &(*i);
}
else if (! args.value().is_null()) {
next_arg = &args.value();
}
for (; ! wrong_arg && ! exit_loop && *p && next_arg; p++) {
switch (*p) {
case 'a':
label = "an amount";
wrong_arg = (! next_arg->is_long() &&
! next_arg->is_amount() &&
! next_arg->is_balance());
break;
case 'b':
label = "a boolean";
wrong_arg = false; // booleans are converted
break;
case 'd':
label = "a date";
wrong_arg = ! next_arg->is_date();
break;
case 'i':
case 'l':
label = "an integer";
if (next_arg->is_long() ||
(next_arg->is_amount() &&
! next_arg->as_amount().has_commodity())) {
wrong_arg = false;
}
else if (next_arg->is_string()) {
wrong_arg = false;
for (const char * q = next_arg->as_string().c_str(); *q; q++) {
if (! std::isdigit(*q) && *q != '-') {
wrong_arg = true;
break;
}
}
}
else {
wrong_arg = true;
}
break;
case 'm':
label = "a regex";
wrong_arg = ! next_arg->is_mask();
break;
case 's':
label = "a string";
wrong_arg = ! next_arg->is_string();
break;
case 't':
label = "a date or time";
wrong_arg = (! next_arg->is_date() &&
! next_arg->is_datetime());
break;
case 'v':
label = "any value";
wrong_arg = false;
break;
case 'P':
label = "a pointer";
wrong_arg = ! next_arg->is_pointer();
break;
case 'S':
label = "a sequence";
wrong_arg = ! next_arg->is_sequence();
break;
case '&':
optional = true;
dont_skip = true;
break;
case '*':
optional = true;
exit_loop = true;
dont_skip = true;
break;
}
if (wrong_arg)
vlabel = next_arg->label();
if (! dont_skip) {
if (is_seq) {
if (++i != args.end()) {
next_arg = &(*i);
offset++;
} else {
next_arg = NULL;
}
} else {
next_arg = NULL;
}
}
dont_skip = false;
}
if (*p == '&' || *p == '*')
optional = true;
if (wrong_arg) {
throw_(std::logic_error,
"Expected " << label << " for argument " << offset
<< ", but received " << vlabel);
}
else if (! optional && ! next_arg) {
throw_(std::logic_error, "Too few arguments to function");
}
else if (! *p && next_arg) {
throw_(std::logic_error, "Too many arguments to function");
}
}
string join_args(call_scope_t& args)
{
std::ostringstream buf;
bool first = true;
for (std::size_t i = 0; i < args.size(); i++) {
if (first) {
buf << args[i];
first = false;
} else {
buf << ' ' << args[i];
}
}
return buf.str();
}
} // namespace ledger

142
src/interactive.h Normal file
View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2003-2009, John Wiegley. 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.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* 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.
*/
/**
* @addtogroup expr
*/
/**
* @file interactive.h
* @author John Wiegley
*
* @ingroup expr
*
* @brief Brief
*
* Long.
*/
#ifndef _INTERACTIVE_H
#define _INTERACTIVE_H
#include "scope.h"
namespace ledger {
/**
* @brief Brief
*
* Long.
*/
class interactive_t : public noncopyable
{
call_scope_t& args;
string spec;
public:
explicit interactive_t(call_scope_t& _args, const string& _spec = "")
: args(_args), spec(_spec) {
TRACE_CTOR(interactive_t, "call_scope_t&, const string&");
verify_arguments();
}
virtual ~interactive_t() {
TRACE_DTOR(interactive_t);
}
void verify_arguments() const;
bool has(std::size_t index) const {
if (index < args.size())
return true;
return false;
}
const value_t& value_at(std::size_t index) const {
assert(has(index));
return args[index];
}
template <typename T>
T get(std::size_t index) const;
};
template <>
inline bool interactive_t::get<bool>(std::size_t index) const {
return value_at(index).to_boolean();
}
template <>
inline long interactive_t::get<long>(std::size_t index) const {
return value_at(index).to_long();
}
template <>
inline string interactive_t::get<string>(std::size_t index) const {
return value_at(index).to_string();
}
template <>
inline mask_t interactive_t::get<mask_t>(std::size_t index) const {
return value_at(index).to_mask();
}
template <>
inline date_t interactive_t::get<date_t>(std::size_t index) const {
return value_at(index).to_date();
}
template <>
inline datetime_t interactive_t::get<datetime_t>(std::size_t index) const {
return value_at(index).to_datetime();
}
template <typename T>
class in_context_t : public interactive_t
{
T& context;
public:
explicit in_context_t(call_scope_t& args, const string& spec)
: interactive_t(args, spec), context(find_scope<T>(args)) {
TRACE_CTOR(in_context_t, "call_scope_t&, const string&");
}
virtual ~in_context_t() {
TRACE_DTOR(in_context_t);
}
T& operator *() {
return context;
}
T * operator->() {
return &context;
}
};
string join_args(call_scope_t& args);
} // namespace ledger
#endif // _INTERACTIVE_H

View file

@ -30,6 +30,7 @@
*/
#include "precmd.h"
#include "interactive.h"
#include "report.h"
namespace ledger {

View file

@ -46,10 +46,12 @@
#ifndef _PRECMD_H
#define _PRECMD_H
#include "scope.h"
#include "value.h"
namespace ledger {
class call_scope_t;
value_t parse_command(call_scope_t& args);
value_t eval_command(call_scope_t& args);
value_t format_command(call_scope_t& args);

View file

@ -30,6 +30,7 @@
*/
#include "report.h"
#include "interactive.h"
#include "iterators.h"
#include "filters.h"
#include "chain.h"
@ -121,19 +122,20 @@ value_t report_t::fn_display_total(call_scope_t& scope)
value_t report_t::fn_market_value(call_scope_t& args)
{
var_t<datetime_t> date(args, 1);
var_t<string> in_terms_of(args, 2);
interactive_t env(args, "a&ts");
commodity_t * commodity = NULL;
if (in_terms_of)
commodity = amount_t::current_pool->find_or_create(*in_terms_of);
if (env.has(2))
commodity = amount_t::current_pool->find_or_create(env.get<string>(2));
DEBUG("report.market", "getting market value of: " << args[0]);
DEBUG("report.market", "getting market value of: " << env.value_at(0));
value_t result =
args[0].value(date ? optional<datetime_t>(*date) : optional<datetime_t>(),
commodity ? optional<commodity_t&>(*commodity) :
optional<commodity_t&>());
env.value_at(0).value(env.has(1) ?
env.get<datetime_t>(1) : optional<datetime_t>(),
commodity ?
optional<commodity_t&>(*commodity) :
optional<commodity_t&>());
DEBUG("report.market", "result is: " << result);
return result;
@ -160,28 +162,24 @@ value_t report_t::fn_quantity(call_scope_t& args)
value_t report_t::fn_truncate(call_scope_t& args)
{
var_t<long> width(args, 1);
var_t<long> account_abbrev(args, 2);
return string_value(format_t::truncate(args[0].as_string(),
width && *width > 0 ? *width : 0,
account_abbrev ? *account_abbrev : -1));
interactive_t env(args, "v&ll");
return string_value(format_t::truncate
(env.get<string>(0),
env.has(1) && env.get<long>(1) > 0 ? env.get<long>(1) : 0,
env.has(2) ? env.get<long>(2) : -1));
}
value_t report_t::fn_print(call_scope_t& args)
{
var_t<long> first_width(args, 1);
var_t<long> latter_width(args, 2);
var_t<string> date_format(args, 3);
interactive_t env(args, "vl&ls");
std::ostringstream out;
args[0].strip_annotations(what_to_keep())
.print(out, *first_width, latter_width ? *latter_width : -1,
date_format ? *date_format :
env.value_at(0)
.strip_annotations(what_to_keep())
.print(out, env.get<long>(1),
env.has(2) ? env.get<long>(2) : -1,
env.has(3) ? env.get<string>(3) :
(HANDLED(date_format_) ?
HANDLER(date_format_).str() : optional<string>()));
return string_value(out.str());
}

View file

@ -502,13 +502,13 @@ public:
"3 + date_width + payee_width + account_width + amount_width))"
" %(print(strip(display_total), total_width, "
"4 + date_width + payee_width + account_width + amount_width "
"+ total_width, true))\n%/"
"+ total_width))\n%/"
"%(print(\" \", 2 + date_width + payee_width))"
"%(print(truncate(account, account_width, abbrev_len), account_width))"
" %(print(strip(display_amount), amount_width, 3 + date_width "
"+ payee_width + account_width + amount_width))"
" %(print(strip(display_total), total_width, 4 + date_width "
"+ payee_width + account_width + amount_width + total_width, true))\n");
"+ payee_width + account_width + amount_width + total_width))\n");
});
OPTION(report_t, related); // -r

View file

@ -60,21 +60,4 @@ expr_t::ptr_op_t symbol_scope_t::lookup(const string& name)
return child_scope_t::lookup(name);
}
string join_args(call_scope_t& args)
{
std::ostringstream buf;
bool first = true;
for (std::size_t i = 0; i < args.size(); i++) {
if (first) {
buf << args[i];
first = false;
} else {
buf << ' ' << args[i];
}
}
return buf.str();
}
} // namespace ledger

View file

@ -158,13 +158,9 @@ public:
}
void set_args(const value_t& _args) {
if (_args.is_sequence())
args = _args;
else
args = _args.to_sequence();
args = _args;
}
value_t& value() {
assert(args.is_null() || args.is_sequence());
return args;
}
@ -291,97 +287,6 @@ public:
T * operator->() { return value; }
};
/**
* @brief Brief
*
* Long.
*/
template <typename T>
class var_t : public noncopyable
{
optional<value_t> value;
var_t();
public:
var_t(scope_t& scope, const string& name)
{
TRACE_CTOR(var_t, "scope_t&, const string&");
try {
value = scope.resolve(name);
}
catch (...) {
DEBUG("scope.var_t", "Failed lookup var_t(\"" << name << "\")");
value = none;
}
}
var_t(call_scope_t& scope, const std::size_t idx)
{
TRACE_CTOR(var_t, "call_scope_t&, const std::size_t");
if (idx < scope.size())
value = scope[idx];
else
value = none;
}
~var_t() throw() {
TRACE_DTOR(var_t);
}
operator bool() { return value; }
T operator *();
T operator *() const;
T * operator->() {
return &**this;
}
const T * operator->() const {
return &**this;
}
};
template <>
inline bool var_t<bool>::operator *() {
return value->to_boolean();
}
template <>
inline bool var_t<bool>::operator *() const {
return value->to_boolean();
}
template <>
inline long var_t<long>::operator *() {
return value->to_long();
}
template <>
inline long var_t<long>::operator *() const {
return value->to_long();
}
template <>
inline string var_t<string>::operator *() {
return value->to_string();
}
template <>
inline string var_t<string>::operator *() const {
return value->to_string();
}
template <>
inline datetime_t var_t<datetime_t>::operator *() {
return value->to_datetime();
}
template <>
inline datetime_t var_t<datetime_t>::operator *() const {
return value->to_datetime();
}
string join_args(call_scope_t& args);
} // namespace ledger
#endif // _SCOPE_H

View file

@ -32,6 +32,7 @@
#include "xact.h"
#include "journal.h"
#include "account.h"
#include "interactive.h"
#include "format.h"
namespace ledger {
@ -186,17 +187,15 @@ namespace {
value_t get_account(call_scope_t& scope)
{
xact_t& xact(find_scope<xact_t>(scope));
in_context_t<xact_t> env(scope, "&l");
var_t<long> max_width(scope, 0);
string name = env->reported_account()->fullname();
string name = xact.reported_account()->fullname();
if (env.has(0) && env.get<long>(0) > 2)
name = format_t::truncate(name, env.get<long>(0) - 2, true);
if (max_width && *max_width > 2)
name = format_t::truncate(name, *max_width - 2, true);
if (xact.has_flags(XACT_VIRTUAL)) {
if (xact.must_balance())
if (env->has_flags(XACT_VIRTUAL)) {
if (env->must_balance())
name = string("[") + name + "]";
else
name = string("(") + name + ")";