Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Sebastian Schrader
Kea
Commits
115a52a6
Commit
115a52a6
authored
Apr 18, 2014
by
Marcin Siodelski
Browse files
[master] Merge branch 'trac3408'
parents
b2c673ce
c180526d
Changes
3
Hide whitespace changes
Inline
Side-by-side
src/lib/cc/data.cc
View file @
115a52a6
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2010
, 2014
Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
...
...
@@ -209,38 +209,53 @@ bool operator!=(const Element& a, const Element& b) {
// factory functions
//
ElementPtr
Element
::
create
()
{
return
(
ElementPtr
(
new
NullElement
()));
Element
::
create
(
const
Position
&
pos
)
{
return
(
ElementPtr
(
new
NullElement
(
pos
)));
}
ElementPtr
Element
::
create
(
const
long
long
int
i
)
{
return
(
ElementPtr
(
new
IntElement
(
static_cast
<
int64_t
>
(
i
))));
Element
::
create
(
const
long
long
int
i
,
const
Position
&
pos
)
{
return
(
ElementPtr
(
new
IntElement
(
static_cast
<
int64_t
>
(
i
)
,
pos
)));
}
ElementPtr
Element
::
create
(
const
double
d
)
{
return
(
ElementPtr
(
new
DoubleElement
(
d
)));
Element
::
create
(
const
int
i
,
const
Position
&
pos
)
{
return
(
create
(
static_cast
<
long
long
int
>
(
i
),
pos
));
};
ElementPtr
Element
::
create
(
const
long
int
i
,
const
Position
&
pos
)
{
return
(
create
(
static_cast
<
long
long
int
>
(
i
),
pos
));
};
ElementPtr
Element
::
create
(
const
double
d
,
const
Position
&
pos
)
{
return
(
ElementPtr
(
new
DoubleElement
(
d
,
pos
)));
}
ElementPtr
Element
::
create
(
const
std
::
string
&
s
)
{
return
(
ElementPtr
(
new
String
Element
(
s
)));
Element
::
create
(
const
bool
b
,
const
Position
&
po
s
)
{
return
(
ElementPtr
(
new
Bool
Element
(
b
,
po
s
)));
}
ElementPtr
Element
::
create
(
const
bool
b
)
{
return
(
ElementPtr
(
new
Bool
Element
(
b
)));
Element
::
create
(
const
std
::
string
&
s
,
const
Position
&
pos
)
{
return
(
ElementPtr
(
new
String
Element
(
s
,
pos
)));
}
ElementPtr
Element
::
create
List
(
)
{
return
(
ElementPtr
(
new
ListElement
()
));
Element
::
create
(
const
char
*
s
,
const
Position
&
pos
)
{
return
(
create
(
std
::
string
(
s
),
pos
));
}
ElementPtr
Element
::
createMap
()
{
return
(
ElementPtr
(
new
MapElement
()));
Element
::
createList
(
const
Position
&
pos
)
{
return
(
ElementPtr
(
new
ListElement
(
pos
)));
}
ElementPtr
Element
::
createMap
(
const
Position
&
pos
)
{
return
(
ElementPtr
(
new
MapElement
(
pos
)));
}
...
...
@@ -399,49 +414,69 @@ numberFromStringstream(std::istream& in, int& pos) {
// value is larger than an int can handle)
//
ElementPtr
fromStringstreamNumber
(
std
::
istream
&
in
,
int
&
pos
)
{
std
::
string
number
=
numberFromStringstream
(
in
,
pos
);
fromStringstreamNumber
(
std
::
istream
&
in
,
const
std
::
string
&
file
,
const
int
&
line
,
int
&
pos
)
{
// Remember position where the value starts. It will be set in the
// Position structure of the Element to be created.
const
uint32_t
start_pos
=
pos
;
// This will move the pos to the end of the value.
const
std
::
string
number
=
numberFromStringstream
(
in
,
pos
);
if
(
number
.
find_first_of
(
".eE"
)
<
number
.
size
())
{
try
{
return
(
Element
::
create
(
boost
::
lexical_cast
<
double
>
(
number
)));
return
(
Element
::
create
(
boost
::
lexical_cast
<
double
>
(
number
),
Element
::
Position
(
line
,
start_pos
)));
}
catch
(
const
boost
::
bad_lexical_cast
&
)
{
isc_throw
(
JSONError
,
std
::
string
(
"Number overflow: "
)
+
number
);
throwJSONError
(
std
::
string
(
"Number overflow: "
)
+
number
,
file
,
line
,
start_pos
);
}
}
else
{
try
{
return
(
Element
::
create
(
boost
::
lexical_cast
<
int64_t
>
(
number
)));
return
(
Element
::
create
(
boost
::
lexical_cast
<
int64_t
>
(
number
),
Element
::
Position
(
line
,
start_pos
)));
}
catch
(
const
boost
::
bad_lexical_cast
&
)
{
isc_throw
(
JSONError
,
std
::
string
(
"Number overflow: "
)
+
number
);
throwJSONError
(
std
::
string
(
"Number overflow: "
)
+
number
,
file
,
line
,
start_pos
);
}
}
return
(
ElementPtr
());
}
ElementPtr
fromStringstreamBool
(
std
::
istream
&
in
,
const
std
::
string
&
file
,
const
int
line
,
int
&
pos
)
{
// Remember position where the value starts. It will be set in the
// Position structure of the Element to be created.
const
uint32_t
start_pos
=
pos
;
// This will move the pos to the end of the value.
const
std
::
string
word
=
wordFromStringstream
(
in
,
pos
);
if
(
boost
::
iequals
(
word
,
"True"
))
{
return
(
Element
::
create
(
true
));
return
(
Element
::
create
(
true
,
Element
::
Position
(
line
,
start_pos
)
));
}
else
if
(
boost
::
iequals
(
word
,
"False"
))
{
return
(
Element
::
create
(
false
));
return
(
Element
::
create
(
false
,
Element
::
Position
(
line
,
start_pos
)
));
}
else
{
throwJSONError
(
std
::
string
(
"Bad boolean value: "
)
+
word
,
file
,
line
,
pos
);
// above is a throw shortcurt, return empty is never reached
return
(
ElementPtr
());
throwJSONError
(
std
::
string
(
"Bad boolean value: "
)
+
word
,
file
,
line
,
start_pos
);
}
return
(
ElementPtr
());
}
ElementPtr
fromStringstreamNull
(
std
::
istream
&
in
,
const
std
::
string
&
file
,
const
int
line
,
int
&
pos
)
{
// Remember position where the value starts. It will be set in the
// Position structure of the Element to be created.
const
uint32_t
start_pos
=
pos
;
// This will move the pos to the end of the value.
const
std
::
string
word
=
wordFromStringstream
(
in
,
pos
);
if
(
boost
::
iequals
(
word
,
"null"
))
{
return
(
Element
::
create
());
return
(
Element
::
create
(
Element
::
Position
(
line
,
start_pos
)
));
}
else
{
throwJSONError
(
std
::
string
(
"Bad null value: "
)
+
word
,
file
,
line
,
pos
);
throwJSONError
(
std
::
string
(
"Bad null value: "
)
+
word
,
file
,
line
,
start_pos
);
return
(
ElementPtr
());
}
}
...
...
@@ -450,7 +485,12 @@ ElementPtr
fromStringstreamString
(
std
::
istream
&
in
,
const
std
::
string
&
file
,
int
&
line
,
int
&
pos
)
{
return
(
Element
::
create
(
strFromStringstream
(
in
,
file
,
line
,
pos
)));
// Remember position where the value starts. It will be set in the
// Position structure of the Element to be created.
const
uint32_t
start_pos
=
pos
;
// This will move the pos to the end of the value.
const
std
::
string
string_value
=
strFromStringstream
(
in
,
file
,
line
,
pos
);
return
(
Element
::
create
(
string_value
,
Element
::
Position
(
line
,
start_pos
)));
}
ElementPtr
...
...
@@ -458,7 +498,7 @@ fromStringstreamList(std::istream& in, const std::string& file, int& line,
int
&
pos
)
{
int
c
=
0
;
ElementPtr
list
=
Element
::
createList
();
ElementPtr
list
=
Element
::
createList
(
Element
::
Position
(
line
,
pos
)
);
ConstElementPtr
cur_list_element
;
skipChars
(
in
,
WHITESPACE
,
line
,
pos
);
...
...
@@ -479,7 +519,7 @@ ElementPtr
fromStringstreamMap
(
std
::
istream
&
in
,
const
std
::
string
&
file
,
int
&
line
,
int
&
pos
)
{
ElementPtr
map
=
Element
::
createMap
();
ElementPtr
map
=
Element
::
createMap
(
Element
::
Position
(
line
,
pos
)
);
skipChars
(
in
,
WHITESPACE
,
line
,
pos
);
int
c
=
in
.
peek
();
if
(
c
==
EOF
)
{
...
...
@@ -594,7 +634,7 @@ Element::fromJSON(std::istream& in, const std::string& file, int& line,
case
'.'
:
in
.
putback
(
c
);
--
pos
;
element
=
fromStringstreamNumber
(
in
,
pos
);
element
=
fromStringstreamNumber
(
in
,
file
,
line
,
pos
);
el_read
=
true
;
break
;
case
't'
:
...
...
src/lib/cc/data.h
View file @
115a52a6
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2010
, 2014
Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
...
...
@@ -15,6 +15,7 @@
#ifndef ISC_DATA_H
#define ISC_DATA_H 1
#include <stdint.h>
#include <string>
#include <vector>
#include <map>
...
...
@@ -72,16 +73,73 @@ public:
///
class
Element
{
public:
/// \brief Represents the position of the data element within a
/// configuration string.
///
/// Position comprises a line number and an offset within this line
/// where the element value starts. For example, if the JSON string is
///
/// \code
/// { "foo": "some string",
/// "bar": 123 }
/// \endcode
///
/// the position of the element "bar" is: line_ = 2; pos_ = 9, because
/// begining of the value "123" is at offset 9 from the beginning of
/// the second line, including whitespaces.
///
/// Note that the @c Position structure is used as an argument to @c Element
/// constructors and factory functions to avoid ambiguity and so that the
/// uint32_t arguments holding line number and position within the line are
/// not confused with the @c Element values passed to these functions.
struct
Position
{
uint32_t
line_
;
///< Line number.
uint32_t
pos_
;
///< Position within the line.
/// \brief Constructor.
///
/// \param line Line number.
/// \param pos Position within the line.
Position
(
const
uint32_t
line
,
const
uint32_t
pos
)
:
line_
(
line
),
pos_
(
pos
)
{
}
};
/// \brief Returns @c Position object with line_ and pos_ set to 0.
///
/// The object containing two zeros is a default for most of the
/// methods creating @c Element objects. The returned value is static
/// so as it is not created everytime the function with the default
/// position argument is called.
static
const
Position
&
ZERO_POSITION
()
{
static
Position
position
(
0
,
0
);
return
(
position
);
}
private:
// technically the type could be omitted; is it useful?
// should we remove it or replace it with a pure virtual
// function getType?
int
type
;
int
type_
;
/// \brief Position of the element in the configuration string.
Position
position_
;
protected:
Element
(
int
t
)
{
type
=
t
;
}
/// \brief Constructor.
///
/// \param t Element type.
/// \param pos Structure holding position of the value of the data element.
/// It comprises the line number and the position within this line. The values
/// held in this structure are used for error logging purposes.
Element
(
int
t
,
const
Position
&
pos
=
ZERO_POSITION
())
:
type_
(
t
),
position_
(
pos
)
{
}
public:
// any is a special type used in list specifications, specifying
// that the elements can be of any type
enum
types
{
integer
,
real
,
boolean
,
null
,
string
,
list
,
map
,
any
};
...
...
@@ -89,7 +147,14 @@ public:
virtual
~
Element
()
{};
/// \return the type of this element
int
getType
()
const
{
return
(
type
);
}
int
getType
()
const
{
return
(
type_
);
}
/// \brief Returns position where the data element's value starts in a
/// configuration string.
///
/// @warning The returned reference is valid as long as the object which
/// created it lives.
const
Position
&
getPosition
()
const
{
return
(
position_
);
}
/// Returns a string representing the Element and all its
/// child elements; note that this is different from stringValue(),
...
...
@@ -282,22 +347,35 @@ public:
/// Notes: Read notes of IntElement definition about the use of
/// long long int, long int and int.
//@{
static
ElementPtr
create
();
static
ElementPtr
create
(
const
long
long
int
i
);
static
ElementPtr
create
(
const
int
i
)
{
return
(
create
(
static_cast
<
long
long
int
>
(
i
)));
};
static
ElementPtr
create
(
const
long
int
i
)
{
return
(
create
(
static_cast
<
long
long
int
>
(
i
)));
};
static
ElementPtr
create
(
const
double
d
);
static
ElementPtr
create
(
const
bool
b
);
static
ElementPtr
create
(
const
std
::
string
&
s
);
static
ElementPtr
create
(
const
Position
&
pos
=
ZERO_POSITION
());
static
ElementPtr
create
(
const
long
long
int
i
,
const
Position
&
pos
=
ZERO_POSITION
());
static
ElementPtr
create
(
const
int
i
,
const
Position
&
pos
=
ZERO_POSITION
());
static
ElementPtr
create
(
const
long
int
i
,
const
Position
&
pos
=
ZERO_POSITION
());
static
ElementPtr
create
(
const
double
d
,
const
Position
&
pos
=
ZERO_POSITION
());
static
ElementPtr
create
(
const
bool
b
,
const
Position
&
pos
=
ZERO_POSITION
());
static
ElementPtr
create
(
const
std
::
string
&
s
,
const
Position
&
pos
=
ZERO_POSITION
());
// need both std:string and char *, since c++ will match
// bool before std::string when you pass it a char *
static
ElementPtr
create
(
const
char
*
s
)
{
return
(
create
(
std
::
string
(
s
)));
}
static
ElementPtr
create
(
const
char
*
s
,
const
Position
&
pos
=
ZERO_POSITION
());
/// \brief Creates an empty ListElement type ElementPtr.
static
ElementPtr
createList
();
///
/// \param pos A structure holding position of the data element value
/// in the configuration string. It is used for error logging purposes.
static
ElementPtr
createList
(
const
Position
&
pos
=
ZERO_POSITION
());
/// \brief Creates an empty MapElement type ElementPtr.
static
ElementPtr
createMap
();
///
/// \param pos A structure holding position of the data element value
/// in the configuration string. It is used for error logging purposes.
static
ElementPtr
createMap
(
const
Position
&
pos
=
ZERO_POSITION
());
//@}
...
...
@@ -321,7 +399,8 @@ public:
/// \return An ElementPtr that contains the element(s) specified
/// in the given input stream.
static
ElementPtr
fromJSON
(
std
::
istream
&
in
)
throw
(
JSONError
);
static
ElementPtr
fromJSON
(
std
::
istream
&
in
,
const
std
::
string
&
file_name
)
throw
(
JSONError
);
static
ElementPtr
fromJSON
(
std
::
istream
&
in
,
const
std
::
string
&
file_name
)
throw
(
JSONError
);
/// Creates an Element from the given input stream, where we keep
/// track of the location in the stream for error reporting.
...
...
@@ -335,7 +414,9 @@ public:
/// \return An ElementPtr that contains the element(s) specified
/// in the given input stream.
// make this one private?
static
ElementPtr
fromJSON
(
std
::
istream
&
in
,
const
std
::
string
&
file
,
int
&
line
,
int
&
pos
)
throw
(
JSONError
);
static
ElementPtr
fromJSON
(
std
::
istream
&
in
,
const
std
::
string
&
file
,
int
&
line
,
int
&
pos
)
throw
(
JSONError
);
//@}
/// \name Type name conversion functions
...
...
@@ -386,7 +467,7 @@ public:
/// (C++ tries to convert integer type values and reference/pointer
/// if value types do not match exactly)
/// We decided the storage as int64_t,
/// three (long long, long, int) override function defintions
/// three (long long, long, int) override function defin
i
tions
/// and cast int/long/long long to int64_t via long long.
/// Therefore, call by value methods (create, setValue) have three
/// (int,long,long long) definitions. Others use int64_t.
...
...
@@ -396,7 +477,8 @@ class IntElement : public Element {
private:
public:
IntElement
(
int64_t
v
)
:
Element
(
integer
),
i
(
v
)
{
}
IntElement
(
int64_t
v
,
const
Position
&
pos
=
ZERO_POSITION
())
:
Element
(
integer
,
pos
),
i
(
v
)
{
}
int64_t
intValue
()
const
{
return
(
i
);
}
using
Element
::
getValue
;
bool
getValue
(
int64_t
&
t
)
const
{
t
=
i
;
return
(
true
);
}
...
...
@@ -410,7 +492,8 @@ class DoubleElement : public Element {
double
d
;
public:
DoubleElement
(
double
v
)
:
Element
(
real
),
d
(
v
)
{};
DoubleElement
(
double
v
,
const
Position
&
pos
=
ZERO_POSITION
())
:
Element
(
real
,
pos
),
d
(
v
)
{};
double
doubleValue
()
const
{
return
(
d
);
}
using
Element
::
getValue
;
bool
getValue
(
double
&
t
)
const
{
t
=
d
;
return
(
true
);
}
...
...
@@ -424,7 +507,8 @@ class BoolElement : public Element {
bool
b
;
public:
BoolElement
(
const
bool
v
)
:
Element
(
boolean
),
b
(
v
)
{};
BoolElement
(
const
bool
v
,
const
Position
&
pos
=
ZERO_POSITION
())
:
Element
(
boolean
,
pos
),
b
(
v
)
{};
bool
boolValue
()
const
{
return
(
b
);
}
using
Element
::
getValue
;
bool
getValue
(
bool
&
t
)
const
{
t
=
b
;
return
(
true
);
}
...
...
@@ -436,7 +520,8 @@ public:
class
NullElement
:
public
Element
{
public:
NullElement
()
:
Element
(
null
)
{};
NullElement
(
const
Position
&
pos
=
ZERO_POSITION
())
:
Element
(
null
,
pos
)
{};
void
toJSON
(
std
::
ostream
&
ss
)
const
;
bool
equals
(
const
Element
&
other
)
const
;
};
...
...
@@ -445,7 +530,8 @@ class StringElement : public Element {
std
::
string
s
;
public:
StringElement
(
std
::
string
v
)
:
Element
(
string
),
s
(
v
)
{};
StringElement
(
std
::
string
v
,
const
Position
&
pos
=
ZERO_POSITION
())
:
Element
(
string
,
pos
),
s
(
v
)
{};
std
::
string
stringValue
()
const
{
return
(
s
);
}
using
Element
::
getValue
;
bool
getValue
(
std
::
string
&
t
)
const
{
t
=
s
;
return
(
true
);
}
...
...
@@ -459,7 +545,8 @@ class ListElement : public Element {
std
::
vector
<
ConstElementPtr
>
l
;
public:
ListElement
()
:
Element
(
list
)
{}
ListElement
(
const
Position
&
pos
=
ZERO_POSITION
())
:
Element
(
list
,
pos
)
{}
const
std
::
vector
<
ConstElementPtr
>&
listValue
()
const
{
return
(
l
);
}
using
Element
::
getValue
;
bool
getValue
(
std
::
vector
<
ConstElementPtr
>&
t
)
const
{
...
...
@@ -490,8 +577,9 @@ class MapElement : public Element {
std
::
map
<
std
::
string
,
ConstElementPtr
>
m
;
public:
MapElement
()
:
Element
(
map
)
{}
// TODO: should we have direct iterators instead of exposing the std::map here?
MapElement
(
const
Position
&
pos
=
ZERO_POSITION
())
:
Element
(
map
,
pos
)
{}
// @todo should we have direct iterators instead of exposing the std::map
// here?
const
std
::
map
<
std
::
string
,
ConstElementPtr
>&
mapValue
()
const
{
return
(
m
);
}
...
...
src/lib/cc/tests/data_unittests.cc
View file @
115a52a6
// Copyright (C) 2009
Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2009
, 2014
Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
...
...
@@ -929,4 +929,87 @@ TEST(Element, merge) {
EXPECT_EQ
(
*
b
,
*
c
);
}
TEST
(
Element
,
getPosition
)
{
// Create a JSON string holding different type of values. Some of the
// values in the config string are not aligned, so as we can check that
// the position is set correctly for the elements.
ElementPtr
top
=
Element
::
fromJSON
(
"{
\n
"
"
\"
a
\"
: 2,
\n
"
"
\"
b
\"
:true,
\n
"
"
\"
cy
\"
:
\"
a string
\"
,
\n
"
"
\"
dyz
\"
: {
\n
"
"
\n
"
"
\"
e
\"
: 3,
\n
"
"
\"
f
\"
: null
\n
"
"
\n
"
" },
\n
"
"
\"
g
\"
: [ 5, 6,
\n
"
" 7 ]
\n
"
"}
\n
"
);
ASSERT_TRUE
(
top
);
// Element "a"
ConstElementPtr
level1_el
=
top
->
get
(
"a"
);
ASSERT_TRUE
(
level1_el
);
EXPECT_EQ
(
2
,
level1_el
->
getPosition
().
line_
);
EXPECT_EQ
(
11
,
level1_el
->
getPosition
().
pos_
);
// Element "b"
level1_el
=
top
->
get
(
"b"
);
ASSERT_TRUE
(
level1_el
);
EXPECT_EQ
(
3
,
level1_el
->
getPosition
().
line_
);
EXPECT_EQ
(
9
,
level1_el
->
getPosition
().
pos_
);
// Element "cy"
level1_el
=
top
->
get
(
"cy"
);
ASSERT_TRUE
(
level1_el
);
EXPECT_EQ
(
4
,
level1_el
->
getPosition
().
line_
);
EXPECT_EQ
(
11
,
level1_el
->
getPosition
().
pos_
);
// Element "dyz"
level1_el
=
top
->
get
(
"dyz"
);
ASSERT_TRUE
(
level1_el
);
EXPECT_EQ
(
5
,
level1_el
->
getPosition
().
line_
);
EXPECT_EQ
(
13
,
level1_el
->
getPosition
().
pos_
);
// Element "e" is a sub element of "dyz".
ConstElementPtr
level2_el
=
level1_el
->
get
(
"e"
);
ASSERT_TRUE
(
level2_el
);
EXPECT_EQ
(
7
,
level2_el
->
getPosition
().
line_
);
EXPECT_EQ
(
12
,
level2_el
->
getPosition
().
pos_
);
// Element "f" is also a sub element of "dyz"
level2_el
=
level1_el
->
get
(
"f"
);
ASSERT_TRUE
(
level2_el
);
EXPECT_EQ
(
8
,
level2_el
->
getPosition
().
line_
);
EXPECT_EQ
(
14
,
level2_el
->
getPosition
().
pos_
);
// Element "g" is a list.
level1_el
=
top
->
get
(
"g"
);
ASSERT_TRUE
(
level1_el
);
EXPECT_EQ
(
11
,
level1_el
->
getPosition
().
line_
);
// Position indicates where the values start (excluding the "[" character)"
EXPECT_EQ
(
11
,
level1_el
->
getPosition
().
pos_
);
// First element from the list.
level2_el
=
level1_el
->
get
(
0
);
ASSERT_TRUE
(
level2_el
);
EXPECT_EQ
(
11
,
level2_el
->
getPosition
().
line_
);
EXPECT_EQ
(
12
,
level2_el
->
getPosition
().
pos_
);
// Second element from the list.
level2_el
=
level1_el
->
get
(
1
);
ASSERT_TRUE
(
level2_el
);
EXPECT_EQ
(
11
,
level2_el
->
getPosition
().
line_
);
EXPECT_EQ
(
15
,
level2_el
->
getPosition
().
pos_
);
// Third element from the list.
level2_el
=
level1_el
->
get
(
2
);
ASSERT_TRUE
(
level2_el
);
EXPECT_EQ
(
12
,
level2_el
->
getPosition
().
line_
);
EXPECT_EQ
(
14
,
level2_el
->
getPosition
().
pos_
);
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment