diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index 980e8bdc2..830e84ef2 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -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 diff --git a/rcl/include/rcl/lexer.h b/rcl/include/rcl/lexer.h new file mode 100644 index 000000000..ae61b839e --- /dev/null +++ b/rcl/include/rcl/lexer.h @@ -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 + +#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. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes [1] + * Thread-Safe | Yes + * Uses Atomics | No + * Lock-Free | Yes + * [1] Only allocates if an argument is invalid or an internal bug is detected. + * + * \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_ diff --git a/rcl/include/rcl/lexer_lookahead.h b/rcl/include/rcl/lexer_lookahead.h new file mode 100644 index 000000000..a72f80b86 --- /dev/null +++ b/rcl/include/rcl/lexer_lookahead.h @@ -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 + +#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() + *
+ * 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() + * + *
+ * 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() + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes [1] + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * [1] Only allocates if an argument is invalid. + * + * \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() + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes [1] + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * [1] Only allocates if an argument is invalid or an internal bug is detected. + * + * \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() + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes [1] + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * [1] Only allocates if an argument is invalid or an internal bug is detected. + * + * \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() + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes [1] + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * [1] Only allocates if an argument is invalid or an error occurs. + * + * \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() + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes [1] + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * [1] Only allocates if an argument is invalid or an error occurs. + * + * \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. +/** + *
+ * 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_ diff --git a/rcl/include/rcl/types.h b/rcl/include/rcl/types.h index 93e9095d4..f63622a27 100644 --- a/rcl/include/rcl/types.h +++ b/rcl/include/rcl/types.h @@ -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_ diff --git a/rcl/src/rcl/arguments.c b/rcl/src/rcl/arguments.c index b40ee46bb..f319e3749 100644 --- a/rcl/src/rcl/arguments.c +++ b/rcl/src/rcl/arguments.c @@ -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( @@ -441,6 +280,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 diff --git a/rcl/src/rcl/lexer.c b/rcl/src/rcl/lexer.c new file mode 100644 index 000000000..6bda5af3f --- /dev/null +++ b/rcl/src/rcl/lexer.c @@ -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 '' transition. + * Every state has exactly one '' transition. + * In the diagram below all states have an `` 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. + * '' 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 `` 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 = "", 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 = "", color=crimson, fontcolor=crimson]; + S8 -> S8 [ label = "a-zA-Z0-9"]; + S8 -> S9 [ label = "_"]; + S9 -> T_TOKEN [ label = "", color=crimson, fontcolor=crimson]; + S9 -> S8 [ label = "a-zA-Z0-9"]; + S10 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S10 -> S11 [ label = "o"]; + S11 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S11 -> S12 [ label = "s"]; + S12 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S12 -> S13 [ label = "t"]; + S12 -> S20 [ label = "s"]; + S13 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S13 -> S14 [ label = "o"]; + S14 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S14 -> S15 [ label = "p"]; + S15 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S15 -> S16 [ label = "i"]; + S16 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S16 -> S17 [ label = "c"]; + S17 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S17 -> S18 [ label = ":"]; + S18 -> S19 [ label = "/"]; + S18 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S19 -> T_URL_TOPIC [ label = "/"]; + S19 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S20 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S20 -> S21 [ label = "e"]; + S21 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S21 -> S22 [ label = "r"]; + S22 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S22 -> S23 [ label = "v"]; + S23 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S23 -> S24 [ label = "i"]; + S24 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S24 -> S25 [ label = "c"]; + S25 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S25 -> S26 [ label = "e"]; + S26 -> S27 [ label = ":"]; + S26 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S27 -> S28 [ label = "/"]; + S27 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S28 -> T_URL_SERVICE [ label = "/"]; + S28 -> S8 [ label = "", color=crimson, fontcolor=crimson]; + S29 -> T_WILD_MULTI[ label = "*"]; + S29 -> T_WILD_ONE [ label = "", color=crimson, fontcolor=crimson]; + S30 -> T_SEPARATOR [ label = "="]; + S30 -> T_COLON [ label = "", 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; +} diff --git a/rcl/src/rcl/lexer_lookahead.c b/rcl/src/rcl/lexer_lookahead.c new file mode 100644 index 000000000..f2c882152 --- /dev/null +++ b/rcl/src/rcl/lexer_lookahead.c @@ -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]); +} diff --git a/rcl/test/CMakeLists.txt b/rcl/test/CMakeLists.txt index 20a5a78c6..6c8e6ad63 100644 --- a/rcl/test/CMakeLists.txt +++ b/rcl/test/CMakeLists.txt @@ -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( diff --git a/rcl/test/rcl/test_arguments.cpp b/rcl/test/rcl/test_arguments.cpp index 7a13a61d6..91df159e7 100644 --- a/rcl/test/rcl/test_arguments.cpp +++ b/rcl/test/rcl/test_arguments.cpp @@ -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) { diff --git a/rcl/test/rcl/test_lexer.cpp b/rcl/test/rcl/test_lexer.cpp new file mode 100644 index 000000000..26a6dc5e7 --- /dev/null +++ b/rcl/test/rcl/test_lexer.cpp @@ -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 + +#include + +#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, "", ""); +} diff --git a/rcl/test/rcl/test_lexer_lookahead.cpp b/rcl/test/rcl/test_lexer_lookahead.cpp new file mode 100644 index 000000000..54d0f285b --- /dev/null +++ b/rcl/test/rcl/test_lexer_lookahead.cpp @@ -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 + +#include + +#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(¬_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); + } +} diff --git a/rcl/test/rcl/test_remap.cpp b/rcl/test/rcl/test_remap.cpp index 894c35977..de3835818 100644 --- a/rcl/test/rcl/test_remap.cpp +++ b/rcl/test/rcl/test_remap.cpp @@ -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); +}