Skip to content

Commit 75c7a11

Browse files
authored
Merge pull request #1884 from joto/lua-array-helpers
Add helper functions for working with Lua arrays
2 parents efb0072 + 134a96c commit 75c7a11

File tree

9 files changed

+242
-110
lines changed

9 files changed

+242
-110
lines changed

src/flex-lua-index.cpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,18 @@ static void check_and_add_columns(flex_table_t const &table,
3232
std::vector<std::string> *columns,
3333
lua_State *lua_state)
3434
{
35-
lua_pushnil(lua_state);
36-
while (lua_next(lua_state, -2) != 0) {
37-
if (!lua_isnumber(lua_state, -2)) {
38-
throw std::runtime_error{
39-
"The 'column' field must contain a string or an array."};
40-
}
35+
if (!luaX_is_array(lua_state)) {
36+
throw std::runtime_error{
37+
"The 'column' field must contain a string or an array."};
38+
}
39+
40+
luaX_for_each(lua_state, [&]() {
4141
if (!lua_isstring(lua_state, -1)) {
4242
throw std::runtime_error{
4343
"The entries in the 'column' array must be strings."};
4444
}
4545
check_and_add_column(table, columns, lua_tostring(lua_state, -1));
46-
lua_pop(lua_state, 1); // table
47-
}
46+
});
4847
}
4948

5049
void flex_lua_setup_index(lua_State *lua_state, flex_table_t *table)

src/flex-write.cpp

Lines changed: 13 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "flex-write.hpp"
1212
#include "geom-functions.hpp"
1313
#include "json-writer.hpp"
14+
#include "lua-utils.hpp"
1415
#include "wkb.hpp"
1516

1617
#include <algorithm>
@@ -131,47 +132,6 @@ static void write_double(db_copy_mgr_t<db_deleter_by_type_and_id_t> *copy_mgr,
131132

132133
using table_register_type = std::vector<void const *>;
133134

134-
/**
135-
* Check that the value on the top of the Lua stack is a simple array.
136-
* This means that all keys must be consecutive integers starting from 1.
137-
*/
138-
static bool is_lua_array(lua_State *lua_state)
139-
{
140-
uint32_t n = 1;
141-
lua_pushnil(lua_state);
142-
while (lua_next(lua_state, -2) != 0) {
143-
lua_pop(lua_state, 1); // remove value from stack
144-
#if LUA_VERSION_NUM >= 503
145-
if (!lua_isinteger(lua_state, -1)) {
146-
lua_pop(lua_state, 1);
147-
return false;
148-
}
149-
int okay = 0;
150-
auto const num = lua_tointegerx(lua_state, -1, &okay);
151-
if (!okay || num != n++) {
152-
lua_pop(lua_state, 1);
153-
return false;
154-
}
155-
#else
156-
if (!lua_isnumber(lua_state, -1)) {
157-
lua_pop(lua_state, 1);
158-
return false;
159-
}
160-
double const num = lua_tonumber(lua_state, -1);
161-
double intpart = 0.0;
162-
if (std::modf(num, &intpart) != 0.0 || intpart < 0 ||
163-
static_cast<uint32_t>(num) != n++) {
164-
lua_pop(lua_state, 1);
165-
return false;
166-
}
167-
#endif
168-
}
169-
170-
// An empty lua table could be both, we decide here that it is not stored
171-
// as a JSON array but as a JSON object.
172-
return n != 1;
173-
}
174-
175135
static void write_json(json_writer_t *writer, lua_State *lua_state,
176136
table_register_type *tables);
177137

@@ -186,19 +146,21 @@ static void write_json_table(json_writer_t *writer, lua_State *lua_state,
186146
}
187147
tables->push_back(table_ptr);
188148

189-
if (is_lua_array(lua_state)) {
149+
if (luaX_is_empty_table(lua_state)) {
150+
// An empty lua table could be both, we decide here that it is not
151+
// stored as a JSON array but as a JSON object.
152+
writer->start_object();
153+
writer->end_object();
154+
} else if (luaX_is_array(lua_state)) {
190155
writer->start_array();
191-
lua_pushnil(lua_state);
192-
while (lua_next(lua_state, -2) != 0) {
156+
luaX_for_each(lua_state, [&]() {
193157
write_json(writer, lua_state, tables);
194158
writer->next();
195-
lua_pop(lua_state, 1);
196-
}
159+
});
197160
writer->end_array();
198161
} else {
199162
writer->start_object();
200-
lua_pushnil(lua_state);
201-
while (lua_next(lua_state, -2) != 0) {
163+
luaX_for_each(lua_state, [&]() {
202164
int const ltype_key = lua_type(lua_state, -2);
203165
if (ltype_key != LUA_TSTRING) {
204166
throw fmt_error("Incorrect data type '{}' as key.",
@@ -208,8 +170,7 @@ static void write_json_table(json_writer_t *writer, lua_State *lua_state,
208170
writer->key(key);
209171
write_json(writer, lua_state, tables);
210172
writer->next();
211-
lua_pop(lua_state, 1);
212-
}
173+
});
213174
writer->end_object();
214175
}
215176
}
@@ -403,8 +364,7 @@ void flex_write_column(lua_State *lua_state,
403364
if (ltype == LUA_TTABLE) {
404365
copy_mgr->new_hash();
405366

406-
lua_pushnil(lua_state);
407-
while (lua_next(lua_state, -2) != 0) {
367+
luaX_for_each(lua_state, [&]() {
408368
char const *const key = lua_tostring(lua_state, -2);
409369
char const *const val = lua_tostring(lua_state, -1);
410370
if (key == nullptr) {
@@ -422,8 +382,7 @@ void flex_write_column(lua_State *lua_state,
422382
lua_typename(lua_state, ltype_value), key);
423383
}
424384
copy_mgr->add_hash_elem(key, val);
425-
lua_pop(lua_state, 1);
426-
}
385+
});
427386

428387
copy_mgr->finish_hash();
429388
} else {

src/geom-transform.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "geom-functions.hpp"
1212
#include "geom-transform.hpp"
1313
#include "logging.hpp"
14+
#include "lua-utils.hpp"
1415

1516
#include <osmium/osm.hpp>
1617

@@ -153,8 +154,7 @@ void init_geom_transform(geom_transform_t *transform, lua_State *lua_state)
153154
assert(transform);
154155
assert(lua_state);
155156

156-
lua_pushnil(lua_state);
157-
while (lua_next(lua_state, -2) != 0) {
157+
luaX_for_each(lua_state, [&]() {
158158
char const *const field = lua_tostring(lua_state, -2);
159159
if (field == nullptr) {
160160
throw std::runtime_error{"All fields in geometry transformation "
@@ -169,9 +169,7 @@ void init_geom_transform(geom_transform_t *transform, lua_State *lua_state)
169169
show_warning = false;
170170
}
171171
}
172-
173-
lua_pop(lua_state, 1);
174-
}
172+
});
175173
}
176174

177175
std::unique_ptr<geom_transform_t>

src/lua-utils.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,34 @@ int luaX_pcall(lua_State *lua_state, int narg, int nres)
202202
}
203203

204204
#endif
205+
206+
bool luaX_is_empty_table(lua_State *lua_state)
207+
{
208+
assert(lua_istable(lua_state, -1));
209+
lua_pushnil(lua_state);
210+
if (lua_next(lua_state, -2) == 0) {
211+
return true;
212+
}
213+
lua_pop(lua_state, 2);
214+
return false;
215+
}
216+
217+
bool luaX_is_array(lua_State *lua_state)
218+
{
219+
// Checking that a Lua table is an array is surprisingly difficult.
220+
// This code is based on:
221+
// https://web.archive.org/web/20140227143701/http://ericjmritz.name/2014/02/26/lua-is_array/
222+
assert(lua_istable(lua_state, -1));
223+
int i = 0;
224+
lua_pushnil(lua_state);
225+
while (lua_next(lua_state, -2) != 0) {
226+
++i;
227+
lua_rawgeti(lua_state, -3, i);
228+
if (lua_isnil(lua_state, -1)) {
229+
lua_pop(lua_state, 3);
230+
return false;
231+
}
232+
lua_pop(lua_state, 2);
233+
}
234+
return true;
235+
}

src/lua-utils.hpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extern "C"
1818
#include <lua.h>
1919
}
2020

21+
#include <cassert>
2122
#include <cstdint>
2223
#include <utility>
2324

@@ -64,4 +65,47 @@ bool luaX_get_table_bool(lua_State *lua_state, char const *key, int table_index,
6465

6566
int luaX_pcall(lua_State *lua_state, int narg, int nres);
6667

68+
/**
69+
* Returns true if the value on top of the stack is an empty Lua table.
70+
*
71+
* \pre Value on top of the Lua stack must be a Lua table.
72+
* \post Stack is unchanged.
73+
*/
74+
bool luaX_is_empty_table(lua_State *lua_state);
75+
76+
/**
77+
* Check that the value on the top of the Lua stack is a simple array.
78+
* This means that all keys must be consecutive integers starting from 1.
79+
*
80+
* \returns True if this is an array (also for Lua tables without any items)
81+
*
82+
* \pre Value on top of the Lua stack must be a Lua table.
83+
* \post Stack is unchanged.
84+
*/
85+
bool luaX_is_array(lua_State *lua_state);
86+
87+
/**
88+
* Call a function for each item in a Lua array table. The item value will
89+
* be on the top of the stack inside that function.
90+
*
91+
* \pre Value on top of the Lua stack must be a Lua array table.
92+
* \pre The function must leave the Lua stack in the same condition it found
93+
* it in.
94+
* \post Stack is unchanged.
95+
*/
96+
template <typename FUNC>
97+
void luaX_for_each(lua_State *lua_state, FUNC &&func)
98+
{
99+
assert(lua_istable(lua_state, -1));
100+
lua_pushnil(lua_state);
101+
while (lua_next(lua_state, -2) != 0) {
102+
#ifndef NDEBUG
103+
int const top = lua_gettop(lua_state);
104+
#endif
105+
std::forward<FUNC>(func)();
106+
assert(top == lua_gettop(lua_state));
107+
lua_pop(lua_state, 1);
108+
}
109+
}
110+
67111
#endif // OSM2PGSQL_LUA_UTILS_HPP

src/output-flex.cpp

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -577,13 +577,11 @@ void output_flex_t::setup_flex_table_columns(flex_table_t *table)
577577
table->name());
578578
}
579579

580+
if (!luaX_is_array(lua_state())) {
581+
throw std::runtime_error{"The 'columns' field must contain an array."};
582+
}
580583
std::size_t num_columns = 0;
581-
lua_pushnil(lua_state());
582-
while (lua_next(lua_state(), -2) != 0) {
583-
if (!lua_isnumber(lua_state(), -2)) {
584-
throw std::runtime_error{
585-
"The 'columns' field must contain an array."};
586-
}
584+
luaX_for_each(lua_state(), [&]() {
587585
if (!lua_istable(lua_state(), -1)) {
588586
throw std::runtime_error{
589587
"The entries in the 'columns' array must be tables."};
@@ -611,16 +609,15 @@ void output_flex_t::setup_flex_table_columns(flex_table_t *table)
611609
column.type() == table_column_type::area) {
612610
column.set_projection(lua_tostring(lua_state(), -1));
613611
} else {
614-
throw std::runtime_error{
615-
"Projection can only be set on geometry and area columns."};
612+
throw std::runtime_error{"Projection can only be set on "
613+
"geometry and area columns."};
616614
}
617615
}
618616

619-
// stack has: projection, create_only, not_null, sql_type, column,
620-
// type, table
621-
lua_pop(lua_state(), 7);
617+
// stack has: projection, create_only, not_null, sql_type, column, type
618+
lua_pop(lua_state(), 6);
622619
++num_columns;
623-
}
620+
});
624621

625622
if (num_columns == 0 && !table->has_id_column()) {
626623
throw fmt_error("No columns defined for table '{}'.", table->name());
@@ -655,21 +652,18 @@ void output_flex_t::setup_indexes(flex_table_t *table)
655652
table->name());
656653
}
657654

658-
lua_pushnil(lua_state());
659-
while (lua_next(lua_state(), -2) != 0) {
660-
if (!lua_isnumber(lua_state(), -2)) {
661-
throw std::runtime_error{
662-
"The 'indexes' field must contain an array."};
663-
}
655+
if (!luaX_is_array(lua_state())) {
656+
throw std::runtime_error{"The 'indexes' field must contain an array."};
657+
}
658+
659+
luaX_for_each(lua_state(), [&]() {
664660
if (!lua_istable(lua_state(), -1)) {
665661
throw std::runtime_error{
666662
"The entries in the 'indexes' array must be Lua tables."};
667663
}
668664

669665
flex_lua_setup_index(lua_state(), table);
670-
671-
lua_pop(lua_state(), 1);
672-
}
666+
});
673667

674668
lua_pop(lua_state(), 1); // "indexes"
675669
}
@@ -1179,25 +1173,24 @@ void output_flex_t::select_relation_members()
11791173
}
11801174

11811175
// Iterate over the 'ways' table to get all ids...
1182-
lua_pushnil(lua_state());
1183-
while (lua_next(lua_state(), -2) != 0) {
1184-
if (!lua_isnumber(lua_state(), -2)) {
1185-
throw std::runtime_error{
1186-
"Table returned from select_relation_members() contains 'ways' "
1187-
"field, but it isn't an array table."};
1188-
}
1176+
if (!luaX_is_array(lua_state())) {
1177+
throw std::runtime_error{
1178+
"Table returned from select_relation_members() contains 'ways' "
1179+
"field, but it isn't an array table."};
1180+
}
11891181

1190-
osmid_t const id = lua_tointeger(lua_state(), -1);
1191-
if (id == 0) {
1192-
throw std::runtime_error{
1193-
"Table returned from select_relation_members() contains 'ways' "
1194-
"field, which must contain an array of non-zero integer way "
1195-
"ids."};
1196-
}
1182+
luaX_for_each(
1183+
lua_state(), [&]() {
1184+
osmid_t const id = lua_tointeger(lua_state(), -1);
1185+
if (id == 0) {
1186+
throw std::runtime_error{
1187+
"Table returned from select_relation_members() contains "
1188+
"'ways' field, which must contain an array of non-zero "
1189+
"integer way ids."};
1190+
}
11971191

1198-
m_stage2_way_ids->set(id);
1199-
lua_pop(lua_state(), 1); // value pushed by lua_next()
1200-
}
1192+
m_stage2_way_ids->set(id);
1193+
});
12011194

12021195
lua_pop(lua_state(), 2); // return value (a table), ways field (a table)
12031196
}

0 commit comments

Comments
 (0)