Static remapping with url scheme (#227)

* DIRTY lexer with bugs rosservice: and rostopic:

* Fix bug with partial url schemes

* Style fixes

* Moved lexer.h to satisfy cpplint

* moved terminals to their own array to reduce code size

* Shrink lexer by using char, add error checking

* Comment/whitespace

* comment

* terminal -> lexeme where appropriate

* Static const global

* Documentation and argument order

* Add rcl_lexer_lookahead2_t

* Allow ignoring lexeme text

* Beginnings of recursive descent parser

* Add method to get current position in text

* new remap parsing passes unit tests

* Test rosservice:// and rostopic://

* fix movement formula comment

* doxygent comment

* move code to make pr diff easier to read

* Comments

* Comment

* Comment about impossibilities

* Set error message

* unsigned literals

* Add a couple more url scheme tests

* remove out of date comment

* end_pos -> length

* another token text

* Whitespace

* call accept -> accept

* use array instead of number suffix

* Missing return; wrong comment

* test methods at end of input

* Test not zero-initialized init

* Test lexing remapping rules

* Windows warning

* Remove const to avoid discarding during cast
This commit is contained in:
Shane Loretz 2018-04-23 10:57:59 -07:00 committed by GitHub
parent c51f8925f4
commit ff024ee9fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 2532 additions and 163 deletions

View file

@ -33,6 +33,8 @@ set(${PROJECT_NAME}_sources
src/rcl/expand_topic_name.c
src/rcl/graph.c
src/rcl/guard_condition.c
src/rcl/lexer.c
src/rcl/lexer_lookahead.c
src/rcl/node.c
src/rcl/publisher.c
src/rcl/rcl.c

118
rcl/include/rcl/lexer.h Normal file
View file

@ -0,0 +1,118 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef RCL__LEXER_H_
#define RCL__LEXER_H_
#include <stddef.h>
#include "rcl/allocator.h"
#include "rcl/macros.h"
#include "rcl/types.h"
#include "rcl/visibility_control.h"
#if __cplusplus
extern "C"
{
#endif
/// Type of lexeme found by lexical analysis.
typedef enum rcl_lexeme_t
{
/// Indicates no valid lexeme was found
RCL_LEXEME_NONE = 0,
/// Indicates end of input has been reached
RCL_LEXEME_EOF = 1,
/// ~/
RCL_LEXEME_TILDE_SLASH = 2,
/// rosservice://
RCL_LEXEME_URL_SERVICE = 3,
/// rostopic://
RCL_LEXEME_URL_TOPIC = 4,
/// :
RCL_LEXEME_COLON = 5,
/// __node
RCL_LEXEME_NODE = 6,
/// __ns
RCL_LEXEME_NS = 7,
/// :=
RCL_LEXEME_SEPARATOR = 8,
/// \1
RCL_LEXEME_BR1 = 9,
/// \2
RCL_LEXEME_BR2 = 10,
/// \3
RCL_LEXEME_BR3 = 11,
/// \4
RCL_LEXEME_BR4 = 12,
/// \5
RCL_LEXEME_BR5 = 13,
/// \6
RCL_LEXEME_BR6 = 14,
/// \7
RCL_LEXEME_BR7 = 15,
/// \8
RCL_LEXEME_BR8 = 16,
/// \9
RCL_LEXEME_BR9 = 17,
/// a name between slashes, must match (([a-zA-Z](_)?)|_)([0-9a-zA-Z](_)?)*
RCL_LEXEME_TOKEN = 18,
/// /
RCL_LEXEME_FORWARD_SLASH = 19,
/// *
RCL_LEXEME_WILD_ONE = 20,
/// **
RCL_LEXEME_WILD_MULTI = 21
} rcl_lexeme_t;
/// Do lexical analysis on a string.
/**
* This function analyzes a string to see if it starts with a valid lexeme.
* If the string does not begin with a valid lexeme then lexeme will be RCL_LEXEME_NONE, and the
* length will be set to include the character that made it impossible.
* If the first character is '\0' then lexeme will be RCL_LEXEME_EOF.
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes [1]
* Thread-Safe | Yes
* Uses Atomics | No
* Lock-Free | Yes
* <i>[1] Only allocates if an argument is invalid or an internal bug is detected.</i>
*
* \param[in] text The string to analyze.
* \param[in] allocator An allocator to use if an error occurs.
* \param[out] lexeme The type of lexeme found in the string.
* \param[out] length The length of text in the string that constitutes the found lexeme.
* \return `RCL_RET_OK` if analysis is successful regardless whether a valid lexeme is found, or
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
* \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or
* \return `RCL_RET_ERROR` if an internal bug is detected.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_lexer_analyze(
const char * text,
rcl_allocator_t allocator,
rcl_lexeme_t * lexeme,
size_t * length);
#if __cplusplus
}
#endif
#endif // RCL__LEXER_H_

View file

@ -0,0 +1,261 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef RCL__LEXER_LOOKAHEAD_H_
#define RCL__LEXER_LOOKAHEAD_H_
#include <stddef.h>
#include "rcl/allocator.h"
#include "rcl/lexer.h"
#include "rcl/macros.h"
#include "rcl/types.h"
#include "rcl/visibility_control.h"
#if __cplusplus
extern "C"
{
#endif
// Forward declaration
struct rcl_lexer_lookahead2_impl_t;
/// Track lexical analysis and allow looking ahead 2 lexemes.
typedef struct rcl_lexer_lookahead2_t
{
struct rcl_lexer_lookahead2_impl_t * impl;
} rcl_lexer_lookahead2_t;
/// Get a zero initialized rcl_lexer_lookahead2_t instance.
/**
* \sa rcl_lexer_lookahead2_init()
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | No
* Thread-Safe | Yes
* Uses Atomics | No
* Lock-Free | Yes
*
* \return zero initialized lookahead2 buffer.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_lexer_lookahead2_t
rcl_get_zero_initialized_lexer_lookahead2();
/// Initialize an rcl_lexer_lookahead2_t instance.
/**
* The lookahead2 buffer borrows a reference to the provided text.
* The text must not be freed before the buffer is finalized.
* The lookahead2 buffer only needs to be finalized if this function does not return RCL_RET_OK.
* \sa rcl_lexer_lookahead2_fini()
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes
* Thread-Safe | No
* Uses Atomics | No
* Lock-Free | Yes
*
* \param[in] buffer A buffer that is zero initialized.
* \sa rcl_get_zero_initialized_lexer_lookahead2()
* \param[in] text The string to analyze.
* \param[in] allocator An allocator to use if an error occurs.
* \return `RCL_RET_OK` if the buffer is successfully initialized, or
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
* \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or
* \return `RCL_RET_ERROR` if an unspecified error occurrs.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_lexer_lookahead2_init(
rcl_lexer_lookahead2_t * buffer,
const char * text,
rcl_allocator_t allocator);
/// Finalize an instance of an rcl_lexer_lookahead2_t structure.
/**
* \sa rcl_lexer_lookahead2_init()
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes [1]
* Thread-Safe | No
* Uses Atomics | No
* Lock-Free | Yes
* <i>[1] Only allocates if an argument is invalid.</i>
*
* \param[in] buffer The structure to be deallocated.
* \return `RCL_RET_OK` if the structure was successfully finalized, or
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
* \return `RCL_RET_ERROR` if an unspecified error occurs.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_lexer_lookahead2_fini(
rcl_lexer_lookahead2_t * buffer);
/// Look ahead at the next lexeme in the string.
/**
* Repeated calls to peek will return the same lexeme.
* A parser that deems the next lexeme as valid must accept it to advance lexing.
* \sa rcl_lexer_lookahead2_accept()
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes [1]
* Thread-Safe | No
* Uses Atomics | No
* Lock-Free | Yes
* <i>[1] Only allocates if an argument is invalid or an internal bug is detected.</i>
*
* \param[in] buffer the lookahead2 buffer being used to analyze a string.
* \param[out] next_type an output variable for the next lexeme in the string.
* \return `RCL_RET_OK` if peeking was successfull, or
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
* \return `RCL_RET_ERROR` if an unspecified error occurs.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_lexer_lookahead2_peek(
rcl_lexer_lookahead2_t * buffer,
rcl_lexeme_t * next_type);
/// Look ahead at the next two lexemes in the string.
/**
* Repeated calls to peek2 will return the same two lexemes.
* A parser that deems the next two lexemes as valid must accept twice to advance lexing.
* \sa rcl_lexer_lookahead2_accept()
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes [1]
* Thread-Safe | No
* Uses Atomics | No
* Lock-Free | Yes
* <i>[1] Only allocates if an argument is invalid or an internal bug is detected.</i>
*
* \param[in] buffer the lookahead2 buffer being used to analyze a string.
* \param[out] next_type1 an output variable for the next lexeme in the string.
* \param[out] next_type2 an output variable for the lexeme after the next lexeme in the string.
* \return `RCL_RET_OK` if peeking was successfull, or
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
* \return `RCL_RET_ERROR` if an unspecified error occurs.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_lexer_lookahead2_peek2(
rcl_lexer_lookahead2_t * buffer,
rcl_lexeme_t * next_type1,
rcl_lexeme_t * next_type2);
/// Accept a lexeme and advance analysis.
/**
* A token must have been peeked before it can be accepted.
* \sa rcl_lexer_lookahead2_peek()
* \sa rcl_lexer_lookahead2_peek2()
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes [1]
* Thread-Safe | No
* Uses Atomics | No
* Lock-Free | Yes
* <i>[1] Only allocates if an argument is invalid or an error occurs.</i>
*
* \param[in] buffer the lookahead2 buffer being used to analyze a string.
* \param[out] lexeme_text pointer to where lexeme begins in string.
* \param[out] lexeme_text_length length of lexeme_text.
* \return `RCL_RET_OK` if peeking was successfull, or
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
* \return `RCL_RET_ERROR` if an unspecified error occurs.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_lexer_lookahead2_accept(
rcl_lexer_lookahead2_t * buffer,
const char ** lexeme_text,
size_t * lexeme_text_length);
/// Require the next lexeme to be a certain type and advance analysis.
/**
* This method is a shortcut to peeking and accepting a lexeme.
* It should be used by a parser when there is only one valid lexeme that could come next.
* \sa rcl_lexer_lookahead2_peek()
* \sa rcl_lexer_lookahead2_accept()
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes [1]
* Thread-Safe | No
* Uses Atomics | No
* Lock-Free | Yes
* <i>[1] Only allocates if an argument is invalid or an error occurs.</i>
*
* \param[in] buffer the lookahead2 buffer being used to analyze a string.
* \param[in] type the type the next lexeme must be.
* \param[out] lexeme_text pointer to where lexeme begins in string.
* \param[out] lexeme_text_length length of lexeme_text.
* \return `RCL_RET_OK` if the next lexeme was the expected one, or
* \return `RCL_RET_WRONG_LEXEME` if the next lexeme was not the expected one, or
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
* \return `RCL_RET_ERROR` if an unspecified error occurs.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_lexer_lookahead2_expect(
rcl_lexer_lookahead2_t * buffer,
rcl_lexeme_t type,
const char ** lexeme_text,
size_t * lexeme_text_length);
/// Get the text at the point where it is currently being analyzed.
/**
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | No
* Thread-Safe | Yes
* Uses Atomics | No
* Lock-Free | Yes
*
* \param[in] buffer the lookahead2 buffer being used to analyze a string.
* \return a pointer inside the original text at the position being analyzed, or
* \return `NULL` if buffer is itself `NULL` or zero initialized, or
* \return an undefined value if buffer is not initialized or has been finalized.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
const char *
rcl_lexer_lookahead2_get_text(
const rcl_lexer_lookahead2_t * buffer);
#if __cplusplus
}
#endif
#endif // RCL__LEXER_LOOKAHEAD_H_

View file

@ -90,5 +90,7 @@ typedef rmw_ret_t rcl_ret_t;
// rcl argument parsing specific ret codes in 1XXX
/// Argument is not a valid remap rule
#define RCL_RET_INVALID_REMAP_RULE 1001
/// Expected one type of lexeme but got another
#define RCL_RET_WRONG_LEXEME 1002
#endif // RCL__TYPES_H_

View file

@ -19,6 +19,7 @@
#include "./arguments_impl.h"
#include "./remap_impl.h"
#include "rcl/error_handling.h"
#include "rcl/lexer_lookahead.h"
#include "rcl/validate_topic_name.h"
#include "rcutils/allocator.h"
#include "rcutils/logging_macros.h"
@ -48,169 +49,7 @@ rcl_ret_t
_rcl_parse_remap_rule(
const char * arg,
rcl_allocator_t allocator,
rcl_remap_t * output_rule)
{
RCL_CHECK_ARGUMENT_FOR_NULL(arg, RCL_RET_INVALID_ARGUMENT, allocator);
RCL_CHECK_ARGUMENT_FOR_NULL(output_rule, RCL_RET_INVALID_ARGUMENT, allocator);
size_t len_node_name = 0;
size_t len_match = 0;
size_t len_replacement = 0;
const char * separator = NULL;
const char * colon = NULL;
const char * match_begin = arg;
const char * replacement_begin = NULL;
// A valid rule has two parts separated by :=
separator = strstr(arg, ":=");
if (NULL == separator) {
RCL_SET_ERROR_MSG("missing :=", allocator);
return RCL_RET_INVALID_REMAP_RULE;
}
replacement_begin = separator + 2;
// must have characters on both sides of the separator
len_match = separator - arg;
len_replacement = strlen(replacement_begin);
if (0 == len_match) {
RCL_SET_ERROR_MSG("match is zero length", allocator);
return RCL_RET_INVALID_REMAP_RULE;
} else if (0 == len_replacement) {
RCL_SET_ERROR_MSG("replacement has zero length", allocator);
return RCL_RET_INVALID_REMAP_RULE;
}
colon = strchr(arg, ':');
if (NULL != colon) {
if (colon < separator) {
// If there is a : on the match side then there is a node-name prefix
match_begin = colon + 1;
len_node_name = colon - arg;
len_match = separator - match_begin;
// node name must have at least one character
if (len_node_name <= 0) {
RCL_SET_ERROR_MSG("node name previx has zero length", allocator);
return RCL_RET_INVALID_REMAP_RULE;
}
} else if (colon > separator) {
// If the colon is on the replacement side then this couldn't be a valid rule
RCL_SET_ERROR_MSG("replacement side cannot contain a :", allocator);
return RCL_RET_INVALID_REMAP_RULE;
}
}
// Maybe match length changed because there was a node name prefix
if (0 == len_match) {
RCL_SET_ERROR_MSG("match is zero length", allocator);
return RCL_RET_INVALID_REMAP_RULE;
}
// Make sure node name contains only valid characters
if (len_node_name) {
int validation_result;
size_t invalid_index;
rmw_ret_t rmw_ret = rmw_validate_node_name_with_size(
arg, len_node_name, &validation_result, &invalid_index);
if (RMW_RET_OK != rmw_ret) {
RCL_SET_ERROR_MSG("failed to run check on node name", allocator);
return RCL_RET_ERROR;
} else if (RMW_NODE_NAME_VALID != validation_result) {
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
allocator,
"node name prefix invalid: %s", rmw_node_name_validation_result_string(validation_result));
return RCL_RET_INVALID_REMAP_RULE;
}
}
// Figure out what type of rule this is, default is to apply to topic and service names
rcl_remap_type_t type = RCL_TOPIC_REMAP | RCL_SERVICE_REMAP;
if (4 == len_match && 0 == strncmp("__ns", match_begin, len_match)) {
type = RCL_NAMESPACE_REMAP;
} else if (6 == len_match && 0 == strncmp("__node", match_begin, len_match)) {
type = RCL_NODENAME_REMAP;
}
if (type & (RCL_TOPIC_REMAP | RCL_SERVICE_REMAP)) {
// Replacement must be a valid topic name
int validation_result;
size_t invalid_index;
rcl_ret_t ret = rcl_validate_topic_name(replacement_begin, &validation_result, &invalid_index);
if (ret != RCL_RET_OK) {
return RCL_RET_ERROR;
} else if (validation_result != RCL_TOPIC_NAME_VALID) {
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
allocator,
"replacement is invalid: %s", rcl_topic_name_validation_result_string(validation_result));
return RCL_RET_INVALID_REMAP_RULE;
}
// Match must be a valid topic name
ret = rcl_validate_topic_name_with_size(
match_begin, len_match, &validation_result, &invalid_index);
if (ret != RCL_RET_OK) {
return RCL_RET_ERROR;
} else if (validation_result != RCL_TOPIC_NAME_VALID) {
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
allocator,
"match is invalid: %s", rcl_topic_name_validation_result_string(validation_result));
return RCL_RET_INVALID_REMAP_RULE;
}
} else if (RCL_NAMESPACE_REMAP == type) {
int validation_result;
size_t invalid_idx;
rmw_ret_t rmw_ret = rmw_validate_namespace(replacement_begin, &validation_result, &invalid_idx);
if (RMW_RET_OK != rmw_ret) {
RCL_SET_ERROR_MSG("failed to run check on namespace", allocator);
return RCL_RET_ERROR;
} else if (RMW_NAMESPACE_VALID != validation_result) {
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
allocator,
"namespace is invalid: %s", rmw_namespace_validation_result_string(validation_result));
return RCL_RET_INVALID_REMAP_RULE;
}
} else if (RCL_NODENAME_REMAP == type) {
int validation_result;
size_t invalid_idx;
rmw_ret_t rmw_ret = rmw_validate_node_name(replacement_begin, &validation_result, &invalid_idx);
if (RMW_RET_OK != rmw_ret) {
RCL_SET_ERROR_MSG("failed to run check on node name", allocator);
return RCL_RET_ERROR;
} else if (RMW_NODE_NAME_VALID != validation_result) {
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
allocator,
"node name is invalid: %s", rmw_node_name_validation_result_string(validation_result));
return RCL_RET_INVALID_REMAP_RULE;
}
}
// Rule is valid, construct a structure for it
output_rule->allocator = allocator;
output_rule->type = type;
if (len_node_name > 0) {
output_rule->node_name = rcutils_strndup(arg, len_node_name, allocator);
if (NULL == output_rule->node_name) {
goto cleanup_rule;
}
}
if (type & (RCL_TOPIC_REMAP | RCL_SERVICE_REMAP)) {
output_rule->match = rcutils_strndup(match_begin, len_match, allocator);
if (NULL == output_rule->match) {
goto cleanup_rule;
}
}
output_rule->replacement = rcutils_strndup(replacement_begin, len_replacement, allocator);
if (NULL == output_rule->replacement) {
goto cleanup_rule;
}
return RCL_RET_OK;
cleanup_rule:
if (RCL_RET_OK != rcl_remap_fini(output_rule)) {
RCUTILS_LOG_ERROR_NAMED(ROS_PACKAGE_NAME, "Failed to fini remap rule after error occurred");
}
return RCL_RET_BAD_ALLOC;
}
rcl_remap_t * output_rule);
rcl_ret_t
rcl_parse_arguments(
@ -447,6 +286,497 @@ rcl_get_global_arguments()
return &__rcl_global_arguments;
}
/// Parses a fully qualified namespace for a namespace replacement rule (ex: `/foo/bar`)
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_fully_qualified_namespace(
rcl_lexer_lookahead2_t * lex_lookahead)
{
rcl_ret_t ret;
// Must have at least one Forward slash /
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_FORWARD_SLASH, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
// repeated tokens and slashes (allow trailing slash, but don't require it)
while (true) {
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_TOKEN, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
rcl_reset_error();
break;
}
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_FORWARD_SLASH, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
rcl_reset_error();
break;
}
}
return RCL_RET_OK;
}
/// Parse either a token or a backreference (ex: `bar`, or `\7`).
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_replacement_token(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
rcl_lexeme_t lexeme;
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
if (
RCL_LEXEME_BR1 == lexeme || RCL_LEXEME_BR2 == lexeme || RCL_LEXEME_BR3 == lexeme ||
RCL_LEXEME_BR4 == lexeme || RCL_LEXEME_BR5 == lexeme || RCL_LEXEME_BR6 == lexeme ||
RCL_LEXEME_BR7 == lexeme || RCL_LEXEME_BR8 == lexeme || RCL_LEXEME_BR9 == lexeme)
{
RCL_SET_ERROR_MSG("Backreferences are not implemented", rule->allocator);
return RCL_RET_ERROR;
} else if (RCL_LEXEME_TOKEN == lexeme) {
ret = rcl_lexer_lookahead2_accept(lex_lookahead, NULL, NULL);
} else {
ret = RCL_RET_INVALID_REMAP_RULE;
}
return ret;
}
/// Parse the replacement side of a name remapping rule (ex: `bar/\1/foo`).
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_replacement_name(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
rcl_lexeme_t lexeme;
const char * replacement_start = rcl_lexer_lookahead2_get_text(lex_lookahead);
if (NULL == replacement_start) {
RCL_SET_ERROR_MSG("failed to get start of replacement", rule->allocator);
return RCL_RET_ERROR;
}
// private name (~/...) or fully qualified name (/...) ?
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
if (RCL_LEXEME_TILDE_SLASH == lexeme || RCL_LEXEME_FORWARD_SLASH == lexeme) {
ret = rcl_lexer_lookahead2_accept(lex_lookahead, NULL, NULL);
}
if (RCL_RET_OK != ret) {
return ret;
}
// token ( '/' token )*
ret = _rcl_parse_remap_replacement_token(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
while (RCL_LEXEME_EOF != lexeme) {
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_FORWARD_SLASH, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
ret = _rcl_parse_remap_replacement_token(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
}
// Copy replacement into rule
const char * replacement_end = rcl_lexer_lookahead2_get_text(lex_lookahead);
size_t length = (size_t)(replacement_end - replacement_start);
rule->replacement = rcutils_strndup(replacement_start, length, rule->allocator);
if (NULL == rule->replacement) {
RCL_SET_ERROR_MSG("failed to copy replacement", rule->allocator);
return RCL_RET_BAD_ALLOC;
}
return RCL_RET_OK;
}
/// Parse either a token or a wildcard (ex: `foobar`, or `*`, or `**`).
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_match_token(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
rcl_lexeme_t lexeme;
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
if (RCL_LEXEME_TOKEN == lexeme) {
ret = rcl_lexer_lookahead2_accept(lex_lookahead, NULL, NULL);
} else if (RCL_LEXEME_WILD_ONE == lexeme) {
RCL_SET_ERROR_MSG("Wildcard '*' is not implemented", rule->allocator);
return RCL_RET_ERROR;
} else if (RCL_LEXEME_WILD_MULTI == lexeme) {
RCL_SET_ERROR_MSG("Wildcard '**' is not implemented", rule->allocator);
return RCL_RET_ERROR;
} else {
RCL_SET_ERROR_MSG("Expecting token or wildcard", rule->allocator);
ret = RCL_RET_INVALID_REMAP_RULE;
}
return ret;
}
/// Parse the match side of a name remapping rule (ex: `rostopic://foo`)
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_match_name(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
rcl_lexeme_t lexeme;
// rostopic:// rosservice://
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
if (RCL_LEXEME_URL_SERVICE == lexeme) {
rule->type = RCL_SERVICE_REMAP;
ret = rcl_lexer_lookahead2_accept(lex_lookahead, NULL, NULL);
} else if (RCL_LEXEME_URL_TOPIC == lexeme) {
rule->type = RCL_TOPIC_REMAP;
ret = rcl_lexer_lookahead2_accept(lex_lookahead, NULL, NULL);
} else {
rule->type = (RCL_TOPIC_REMAP | RCL_SERVICE_REMAP);
}
if (RCL_RET_OK != ret) {
return ret;
}
const char * match_start = rcl_lexer_lookahead2_get_text(lex_lookahead);
if (NULL == match_start) {
RCL_SET_ERROR_MSG("failed to get start of match", rule->allocator);
return RCL_RET_ERROR;
}
// private name (~/...) or fully qualified name (/...) ?
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
if (RCL_LEXEME_TILDE_SLASH == lexeme || RCL_LEXEME_FORWARD_SLASH == lexeme) {
ret = rcl_lexer_lookahead2_accept(lex_lookahead, NULL, NULL);
}
if (RCL_RET_OK != ret) {
return ret;
}
// token ( '/' token )*
ret = _rcl_parse_remap_match_token(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
while (RCL_LEXEME_SEPARATOR != lexeme) {
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_FORWARD_SLASH, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
ret = _rcl_parse_remap_match_token(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
}
// Copy match into rule
const char * match_end = rcl_lexer_lookahead2_get_text(lex_lookahead);
size_t length = (size_t)(match_end - match_start);
rule->match = rcutils_strndup(match_start, length, rule->allocator);
if (NULL == rule->match) {
RCL_SET_ERROR_MSG("failed to copy match", rule->allocator);
return RCL_RET_BAD_ALLOC;
}
return RCL_RET_OK;
}
/// Parse a name remapping rule (ex: `rostopic:///foo:=bar`).
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_name_remap(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
// match
ret = _rcl_parse_remap_match_name(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
// :=
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_SEPARATOR, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
// replacement
ret = _rcl_parse_remap_replacement_name(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
return RCL_RET_OK;
}
/// Parse a namespace replacement rule (ex: `__ns:=/new/ns`).
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_namespace_replacement(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
// __ns
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_NS, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
// :=
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_SEPARATOR, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
// /foo/bar
const char * ns_start = rcl_lexer_lookahead2_get_text(lex_lookahead);
if (NULL == ns_start) {
RCL_SET_ERROR_MSG("failed to get start of namespace", rule->allocator);
return RCL_RET_ERROR;
}
ret = _rcl_parse_remap_fully_qualified_namespace(lex_lookahead);
if (RCL_RET_OK != ret) {
return ret;
}
// Copy namespace into rule
const char * ns_end = rcl_lexer_lookahead2_get_text(lex_lookahead);
size_t length = (size_t)(ns_end - ns_start);
rule->replacement = rcutils_strndup(ns_start, length, rule->allocator);
if (NULL == rule->replacement) {
RCL_SET_ERROR_MSG("failed to copy namespace", rule->allocator);
return RCL_RET_BAD_ALLOC;
}
rule->type = RCL_NAMESPACE_REMAP;
return RCL_RET_OK;
}
/// Parse a nodename replacement rule (ex: `__node:=new_name`).
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_nodename_replacement(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
const char * node_name;
size_t length;
// __node
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_NODE, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
// :=
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_SEPARATOR, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
// new_node_name
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_TOKEN, &node_name, &length);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
if (RCL_RET_OK != ret) {
return ret;
}
// copy the node name into the replacement side of the rule
rule->replacement = rcutils_strndup(node_name, length, rule->allocator);
if (NULL == rule->replacement) {
RCL_SET_ERROR_MSG("failed to allocate node name", rule->allocator);
return RCL_RET_BAD_ALLOC;
}
rule->type = RCL_NODENAME_REMAP;
return RCL_RET_OK;
}
/// Parse a nodename prefix including trailing colon (ex: `node_name:`).
/// \sa _rcl_parse_remap_begin_remap_rule()
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_nodename_prefix(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
const char * node_name;
size_t length;
// Expect a token and a colon
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_TOKEN, &node_name, &length);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_COLON, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
// copy the node name into the rule
rule->node_name = rcutils_strndup(node_name, length, rule->allocator);
if (NULL == rule->node_name) {
RCL_SET_ERROR_MSG("failed to allocate node name", rule->allocator);
return RCL_RET_BAD_ALLOC;
}
return RCL_RET_OK;
}
/// Start recursive descent parsing of a remap rule.
/// \param[in] lex_lookahead a lookahead(2) buffer for the parser to use.
/// \param[in,out] rule input a zero intialized rule, output a fully initialized one.
/// \return RCL_RET_OK if a valid rule was parsed, or
/// \return RCL_RET_INVALID_REMAP_RULE if the argument is not a valid rule, or
/// \return RCL_RET_BAD_ALLOC if an allocation failed, or
/// \return RLC_RET_ERROR if an unspecified error occurred.
/// \internal
RCL_LOCAL
rcl_ret_t
_rcl_parse_remap_begin_remap_rule(
rcl_lexer_lookahead2_t * lex_lookahead,
rcl_remap_t * rule)
{
rcl_ret_t ret;
rcl_lexeme_t lexeme1;
rcl_lexeme_t lexeme2;
// Check for optional nodename prefix
ret = rcl_lexer_lookahead2_peek2(lex_lookahead, &lexeme1, &lexeme2);
if (RCL_RET_OK != ret) {
return ret;
}
if (RCL_LEXEME_TOKEN == lexeme1 && RCL_LEXEME_COLON == lexeme2) {
ret = _rcl_parse_remap_nodename_prefix(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
}
ret = rcl_lexer_lookahead2_peek(lex_lookahead, &lexeme1);
if (RCL_RET_OK != ret) {
return ret;
}
// What type of rule is this (node name replacement, namespace replacement, or name remap)?
if (RCL_LEXEME_NODE == lexeme1) {
ret = _rcl_parse_remap_nodename_replacement(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
} else if (RCL_LEXEME_NS == lexeme1) {
ret = _rcl_parse_remap_namespace_replacement(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
} else {
ret = _rcl_parse_remap_name_remap(lex_lookahead, rule);
if (RCL_RET_OK != ret) {
return ret;
}
}
// Make sure all characters in string have been consumed
ret = rcl_lexer_lookahead2_expect(lex_lookahead, RCL_LEXEME_EOF, NULL, NULL);
if (RCL_RET_WRONG_LEXEME == ret) {
return RCL_RET_INVALID_REMAP_RULE;
}
return ret;
}
rcl_ret_t
_rcl_parse_remap_rule(
const char * arg,
rcl_allocator_t allocator,
rcl_remap_t * output_rule)
{
RCL_CHECK_ARGUMENT_FOR_NULL(arg, RCL_RET_INVALID_ARGUMENT, allocator);
RCL_CHECK_ARGUMENT_FOR_NULL(output_rule, RCL_RET_INVALID_ARGUMENT, allocator);
rcl_ret_t ret;
output_rule->allocator = allocator;
rcl_lexer_lookahead2_t lex_lookahead = rcl_get_zero_initialized_lexer_lookahead2();
ret = rcl_lexer_lookahead2_init(&lex_lookahead, arg, allocator);
if (RCL_RET_OK != ret) {
return ret;
}
ret = _rcl_parse_remap_begin_remap_rule(&lex_lookahead, output_rule);
if (RCL_RET_OK != ret) {
// cleanup stuff, but return the original error code
if (RCL_RET_OK != rcl_remap_fini(output_rule)) {
RCUTILS_LOG_ERROR_NAMED(ROS_PACKAGE_NAME, "Failed to fini remap rule after error occurred");
}
if (RCL_RET_OK != rcl_lexer_lookahead2_fini(&lex_lookahead)) {
RCUTILS_LOG_ERROR_NAMED(ROS_PACKAGE_NAME, "Failed to fini lookahead2 after error occurred");
}
} else {
ret = rcl_lexer_lookahead2_fini(&lex_lookahead);
}
return ret;
}
#if __cplusplus
}
#endif

655
rcl/src/rcl/lexer.c Normal file
View file

@ -0,0 +1,655 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "rcl/error_handling.h"
#include "rcl/lexer.h"
/* The lexer tries to find a lexeme in a string.
* It looks at one character at a time, and uses that character's value to decide how to transition
* a state machine.
* A transition is taken if a character's ASCII value falls within its range.
* There is never more than one matching transition.
*
* If no transition matches then it uses a state's '<else,M>' transition.
* Every state has exactly one '<else,M>' transition.
* In the diagram below all states have an `<else,0>` to T_NONE unless otherwise specified.
*
* When a transition is taken it causes the lexer to move to another character in the string.
* Normal transitions always move the lexer forwards one character.
* '<else,M>' transitions may cause the lexer to move forwards 1, or backwards N.
* The movement M is written as M = 1 + N so it can be stored in an unsigned integer.
* For example, an `<else>` transition with M = 0 moves the lexer forwards 1 character, M = 1 keeps
* the lexer at the current character, and M = 2 moves the lexer backwards one character.
digraph remapping_lexer {
rankdir=LR;
node [shape = box, fontsize = 7];
T_TILDE_SLASH
T_URL_SERVICE
T_URL_TOPIC
T_COLON
T_NODE
T_NS
T_SEPARATOR
T_BR1
T_BR2
T_BR3
T_BR4
T_BR5
T_BR6
T_BR7
T_BR8
T_BR9
T_TOKEN
T_FORWARD_SLASH
T_WILD_ONE
T_WILD_MULTI
T_EOF
T_NONE
node [shape = circle];
S0 -> T_FORWARD_SLASH [ label = "/"];
S0 -> S1 [ label = "\\"];
S0 -> S2 [ label = "~"];
S0 -> S3 [ label = "_" ];
S0 -> S8 [ label = "a-qs-zA-Z"];
S0 -> S10 [ label = "r"];
S0 -> S29 [ label = "*"];
S0 -> S30 [ label = ":"];
S1 -> T_BR1 [ label = "1"];
S1 -> T_BR2 [ label = "2"];
S1 -> T_BR3 [ label = "3"];
S1 -> T_BR4 [ label = "4"];
S1 -> T_BR5 [ label = "5"];
S1 -> T_BR6 [ label = "6"];
S1 -> T_BR7 [ label = "7"];
S1 -> T_BR8 [ label = "8"];
S1 -> T_BR9 [ label = "9"];
S2 -> T_TILDE_SLASH [ label ="/" ];
S3 -> S4 [ label = "_" ];
S3 -> S9 [ label = "<else,1>", color = crimson, fontcolor = crimson];
S4 -> S5 [ label = "n" ];
S5 -> T_NS [ label = "s"];
S5 -> S6 [ label = "o" ];
S6 -> S7 [ label = "d" ];
S7 -> T_NODE [ label = "e"];
S8 -> T_TOKEN [ label = "<else,1>", color=crimson, fontcolor=crimson];
S8 -> S8 [ label = "a-zA-Z0-9"];
S8 -> S9 [ label = "_"];
S9 -> T_TOKEN [ label = "<else,1>", color=crimson, fontcolor=crimson];
S9 -> S8 [ label = "a-zA-Z0-9"];
S10 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S10 -> S11 [ label = "o"];
S11 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S11 -> S12 [ label = "s"];
S12 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S12 -> S13 [ label = "t"];
S12 -> S20 [ label = "s"];
S13 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S13 -> S14 [ label = "o"];
S14 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S14 -> S15 [ label = "p"];
S15 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S15 -> S16 [ label = "i"];
S16 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S16 -> S17 [ label = "c"];
S17 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S17 -> S18 [ label = ":"];
S18 -> S19 [ label = "/"];
S18 -> S8 [ label = "<else,2>", color=crimson, fontcolor=crimson];
S19 -> T_URL_TOPIC [ label = "/"];
S19 -> S8 [ label = "<else,3>", color=crimson, fontcolor=crimson];
S20 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S20 -> S21 [ label = "e"];
S21 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S21 -> S22 [ label = "r"];
S22 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S22 -> S23 [ label = "v"];
S23 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S23 -> S24 [ label = "i"];
S24 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S24 -> S25 [ label = "c"];
S25 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S25 -> S26 [ label = "e"];
S26 -> S27 [ label = ":"];
S26 -> S8 [ label = "<else,1>", color=crimson, fontcolor=crimson];
S27 -> S28 [ label = "/"];
S27 -> S8 [ label = "<else,2>", color=crimson, fontcolor=crimson];
S28 -> T_URL_SERVICE [ label = "/"];
S28 -> S8 [ label = "<else,3>", color=crimson, fontcolor=crimson];
S29 -> T_WILD_MULTI[ label = "*"];
S29 -> T_WILD_ONE [ label = "<else,1>", color=crimson, fontcolor=crimson];
S30 -> T_SEPARATOR [ label = "="];
S30 -> T_COLON [ label = "<else,1>", color=crimson, fontcolor=crimson];
}
*/
/// Represents a transition from one state to another
/// \internal
typedef struct rcl_lexer_transition_t
{
/// Index of a state to transition to
const unsigned char to_state;
/// Start of a range of chars (inclusive) which activates this transition
const char range_start;
/// End of a range of chars (inclusive) which activates this transition
const char range_end;
} rcl_lexer_transition_t;
/// Represents a non-terminal state
/// \internal
typedef struct rcl_lexer_state_t
{
/// Transition to this state if no other transition matches
const unsigned char else_state;
/// Movement associated with taking else state
const unsigned char else_movement;
/// Transitions in the state machine (NULL value at end of array)
const rcl_lexer_transition_t transitions[11];
} rcl_lexer_state_t;
#define S0 0u
#define S1 1u
#define S2 2u
#define S3 3u
#define S4 4u
#define S5 5u
#define S6 6u
#define S7 7u
#define S8 8u
#define S9 9u
#define S10 10u
#define S11 11u
#define S12 12u
#define S13 13u
#define S14 14u
#define S15 15u
#define S16 16u
#define S17 17u
#define S18 18u
#define S19 19u
#define S20 20u
#define S21 21u
#define S22 22u
#define S23 23u
#define S24 24u
#define S25 25u
#define S26 26u
#define S27 27u
#define S28 28u
#define S29 29u
#define S30 30u
#define LAST_STATE S30
#define T_TILDE_SLASH 31u
#define T_URL_SERVICE 32u
#define T_URL_TOPIC 33u
#define T_COLON 34u
#define T_NODE 35u
#define T_NS 36u
#define T_SEPARATOR 37u
#define T_BR1 38u
#define T_BR2 39u
#define T_BR3 40u
#define T_BR4 41u
#define T_BR5 42u
#define T_BR6 43u
#define T_BR7 44u
#define T_BR8 45u
#define T_BR9 46u
#define T_TOKEN 47u
#define T_FORWARD_SLASH 48u
#define T_WILD_ONE 49u
#define T_WILD_MULTI 50u
#define T_EOF 51u
#define T_NONE 52u
// used to figure out if a state is terminal or not
#define FIRST_TERMINAL T_TILDE_SLASH
#define LAST_TERMINAL T_NONE
// Used to mark where the last transition is in a state
#define END_TRANSITIONS {0, '\0', '\0'}
static const rcl_lexer_state_t g_states[LAST_STATE + 1] =
{
// S0
{
T_NONE,
0u,
{
{T_FORWARD_SLASH, '/', '/'},
{S1, '\\', '\\'},
{S2, '~', '~'},
{S3, '_', '_'},
{S8, 'a', 'q'},
{S8, 's', 'z'},
{S8, 'A', 'Z'},
{S10, 'r', 'r'},
{S29, '*', '*'},
{S30, ':', ':'},
END_TRANSITIONS
}
},
// S1
{
T_NONE,
0u,
{
{T_BR1, '1', '1'},
{T_BR2, '2', '2'},
{T_BR3, '3', '3'},
{T_BR4, '4', '4'},
{T_BR5, '5', '5'},
{T_BR6, '6', '6'},
{T_BR7, '7', '7'},
{T_BR8, '8', '8'},
{T_BR9, '9', '9'},
END_TRANSITIONS
}
},
// S2
{
T_NONE,
0u,
{
{T_TILDE_SLASH, '/', '/'},
END_TRANSITIONS
}
},
// S3
{
S9,
1u,
{
{S4, '_', '_'},
END_TRANSITIONS
}
},
// S4
{
T_NONE,
0u,
{
{S5, 'n', 'n'},
END_TRANSITIONS
}
},
// S5
{
T_NONE,
0u,
{
{T_NS, 's', 's'},
{S6, 'o', 'o'},
END_TRANSITIONS
}
},
// S6
{
T_NONE,
0u,
{
{S7, 'd', 'd'},
END_TRANSITIONS
}
},
// S7
{
T_NONE,
0u,
{
{T_NODE, 'e', 'e'},
END_TRANSITIONS
}
},
// S8
{
T_TOKEN,
1u,
{
{S8, 'a', 'z'},
{S8, 'A', 'Z'},
{S8, '0', '9'},
{S9, '_', '_'},
END_TRANSITIONS
}
},
// S9
{
T_TOKEN,
1u,
{
{S8, 'a', 'z'},
{S8, 'A', 'Z'},
{S8, '0', '9'},
END_TRANSITIONS
}
},
// S10
{
S8,
1u,
{
{S11, 'o', 'o'},
END_TRANSITIONS
}
},
// S11
{
S8,
1u,
{
{S12, 's', 's'},
END_TRANSITIONS
}
},
// S12
{
S8,
1u,
{
{S13, 't', 't'},
{S20, 's', 's'},
END_TRANSITIONS
}
},
// S13
{
S8,
1u,
{
{S14, 'o', 'o'},
END_TRANSITIONS
}
},
// S14
{
S8,
1u,
{
{S15, 'p', 'p'},
END_TRANSITIONS
}
},
// S15
{
S8,
1u,
{
{S16, 'i', 'i'},
END_TRANSITIONS
}
},
// S16
{
S8,
1u,
{
{S17, 'c', 'c'},
END_TRANSITIONS
}
},
// S17
{
S8,
1u,
{
{S18, ':', ':'},
END_TRANSITIONS
}
},
// S18
{
S8,
2u,
{
{S19, '/', '/'},
END_TRANSITIONS
}
},
// S19
{
S8,
3u,
{
{T_URL_TOPIC, '/', '/'},
END_TRANSITIONS
}
},
// S20
{
S8,
1u,
{
{S21, 'e', 'e'},
END_TRANSITIONS
}
},
// S21
{
S8,
1u,
{
{S22, 'r', 'r'},
END_TRANSITIONS
}
},
// S22
{
S8,
1u,
{
{S23, 'v', 'v'},
END_TRANSITIONS
}
},
// S23
{
S8,
1u,
{
{S24, 'i', 'i'},
END_TRANSITIONS
}
},
// S24
{
S8,
1u,
{
{S25, 'c', 'c'},
END_TRANSITIONS
}
},
// S25
{
S8,
1u,
{
{S26, 'e', 'e'},
END_TRANSITIONS
}
},
// S26
{
S8,
1u,
{
{S27, ':', ':'},
END_TRANSITIONS
}
},
// S27
{
S8,
2u,
{
{S28, '/', '/'},
END_TRANSITIONS
}
},
// S28
{
S8,
3u,
{
{T_URL_SERVICE, '/', '/'},
END_TRANSITIONS
}
},
// S29
{
T_WILD_ONE,
1u,
{
{T_WILD_MULTI, '*', '*'},
END_TRANSITIONS
}
},
// S30
{
T_COLON,
1u,
{
{T_SEPARATOR, '=', '='},
END_TRANSITIONS
}
},
};
static const rcl_lexeme_t g_terminals[LAST_TERMINAL + 1] = {
// 0
RCL_LEXEME_TILDE_SLASH,
// 1
RCL_LEXEME_URL_SERVICE,
// 2
RCL_LEXEME_URL_TOPIC,
// 3
RCL_LEXEME_COLON,
// 4
RCL_LEXEME_NODE,
// 5
RCL_LEXEME_NS,
// 6
RCL_LEXEME_SEPARATOR,
// 7
RCL_LEXEME_BR1,
// 8
RCL_LEXEME_BR2,
// 9
RCL_LEXEME_BR3,
// 10
RCL_LEXEME_BR4,
// 11
RCL_LEXEME_BR5,
// 12
RCL_LEXEME_BR6,
// 13
RCL_LEXEME_BR7,
// 14
RCL_LEXEME_BR8,
// 15
RCL_LEXEME_BR9,
// 16
RCL_LEXEME_TOKEN,
// 17
RCL_LEXEME_FORWARD_SLASH,
// 18
RCL_LEXEME_WILD_ONE,
// 19
RCL_LEXEME_WILD_MULTI,
// 20
RCL_LEXEME_EOF,
// 21
RCL_LEXEME_NONE,
};
rcl_ret_t
rcl_lexer_analyze(
const char * text,
rcl_allocator_t alloc,
rcl_lexeme_t * lexeme,
size_t * length)
{
RCL_CHECK_ALLOCATOR_WITH_MSG(&alloc, "invalid allocator", return RCL_RET_INVALID_ARGUMENT);
RCL_CHECK_ARGUMENT_FOR_NULL(text, RCL_RET_INVALID_ARGUMENT, alloc);
RCL_CHECK_ARGUMENT_FOR_NULL(lexeme, RCL_RET_INVALID_ARGUMENT, alloc);
RCL_CHECK_ARGUMENT_FOR_NULL(length, RCL_RET_INVALID_ARGUMENT, alloc);
*length = 0u;
if ('\0' == text[0u]) {
// Early exit if string is empty
*lexeme = RCL_LEXEME_EOF;
return RCL_RET_OK;
}
const rcl_lexer_state_t * state;
char current_char;
size_t next_state = S0;
size_t movement;
// Analyze one character at a time until lexeme is found
do {
if (next_state > LAST_STATE) {
// Should never happen
RCL_SET_ERROR_MSG("Internal lexer bug: next state does not exist", alloc);
return RCL_RET_ERROR;
}
state = &(g_states[next_state]);
current_char = text[*length];
next_state = 0u;
movement = 0u;
// Look for a transition that contains this character in its range
size_t transition_idx = 0u;
const rcl_lexer_transition_t * transition;
do {
transition = &(state->transitions[transition_idx]);
if (transition->range_start <= current_char && transition->range_end >= current_char) {
next_state = transition->to_state;
break;
}
++transition_idx;
} while (0u != transition->to_state);
// if no transition was found, take the else transition
if (0u == next_state) {
next_state = state->else_state;
movement = state->else_movement;
}
// Move the lexer to another character in the string
if (0u == movement) {
// Go forwards 1 char
++(*length);
} else {
// Go backwards N chars
if (movement - 1u > *length) {
// Should never happen
RCL_SET_ERROR_MSG("Internal lexer bug: movement would read before start of string", alloc);
return RCL_RET_ERROR;
}
*length -= movement - 1u;
}
} while (next_state < FIRST_TERMINAL);
if (FIRST_TERMINAL > next_state || next_state - FIRST_TERMINAL > LAST_TERMINAL) {
// Should never happen
RCL_SET_ERROR_MSG("Internal lexer bug: terminal state does not exist", alloc);
return RCL_RET_ERROR;
}
*lexeme = g_terminals[next_state - FIRST_TERMINAL];
return RCL_RET_OK;
}

View file

@ -0,0 +1,237 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "rcl/error_handling.h"
#include "rcl/lexer_lookahead.h"
struct rcl_lexer_lookahead2_impl_t
{
// Text that is being analyzed for lexemes
const char * text;
// Where in the text analysis is being performed
size_t text_idx;
// first character of lexeme
size_t start[2];
// One past last character of lexeme
size_t end[2];
// Type of lexeme
rcl_lexeme_t type[2];
// Allocator to use if an error occurrs
rcl_allocator_t allocator;
};
rcl_lexer_lookahead2_t
rcl_get_zero_initialized_lexer_lookahead2()
{
static rcl_lexer_lookahead2_t zero_initialized = {
.impl = NULL,
};
return zero_initialized;
}
rcl_ret_t
rcl_lexer_lookahead2_init(
rcl_lexer_lookahead2_t * buffer,
const char * text,
rcl_allocator_t allocator)
{
RCL_CHECK_ALLOCATOR_WITH_MSG(&allocator, "invalid allocator", return RCL_RET_INVALID_ARGUMENT);
RCL_CHECK_ARGUMENT_FOR_NULL(buffer, RCL_RET_INVALID_ARGUMENT, allocator);
RCL_CHECK_ARGUMENT_FOR_NULL(text, RCL_RET_INVALID_ARGUMENT, allocator);
if (NULL != buffer->impl) {
RCL_SET_ERROR_MSG("buffer must be zero initialized", allocator);
return RCL_RET_INVALID_ARGUMENT;
}
buffer->impl = allocator.allocate(sizeof(struct rcl_lexer_lookahead2_impl_t), allocator.state);
RCL_CHECK_FOR_NULL_WITH_MSG(
buffer->impl, "Failed to allocate lookahead impl", return RCL_RET_BAD_ALLOC, allocator);
buffer->impl->text = text;
buffer->impl->text_idx = 0u;
buffer->impl->start[0] = 0u;
buffer->impl->start[1] = 0u;
buffer->impl->end[0] = 0u;
buffer->impl->end[1] = 0u;
buffer->impl->type[0] = RCL_LEXEME_NONE;
buffer->impl->type[1] = RCL_LEXEME_NONE;
buffer->impl->allocator = allocator;
return RCL_RET_OK;
}
rcl_ret_t
rcl_lexer_lookahead2_fini(
rcl_lexer_lookahead2_t * buffer)
{
RCL_CHECK_ARGUMENT_FOR_NULL(buffer, RCL_RET_INVALID_ARGUMENT, rcl_get_default_allocator());
RCL_CHECK_FOR_NULL_WITH_MSG(
buffer->impl, "buffer finalized twice", return RCL_RET_INVALID_ARGUMENT,
rcl_get_default_allocator());
RCL_CHECK_ALLOCATOR_WITH_MSG(
&(buffer->impl->allocator), "invalid allocator", return RCL_RET_INVALID_ARGUMENT);
buffer->impl->allocator.deallocate(buffer->impl, buffer->impl->allocator.state);
buffer->impl = NULL;
return RCL_RET_OK;
}
rcl_ret_t
rcl_lexer_lookahead2_peek(
rcl_lexer_lookahead2_t * buffer,
rcl_lexeme_t * next_type)
{
RCL_CHECK_ARGUMENT_FOR_NULL(buffer, RCL_RET_INVALID_ARGUMENT, rcl_get_default_allocator());
RCL_CHECK_FOR_NULL_WITH_MSG(
buffer->impl, "buffer not initialized", return RCL_RET_INVALID_ARGUMENT,
rcl_get_default_allocator());
RCL_CHECK_ARGUMENT_FOR_NULL(next_type, RCL_RET_INVALID_ARGUMENT, buffer->impl->allocator);
rcl_ret_t ret;
size_t length;
if (buffer->impl->text_idx >= buffer->impl->end[0]) {
// No buffered lexeme; get one
ret = rcl_lexer_analyze(
rcl_lexer_lookahead2_get_text(buffer),
buffer->impl->allocator,
&(buffer->impl->type[0]),
&length);
if (RCL_RET_OK != ret) {
return ret;
}
buffer->impl->start[0] = buffer->impl->text_idx;
buffer->impl->end[0] = buffer->impl->start[0] + length;
}
*next_type = buffer->impl->type[0];
return RCL_RET_OK;
}
rcl_ret_t
rcl_lexer_lookahead2_peek2(
rcl_lexer_lookahead2_t * buffer,
rcl_lexeme_t * next_type1,
rcl_lexeme_t * next_type2)
{
rcl_ret_t ret;
// Peek 1 ahead first (reusing its error checking for buffer and next_type1)
ret = rcl_lexer_lookahead2_peek(buffer, next_type1);
if (RCL_RET_OK != ret) {
return ret;
}
RCL_CHECK_ARGUMENT_FOR_NULL(next_type2, RCL_RET_INVALID_ARGUMENT, buffer->impl->allocator);
size_t length;
if (buffer->impl->text_idx >= buffer->impl->end[1]) {
// No buffered lexeme; get one
ret = rcl_lexer_analyze(
&(buffer->impl->text[buffer->impl->end[0]]),
buffer->impl->allocator,
&(buffer->impl->type[1]),
&length);
if (RCL_RET_OK != ret) {
return ret;
}
buffer->impl->start[1] = buffer->impl->end[0];
buffer->impl->end[1] = buffer->impl->start[1] + length;
}
*next_type2 = buffer->impl->type[1];
return RCL_RET_OK;
}
rcl_ret_t
rcl_lexer_lookahead2_accept(
rcl_lexer_lookahead2_t * buffer,
const char ** lexeme_text,
size_t * lexeme_text_length)
{
RCL_CHECK_ARGUMENT_FOR_NULL(buffer, RCL_RET_INVALID_ARGUMENT, rcl_get_default_allocator());
RCL_CHECK_FOR_NULL_WITH_MSG(
buffer->impl, "buffer not initialized", return RCL_RET_INVALID_ARGUMENT,
rcl_get_default_allocator());
if (
(NULL == lexeme_text && NULL != lexeme_text_length) ||
(NULL != lexeme_text && NULL == lexeme_text_length))
{
RCL_SET_ERROR_MSG("text and length must both be set or both be NULL", buffer->impl->allocator);
return RCL_RET_INVALID_ARGUMENT;
}
if (RCL_LEXEME_EOF == buffer->impl->type[0]) {
// Reached EOF, nothing to accept
if (NULL != lexeme_text && NULL != lexeme_text_length) {
*lexeme_text = rcl_lexer_lookahead2_get_text(buffer);
*lexeme_text_length = 0u;
}
return RCL_RET_OK;
}
if (buffer->impl->text_idx >= buffer->impl->end[0]) {
RCL_SET_ERROR_MSG("no lexeme to accept", buffer->impl->allocator);
return RCL_RET_ERROR;
}
if (NULL != lexeme_text && NULL != lexeme_text_length) {
*lexeme_text = &(buffer->impl->text[buffer->impl->start[0]]);
*lexeme_text_length = buffer->impl->end[0] - buffer->impl->start[0];
}
// Advance lexer position
buffer->impl->text_idx = buffer->impl->end[0];
// Move second lexeme in buffer to first position
buffer->impl->start[0] = buffer->impl->start[1];
buffer->impl->end[0] = buffer->impl->end[1];
buffer->impl->type[0] = buffer->impl->type[1];
return RCL_RET_OK;
}
rcl_ret_t
rcl_lexer_lookahead2_expect(
rcl_lexer_lookahead2_t * buffer,
rcl_lexeme_t type,
const char ** lexeme_text,
size_t * lexeme_text_length)
{
rcl_ret_t ret;
rcl_lexeme_t lexeme;
ret = rcl_lexer_lookahead2_peek(buffer, &lexeme);
if (RCL_RET_OK != ret) {
return ret;
}
if (type != lexeme) {
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
buffer->impl->allocator, "Expected %d got %d at %lu", type, lexeme, buffer->impl->text_idx);
return RCL_RET_WRONG_LEXEME;
}
return rcl_lexer_lookahead2_accept(buffer, lexeme_text, lexeme_text_length);
}
const char *
rcl_lexer_lookahead2_get_text(
const rcl_lexer_lookahead2_t * buffer)
{
return &(buffer->impl->text[buffer->impl->text_idx]);
}

View file

@ -67,6 +67,22 @@ function(test_target_function)
AMENT_DEPENDENCIES ${rmw_implementation}
)
rcl_add_custom_gtest(test_lexer${target_suffix}
SRCS rcl/test_lexer.cpp
ENV ${extra_test_env}
APPEND_LIBRARY_DIRS ${extra_lib_dirs}
LIBRARIES ${PROJECT_NAME} ${extra_test_libraries}
AMENT_DEPENDENCIES ${rmw_implementation}
)
rcl_add_custom_gtest(test_lexer_lookahead${target_suffix}
SRCS rcl/test_lexer_lookahead.cpp
ENV ${extra_test_env}
APPEND_LIBRARY_DIRS ${extra_lib_dirs}
LIBRARIES ${PROJECT_NAME} ${extra_test_libraries}
AMENT_DEPENDENCIES ${rmw_implementation}
)
set(SKIP_TEST "")
# TODO(wjwwood): remove this when the graph API works properly for connext dynamic
if(

View file

@ -95,6 +95,11 @@ TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), check_valid_vs_inval
EXPECT_TRUE(is_valid_arg("foo:=/bar"));
EXPECT_TRUE(is_valid_arg("/foo123:=/bar123"));
EXPECT_TRUE(is_valid_arg("node:/foo123:=/bar123"));
EXPECT_TRUE(is_valid_arg("rostopic:=/foo/bar"));
EXPECT_TRUE(is_valid_arg("rosservice:=baz"));
EXPECT_TRUE(is_valid_arg("rostopic://rostopic:=rosservice"));
EXPECT_TRUE(is_valid_arg("rostopic:///rosservice:=rostopic"));
EXPECT_TRUE(is_valid_arg("rostopic:///foo/bar:=baz"));
EXPECT_FALSE(is_valid_arg(":="));
EXPECT_FALSE(is_valid_arg("foo:="));
@ -110,6 +115,8 @@ TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), check_valid_vs_inval
EXPECT_FALSE(is_valid_arg("foo:=/b ar"));
EXPECT_FALSE(is_valid_arg("f{oo:=/bar"));
EXPECT_FALSE(is_valid_arg("foo:=/b}ar"));
EXPECT_FALSE(is_valid_arg("rostopic://:=rosservice"));
EXPECT_FALSE(is_valid_arg("rostopic::=rosservice"));
}
TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), test_no_args) {

331
rcl/test/rcl/test_lexer.cpp Normal file
View file

@ -0,0 +1,331 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <string>
#include "rcl/lexer.h"
#ifdef RMW_IMPLEMENTATION
# define CLASSNAME_(NAME, SUFFIX) NAME ## __ ## SUFFIX
# define CLASSNAME(NAME, SUFFIX) CLASSNAME_(NAME, SUFFIX)
#else
# define CLASSNAME(NAME, SUFFIX) NAME
#endif
class CLASSNAME (TestLexerFixture, RMW_IMPLEMENTATION) : public ::testing::Test
{
public:
void SetUp()
{
}
void TearDown()
{
}
};
// Not using a function so gtest failure output shows the line number where the macro is used
#define EXPECT_LEX(expected_lexeme, expected_text, text) \
do { \
rcl_lexeme_t actual_lexeme; \
size_t length; \
rcl_allocator_t allocator = rcl_get_default_allocator(); \
rcl_ret_t ret = rcl_lexer_analyze(text, allocator, &actual_lexeme, &length); \
ASSERT_EQ(RCL_RET_OK, ret); \
EXPECT_EQ(expected_lexeme, actual_lexeme); \
std::string actual_text(text, length); \
EXPECT_STREQ(expected_text, actual_text.c_str()); \
} while (false)
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_token)
{
// Things get recognized as tokens whether input ends or non token characters come after them
EXPECT_LEX(RCL_LEXEME_TOKEN, "foo", "foo");
EXPECT_LEX(RCL_LEXEME_TOKEN, "foo", "foo:");
EXPECT_LEX(RCL_LEXEME_TOKEN, "foo_", "foo_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "foo_", "foo_:");
// Check full range for starting character
EXPECT_LEX(RCL_LEXEME_TOKEN, "a", "a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "b", "b");
EXPECT_LEX(RCL_LEXEME_TOKEN, "c", "c");
EXPECT_LEX(RCL_LEXEME_TOKEN, "d", "d");
EXPECT_LEX(RCL_LEXEME_TOKEN, "e", "e");
EXPECT_LEX(RCL_LEXEME_TOKEN, "f", "f");
EXPECT_LEX(RCL_LEXEME_TOKEN, "g", "g");
EXPECT_LEX(RCL_LEXEME_TOKEN, "h", "h");
EXPECT_LEX(RCL_LEXEME_TOKEN, "i", "i");
EXPECT_LEX(RCL_LEXEME_TOKEN, "j", "j");
EXPECT_LEX(RCL_LEXEME_TOKEN, "k", "k");
EXPECT_LEX(RCL_LEXEME_TOKEN, "l", "l");
EXPECT_LEX(RCL_LEXEME_TOKEN, "m", "m");
EXPECT_LEX(RCL_LEXEME_TOKEN, "n", "n");
EXPECT_LEX(RCL_LEXEME_TOKEN, "o", "o");
EXPECT_LEX(RCL_LEXEME_TOKEN, "p", "p");
EXPECT_LEX(RCL_LEXEME_TOKEN, "q", "q");
EXPECT_LEX(RCL_LEXEME_TOKEN, "r", "r");
EXPECT_LEX(RCL_LEXEME_TOKEN, "s", "s");
EXPECT_LEX(RCL_LEXEME_TOKEN, "t", "t");
EXPECT_LEX(RCL_LEXEME_TOKEN, "u", "u");
EXPECT_LEX(RCL_LEXEME_TOKEN, "v", "v");
EXPECT_LEX(RCL_LEXEME_TOKEN, "w", "w");
EXPECT_LEX(RCL_LEXEME_TOKEN, "x", "x");
EXPECT_LEX(RCL_LEXEME_TOKEN, "y", "y");
EXPECT_LEX(RCL_LEXEME_TOKEN, "z", "z");
EXPECT_LEX(RCL_LEXEME_TOKEN, "A", "A");
EXPECT_LEX(RCL_LEXEME_TOKEN, "B", "B");
EXPECT_LEX(RCL_LEXEME_TOKEN, "C", "C");
EXPECT_LEX(RCL_LEXEME_TOKEN, "D", "D");
EXPECT_LEX(RCL_LEXEME_TOKEN, "E", "E");
EXPECT_LEX(RCL_LEXEME_TOKEN, "F", "F");
EXPECT_LEX(RCL_LEXEME_TOKEN, "G", "G");
EXPECT_LEX(RCL_LEXEME_TOKEN, "H", "H");
EXPECT_LEX(RCL_LEXEME_TOKEN, "I", "I");
EXPECT_LEX(RCL_LEXEME_TOKEN, "J", "J");
EXPECT_LEX(RCL_LEXEME_TOKEN, "K", "K");
EXPECT_LEX(RCL_LEXEME_TOKEN, "L", "L");
EXPECT_LEX(RCL_LEXEME_TOKEN, "M", "M");
EXPECT_LEX(RCL_LEXEME_TOKEN, "N", "N");
EXPECT_LEX(RCL_LEXEME_TOKEN, "O", "O");
EXPECT_LEX(RCL_LEXEME_TOKEN, "P", "P");
EXPECT_LEX(RCL_LEXEME_TOKEN, "Q", "Q");
EXPECT_LEX(RCL_LEXEME_TOKEN, "R", "R");
EXPECT_LEX(RCL_LEXEME_TOKEN, "S", "S");
EXPECT_LEX(RCL_LEXEME_TOKEN, "T", "T");
EXPECT_LEX(RCL_LEXEME_TOKEN, "U", "U");
EXPECT_LEX(RCL_LEXEME_TOKEN, "V", "V");
EXPECT_LEX(RCL_LEXEME_TOKEN, "W", "W");
EXPECT_LEX(RCL_LEXEME_TOKEN, "X", "X");
EXPECT_LEX(RCL_LEXEME_TOKEN, "Y", "Y");
EXPECT_LEX(RCL_LEXEME_TOKEN, "Z", "Z");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_", "_");
// Check banned characters adjacent to allowed ones in ASCII
EXPECT_LEX(RCL_LEXEME_NONE, "@", "@");
EXPECT_LEX(RCL_LEXEME_NONE, "[", "[");
EXPECT_LEX(RCL_LEXEME_NONE, "`", "`");
EXPECT_LEX(RCL_LEXEME_NONE, "{", "{");
// Tokens cannot start with digits
EXPECT_LEX(RCL_LEXEME_NONE, "0", "0");
EXPECT_LEX(RCL_LEXEME_NONE, "1", "1");
EXPECT_LEX(RCL_LEXEME_NONE, "2", "2");
EXPECT_LEX(RCL_LEXEME_NONE, "3", "3");
EXPECT_LEX(RCL_LEXEME_NONE, "4", "4");
EXPECT_LEX(RCL_LEXEME_NONE, "5", "5");
EXPECT_LEX(RCL_LEXEME_NONE, "6", "6");
EXPECT_LEX(RCL_LEXEME_NONE, "7", "7");
EXPECT_LEX(RCL_LEXEME_NONE, "8", "8");
EXPECT_LEX(RCL_LEXEME_NONE, "9", "9");
// Tokens may contain underscores
EXPECT_LEX(RCL_LEXEME_TOKEN, "_abcd", "_abcd");
EXPECT_LEX(RCL_LEXEME_TOKEN, "abcd_", "abcd_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "ab_cd", "ab_cd");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_a_b_c_d_", "_a_b_c_d_");
// Tokens cannot contain double underscores
EXPECT_LEX(RCL_LEXEME_TOKEN, "_a_", "_a__bcd");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a_", "a__bcd");
EXPECT_LEX(RCL_LEXEME_TOKEN, "A_", "A__bcd");
EXPECT_LEX(RCL_LEXEME_NONE, "__a", "__a");
EXPECT_LEX(RCL_LEXEME_NONE, "__A", "__A");
// Tokens may contain digits
EXPECT_LEX(RCL_LEXEME_TOKEN, "_0_", "_0_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_1_", "_1_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_2_", "_2_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_3_", "_3_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_4_", "_4_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_5_", "_5_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_6_", "_6_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_7_", "_7_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_8_", "_8_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_9_", "_9_");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a0a", "a0a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a1a", "a1a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a2a", "a2a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a3a", "a3a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a4a", "a4a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a5a", "a5a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a6a", "a6a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a7a", "a7a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a8a", "a8a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a9a", "a9a");
// Tokens may end with digits
EXPECT_LEX(RCL_LEXEME_TOKEN, "_0", "_0");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_1", "_1");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_2", "_2");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_3", "_3");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_4", "_4");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_5", "_5");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_6", "_6");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_7", "_7");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_8", "_8");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_9", "_9");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a0", "a0");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a1", "a1");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a2", "a2");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a3", "a3");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a4", "a4");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a5", "a5");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a6", "a6");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a7", "a7");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a8", "a8");
EXPECT_LEX(RCL_LEXEME_TOKEN, "a9", "a9");
// Things that almost look like a url scheme but are actually tokens
EXPECT_LEX(RCL_LEXEME_TOKEN, "ro", "ro");
EXPECT_LEX(RCL_LEXEME_TOKEN, "ros", "ros");
EXPECT_LEX(RCL_LEXEME_TOKEN, "ross", "ross");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosse", "rosse");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosser", "rosser");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosserv", "rosserv");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosservi", "rosservi");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosservic", "rosservic");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosservice", "rosservice");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosservice", "rosservice:");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosservice", "rosservice:=");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosservice", "rosservice:/");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosservice", "rosservice:/a");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rost", "rost");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rosto", "rosto");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rostop", "rostop");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rostopi", "rostopi");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rostopic", "rostopic");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rostopic", "rostopic:");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rostopic", "rostopic:=");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rostopic", "rostopic:/");
EXPECT_LEX(RCL_LEXEME_TOKEN, "rostopic", "rostopic:/a");
// Tokens may contain uppercase characters
EXPECT_LEX(RCL_LEXEME_TOKEN, "ABC", "ABC");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_DEF", "_DEF");
EXPECT_LEX(RCL_LEXEME_TOKEN, "_GHI_", "_GHI_");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_url_scheme)
{
// No text after scheme
EXPECT_LEX(RCL_LEXEME_URL_SERVICE, "rosservice://", "rosservice://");
EXPECT_LEX(RCL_LEXEME_URL_TOPIC, "rostopic://", "rostopic://");
// Some text after scheme
EXPECT_LEX(RCL_LEXEME_URL_SERVICE, "rosservice://", "rosservice://abcd");
EXPECT_LEX(RCL_LEXEME_URL_SERVICE, "rosservice://", "rosservice:///");
EXPECT_LEX(RCL_LEXEME_URL_TOPIC, "rostopic://", "rostopic://abcd");
EXPECT_LEX(RCL_LEXEME_URL_TOPIC, "rostopic://", "rostopic:///");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_backreferences)
{
// No text after backreference
EXPECT_LEX(RCL_LEXEME_BR1, "\\1", "\\1");
EXPECT_LEX(RCL_LEXEME_BR2, "\\2", "\\2");
EXPECT_LEX(RCL_LEXEME_BR3, "\\3", "\\3");
EXPECT_LEX(RCL_LEXEME_BR4, "\\4", "\\4");
EXPECT_LEX(RCL_LEXEME_BR5, "\\5", "\\5");
EXPECT_LEX(RCL_LEXEME_BR6, "\\6", "\\6");
EXPECT_LEX(RCL_LEXEME_BR7, "\\7", "\\7");
EXPECT_LEX(RCL_LEXEME_BR8, "\\8", "\\8");
EXPECT_LEX(RCL_LEXEME_BR9, "\\9", "\\9");
// Some text after backreference
EXPECT_LEX(RCL_LEXEME_BR1, "\\1", "\\1a");
EXPECT_LEX(RCL_LEXEME_BR2, "\\2", "\\2a");
EXPECT_LEX(RCL_LEXEME_BR3, "\\3", "\\3a");
EXPECT_LEX(RCL_LEXEME_BR4, "\\4", "\\4a");
EXPECT_LEX(RCL_LEXEME_BR5, "\\5", "\\5a");
EXPECT_LEX(RCL_LEXEME_BR6, "\\6", "\\6a");
EXPECT_LEX(RCL_LEXEME_BR7, "\\7", "\\7a");
EXPECT_LEX(RCL_LEXEME_BR8, "\\8", "\\8a");
EXPECT_LEX(RCL_LEXEME_BR9, "\\9", "\\9a");
// Not valid backreferences
EXPECT_LEX(RCL_LEXEME_NONE, "\\0", "\\0");
EXPECT_LEX(RCL_LEXEME_NONE, "\\a", "\\a");
EXPECT_LEX(RCL_LEXEME_NONE, "\\Z", "\\Z");
EXPECT_LEX(RCL_LEXEME_NONE, "\\_", "\\_");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_forward_slash)
{
EXPECT_LEX(RCL_LEXEME_FORWARD_SLASH, "/", "/");
EXPECT_LEX(RCL_LEXEME_FORWARD_SLASH, "/", "//");
EXPECT_LEX(RCL_LEXEME_FORWARD_SLASH, "/", "/_");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_wildcards)
{
EXPECT_LEX(RCL_LEXEME_WILD_ONE, "*", "*");
EXPECT_LEX(RCL_LEXEME_WILD_ONE, "*", "*/");
EXPECT_LEX(RCL_LEXEME_WILD_MULTI, "**", "**");
EXPECT_LEX(RCL_LEXEME_WILD_MULTI, "**", "**/");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_colon)
{
EXPECT_LEX(RCL_LEXEME_COLON, ":", ":");
EXPECT_LEX(RCL_LEXEME_COLON, ":", ":r");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_separator)
{
EXPECT_LEX(RCL_LEXEME_SEPARATOR, ":=", ":=");
EXPECT_LEX(RCL_LEXEME_SEPARATOR, ":=", ":=0");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_ns)
{
// Has __ns
EXPECT_LEX(RCL_LEXEME_NS, "__ns", "__ns");
EXPECT_LEX(RCL_LEXEME_NS, "__ns", "__nsssss");
// Things that are almost __ns
EXPECT_LEX(RCL_LEXEME_NONE, "__", "__");
EXPECT_LEX(RCL_LEXEME_NONE, "__n", "__n");
EXPECT_LEX(RCL_LEXEME_NONE, "__n!", "__n!");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_node)
{
// Has __node
EXPECT_LEX(RCL_LEXEME_NODE, "__node", "__node");
EXPECT_LEX(RCL_LEXEME_NODE, "__node", "__nodessss");
// Things that are almost __node
EXPECT_LEX(RCL_LEXEME_NONE, "__", "__");
EXPECT_LEX(RCL_LEXEME_NONE, "__n", "__n");
EXPECT_LEX(RCL_LEXEME_NONE, "__na", "__na");
EXPECT_LEX(RCL_LEXEME_NONE, "__no", "__no");
EXPECT_LEX(RCL_LEXEME_NONE, "__noa", "__noa");
EXPECT_LEX(RCL_LEXEME_NONE, "__nod", "__nod");
EXPECT_LEX(RCL_LEXEME_NONE, "__noda", "__noda");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_tilde_slash)
{
EXPECT_LEX(RCL_LEXEME_TILDE_SLASH, "~/", "~/");
EXPECT_LEX(RCL_LEXEME_TILDE_SLASH, "~/", "~//");
EXPECT_LEX(RCL_LEXEME_NONE, "~", "~");
EXPECT_LEX(RCL_LEXEME_NONE, "~!", "~!");
}
TEST_F(CLASSNAME(TestLexerFixture, RMW_IMPLEMENTATION), test_eof)
{
EXPECT_LEX(RCL_LEXEME_EOF, "", "");
}

View file

@ -0,0 +1,374 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <string>
#include "../scope_exit.hpp"
#include "rcl/error_handling.h"
#include "rcl/lexer_lookahead.h"
#ifdef RMW_IMPLEMENTATION
# define CLASSNAME_(NAME, SUFFIX) NAME ## __ ## SUFFIX
# define CLASSNAME(NAME, SUFFIX) CLASSNAME_(NAME, SUFFIX)
#else
# define CLASSNAME(NAME, SUFFIX) NAME
#endif
class CLASSNAME (TestLexerLookaheadFixture, RMW_IMPLEMENTATION) : public ::testing::Test
{
public:
void SetUp()
{
}
void TearDown()
{
}
};
#define SCOPE_LOOKAHEAD2(name, text) \
{ \
name = rcl_get_zero_initialized_lexer_lookahead2(); \
rcl_ret_t ret = rcl_lexer_lookahead2_init(&name, text, rcl_get_default_allocator()); \
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); \
} \
auto __scope_lookahead2_ ## name = make_scope_exit( \
[&name]() { \
rcl_ret_t ret = rcl_lexer_lookahead2_fini(&buffer); \
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); \
})
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_init_fini_twice)
{
rcl_lexer_lookahead2_t buffer = rcl_get_zero_initialized_lexer_lookahead2();
rcl_ret_t ret = rcl_lexer_lookahead2_init(&buffer, "foobar", rcl_get_default_allocator());
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
ret = rcl_lexer_lookahead2_fini(&buffer);
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
ret = rcl_lexer_lookahead2_fini(&buffer);
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
rcl_reset_error();
}
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_init_not_zero_initialized)
{
rcl_lexer_lookahead2_t buffer;
int not_zero = 1;
buffer.impl = reinterpret_cast<rcl_lexer_lookahead2_impl_t *>(&not_zero);
rcl_ret_t ret = rcl_lexer_lookahead2_init(&buffer, "foobar", rcl_get_default_allocator());
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
rcl_reset_error();
}
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_peek)
{
rcl_ret_t ret;
rcl_lexer_lookahead2_t buffer;
SCOPE_LOOKAHEAD2(buffer, "foobar");
rcl_lexeme_t lexeme = RCL_LEXEME_NONE;
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret);
EXPECT_EQ(RCL_LEXEME_TOKEN, lexeme);
// Test again to make sure peek isn't advancing the lexer
lexeme = RCL_LEXEME_NONE;
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret);
EXPECT_EQ(RCL_LEXEME_TOKEN, lexeme);
}
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_peek2)
{
rcl_ret_t ret;
rcl_lexer_lookahead2_t buffer;
SCOPE_LOOKAHEAD2(buffer, "foobar/");
rcl_lexeme_t lexeme1 = RCL_LEXEME_NONE;
rcl_lexeme_t lexeme2 = RCL_LEXEME_NONE;
ret = rcl_lexer_lookahead2_peek2(&buffer, &lexeme1, &lexeme2);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_EQ(RCL_LEXEME_TOKEN, lexeme1);
EXPECT_EQ(RCL_LEXEME_FORWARD_SLASH, lexeme2);
// Test again to make sure peek2 isn't advancing the lexer
lexeme1 = RCL_LEXEME_NONE;
lexeme2 = RCL_LEXEME_NONE;
ret = rcl_lexer_lookahead2_peek2(&buffer, &lexeme1, &lexeme2);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_EQ(RCL_LEXEME_TOKEN, lexeme1);
EXPECT_EQ(RCL_LEXEME_FORWARD_SLASH, lexeme2);
}
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_eof)
{
rcl_ret_t ret;
rcl_lexer_lookahead2_t buffer;
SCOPE_LOOKAHEAD2(buffer, "");
{
rcl_lexeme_t lexeme = RCL_LEXEME_NONE;
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret);
EXPECT_EQ(RCL_LEXEME_EOF, lexeme);
}
{
rcl_lexeme_t lexeme1 = RCL_LEXEME_NONE;
rcl_lexeme_t lexeme2 = RCL_LEXEME_NONE;
ret = rcl_lexer_lookahead2_peek2(&buffer, &lexeme1, &lexeme2);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_EQ(RCL_LEXEME_EOF, lexeme1);
EXPECT_EQ(RCL_LEXEME_EOF, lexeme2);
}
// Accepting keeps the lexer at EOF
{
EXPECT_EQ(RCL_RET_OK, rcl_lexer_lookahead2_accept(&buffer, NULL, NULL));
rcl_lexeme_t lexeme = RCL_LEXEME_NONE;
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret);
EXPECT_EQ(RCL_LEXEME_EOF, lexeme);
}
}
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_accept)
{
rcl_ret_t ret;
rcl_lexer_lookahead2_t buffer;
SCOPE_LOOKAHEAD2(buffer, "foobar/");
rcl_lexeme_t lexeme = RCL_LEXEME_NONE;
const char * lexeme_text;
size_t lexeme_text_length;
// Peek token
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_EQ(RCL_LEXEME_TOKEN, lexeme);
// accept token
ret = rcl_lexer_lookahead2_accept(&buffer, &lexeme_text, &lexeme_text_length);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_STREQ("foobar", std::string(lexeme_text, lexeme_text_length).c_str());
// peek forward slash
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_EQ(RCL_LEXEME_FORWARD_SLASH, lexeme);
// accept forward slash
ret = rcl_lexer_lookahead2_accept(&buffer, &lexeme_text, &lexeme_text_length);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_STREQ("/", std::string(lexeme_text, lexeme_text_length).c_str());
// peek eof
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_EQ(RCL_LEXEME_EOF, lexeme);
// accept eof
ret = rcl_lexer_lookahead2_accept(&buffer, &lexeme_text, &lexeme_text_length);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_STREQ("", std::string(lexeme_text, lexeme_text_length).c_str());
// peek eof again
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_EQ(RCL_LEXEME_EOF, lexeme);
}
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_expect)
{
rcl_ret_t ret;
rcl_lexer_lookahead2_t buffer;
SCOPE_LOOKAHEAD2(buffer, "node_name:__node:=new_1");
const char * lexeme_text;
size_t lexeme_text_length;
ret = rcl_lexer_lookahead2_expect(&buffer, RCL_LEXEME_TOKEN, &lexeme_text, &lexeme_text_length);
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
EXPECT_STREQ("node_name", std::string(lexeme_text, lexeme_text_length).c_str());
ret = rcl_lexer_lookahead2_expect(
&buffer, RCL_LEXEME_FORWARD_SLASH, &lexeme_text, &lexeme_text_length);
EXPECT_EQ(RCL_RET_WRONG_LEXEME, ret) << rcl_get_error_string_safe();
}
#define EXPECT_LOOKAHEAD(expected_lexeme, expected_text, buffer) \
do { \
const char * lexeme_text; \
size_t lexeme_text_length; \
rcl_lexeme_t lexeme; \
ret = rcl_lexer_lookahead2_peek(&buffer, &lexeme); \
EXPECT_EQ(expected_lexeme, lexeme); \
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); \
ret = rcl_lexer_lookahead2_accept(&buffer, &lexeme_text, &lexeme_text_length); \
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); \
EXPECT_STREQ(expected_text, std::string(lexeme_text, lexeme_text_length).c_str()); \
} while (false)
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_lex_long_string)
{
rcl_ret_t ret;
rcl_lexer_lookahead2_t buffer;
SCOPE_LOOKAHEAD2(buffer, ":\\1rostopic://\\2rosservice://~/\\8:=**:*foobar");
EXPECT_LOOKAHEAD(RCL_LEXEME_COLON, ":", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_BR1, "\\1", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_URL_TOPIC, "rostopic://", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_BR2, "\\2", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_URL_SERVICE, "rosservice://", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TILDE_SLASH, "~/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_BR8, "\\8", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_WILD_MULTI, "**", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_COLON, ":", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_WILD_ONE, "*", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foobar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
TEST_F(CLASSNAME(TestLexerLookaheadFixture, RMW_IMPLEMENTATION), test_lex_remap_rules)
{
rcl_ret_t ret;
rcl_lexer_lookahead2_t buffer;
{
SCOPE_LOOKAHEAD2(buffer, "foo:=bar");
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
SCOPE_LOOKAHEAD2(buffer, "/foo/bar:=fiz/buzz");
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "fiz", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "buzz", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Nodename prefix
SCOPE_LOOKAHEAD2(buffer, "nodename:~/foo:=foo");
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "nodename", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_COLON, ":", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TILDE_SLASH, "~/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Partial namespace replacement
SCOPE_LOOKAHEAD2(buffer, "/foo/**:=/fizz/\\1");
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_WILD_MULTI, "**", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "fizz", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_BR1, "\\1", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Full namespace replacement
SCOPE_LOOKAHEAD2(buffer, "/foo/bar/*:=/bar/foo/\\1");
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_WILD_ONE, "*", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_BR1, "\\1", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Change a base name
SCOPE_LOOKAHEAD2(buffer, "**/foo:=\\1/bar");
EXPECT_LOOKAHEAD(RCL_LEXEME_WILD_MULTI, "**", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_BR1, "\\1", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Change namespace
SCOPE_LOOKAHEAD2(buffer, "__ns:=/new/namespace");
EXPECT_LOOKAHEAD(RCL_LEXEME_NS, "__ns", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "new", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "namespace", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Change node name
SCOPE_LOOKAHEAD2(buffer, "__node:=left_camera_driver");
EXPECT_LOOKAHEAD(RCL_LEXEME_NODE, "__node", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "left_camera_driver", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Topic only remap
SCOPE_LOOKAHEAD2(buffer, "rostopic://foo/bar:=bar/foo");
EXPECT_LOOKAHEAD(RCL_LEXEME_URL_TOPIC, "rostopic://", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
{
// Service only remap
SCOPE_LOOKAHEAD2(buffer, "rosservice:///foo/bar:=/bar/foo");
EXPECT_LOOKAHEAD(RCL_LEXEME_URL_SERVICE, "rosservice://", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_SEPARATOR, ":=", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "bar", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_FORWARD_SLASH, "/", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_TOKEN, "foo", buffer);
EXPECT_LOOKAHEAD(RCL_LEXEME_EOF, "", buffer);
}
}

View file

@ -465,3 +465,39 @@ TEST_F(CLASSNAME(TestRemapFixture, RMW_IMPLEMENTATION), other_rules_before_noden
EXPECT_STREQ("remap_name", output);
allocator.deallocate(output, allocator.state);
}
TEST_F(CLASSNAME(TestRemapFixture, RMW_IMPLEMENTATION), url_scheme_rosservice) {
rcl_ret_t ret;
rcl_arguments_t global_arguments;
SCOPE_ARGS(global_arguments, "process_name", "rosservice://foo:=bar");
char * output = NULL;
ret = rcl_remap_service_name(
NULL, &global_arguments, "/ns/foo", "NodeName", "/ns", rcl_get_default_allocator(), &output);
EXPECT_EQ(RCL_RET_OK, ret);
ASSERT_STREQ("/ns/bar", output);
rcl_get_default_allocator().deallocate(output, rcl_get_default_allocator().state);
ret = rcl_remap_topic_name(
NULL, &global_arguments, "/ns/foo", "NodeName", "/ns", rcl_get_default_allocator(), &output);
EXPECT_EQ(RCL_RET_OK, ret);
EXPECT_EQ(NULL, output);
}
TEST_F(CLASSNAME(TestRemapFixture, RMW_IMPLEMENTATION), url_scheme_rostopic) {
rcl_ret_t ret;
rcl_arguments_t global_arguments;
SCOPE_ARGS(global_arguments, "process_name", "rostopic://foo:=bar");
char * output = NULL;
ret = rcl_remap_topic_name(
NULL, &global_arguments, "/ns/foo", "NodeName", "/ns", rcl_get_default_allocator(), &output);
EXPECT_EQ(RCL_RET_OK, ret);
ASSERT_STREQ("/ns/bar", output);
rcl_get_default_allocator().deallocate(output, rcl_get_default_allocator().state);
ret = rcl_remap_service_name(
NULL, &global_arguments, "/ns/foo", "NodeName", "/ns", rcl_get_default_allocator(), &output);
EXPECT_EQ(RCL_RET_OK, ret);
EXPECT_EQ(NULL, output);
}