rizin/subprojects/rizin-shell-parser/src/scanner.c
2024-03-02 17:11:02 +08:00

191 lines
5.1 KiB
C

// SPDX-FileCopyrightText: 2020 ret2libc <sirmy15@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include "tree_sitter/parser.h"
#include <ctype.h>
#include <wctype.h>
#include <stdio.h>
#include <string.h>
#define CMD_IDENTIFIER_MAX_LENGTH 32
#define ESCAPE_CHAR '\\'
enum TokenType {
CMD_IDENTIFIER,
HELP_STMT,
FILE_DESCRIPTOR,
EQ_SEP_CONCAT,
CONCAT,
SPEC_SEP,
};
void *tree_sitter_rzcmd_external_scanner_create() {
return NULL;
}
void tree_sitter_rzcmd_external_scanner_destroy(void *payload) {
}
unsigned tree_sitter_rzcmd_external_scanner_serialize(void *payload, char *buffer) {
return 0;
}
void tree_sitter_rzcmd_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
}
static bool is_env_cmd(const char *s) {
return !strncmp (s, "env", 3);
}
static bool is_at_cmd(const char *s) {
return s[0] == '@';
}
static bool is_remote_cmd(const char *s) {
return s[0] == 'R';
}
static bool is_comment(const char *s) {
return !strncmp (s, "/*", 2) || !strcmp (s, "#");
}
static bool is_interpret_cmd(const char *s) {
return s[0] == '.';
}
static bool is_special_start(const int32_t ch) {
return ch == '*' || ch == '(' || ch == '@' || ch == '|' || ch == '>' ||
ch == '.' || ch == '|' || ch == '~' || ch == '!' || ch == '?' ||
ch == '$';
}
static bool is_start_of_command(const int32_t ch) {
return iswalpha (ch) || ch == '$' || ch == ':' || ch == '+' ||
ch == '=' || ch == '/' || ch == '_' || ch == '#' || ch == '\\' ||
ch == '-' || ch == '<' || ch == '&' || ch == '%' || is_special_start (ch);
}
static bool is_mid_command(const char *res, int len, const int32_t ch) {
if (ch == ESCAPE_CHAR) {
return true;
}
if (res[0] == '#') {
if (len == 1) {
return ch == '!' || ch == '?';
}
return ch == '?';
} else if (res[0] == '<') {
return ch == '?';
}
return iswalnum (ch) || ch == '$' || ch == '?' || ch == '.' || ch == '!' ||
ch == '+' || ch == '=' || ch == '/' || ch == '*' ||
ch == '-' || ch == '&' || ch == '_' || ch == '%' ||
(is_interpret_cmd (res) && ch == '(') ||
(is_remote_cmd (res) && ch == '<') || (is_at_cmd (res) && ch == '@');
}
static bool is_concat(const int32_t ch) {
return ch != '\0' && !iswspace(ch) && ch != '#' && ch != '@' &&
ch != '|' && ch != '>' && ch != ';' &&
ch != ')' && ch != '`' && ch != '~' && ch != '\\';
}
static bool is_concat_eq_sep(const int32_t ch) {
return is_concat(ch) && ch != '=';
}
static bool is_recursive_help(const int32_t before_last_ch, const int32_t last_ch) {
return before_last_ch == '?' && last_ch == '*';
}
static bool is_recursive_help_json(const int32_t trd_last_ch, const int32_t snd_last_ch, const int32_t last_ch) {
return trd_last_ch == '?' && snd_last_ch == '*' && last_ch == 'j';
}
static bool scan_number(TSLexer *lexer, const bool *valid_symbols) {
if (!valid_symbols[FILE_DESCRIPTOR]) {
return false;
}
// skip spaces at the beginning
while (iswspace (lexer->lookahead)) {
lexer->advance (lexer, true);
}
if (!iswdigit (lexer->lookahead)) {
return false;
}
lexer->advance (lexer, false);
for (;;) {
if (iswdigit (lexer->lookahead)) {
lexer->advance (lexer, false);
} else if (lexer->lookahead != '>') {
return false;
} else {
break;
}
}
if (lexer->lookahead == '>') {
lexer->result_symbol = FILE_DESCRIPTOR;
return true;
}
return false;
}
bool tree_sitter_rzcmd_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
if (valid_symbols[SPEC_SEP] && lexer->lookahead == ':') {
lexer->advance(lexer, false);
lexer->result_symbol = SPEC_SEP;
return true;
} else if (valid_symbols[CONCAT] && is_concat(lexer->lookahead)) {
lexer->result_symbol = CONCAT;
return true;
} else if (valid_symbols[EQ_SEP_CONCAT] && is_concat_eq_sep(lexer->lookahead)) {
lexer->result_symbol = EQ_SEP_CONCAT;
return true;
}
if (valid_symbols[CMD_IDENTIFIER] || valid_symbols[HELP_STMT]) {
char res[CMD_IDENTIFIER_MAX_LENGTH + 1];
int i_res = 0;
while (iswspace (lexer->lookahead)) {
lexer->advance (lexer, true);
}
if (!is_start_of_command (lexer->lookahead)) {
return false;
}
res[i_res++] = lexer->lookahead;
lexer->advance (lexer, false);
while (lexer->lookahead && i_res < CMD_IDENTIFIER_MAX_LENGTH && is_mid_command (res, i_res, lexer->lookahead)) {
if (lexer->lookahead == ESCAPE_CHAR) {
// ignore escape char and just get the next one, whatever it is
lexer->advance (lexer, false);
}
res[i_res++] = lexer->lookahead;
lexer->advance (lexer, false);
}
res[i_res] = '\0';
if (is_comment (res)) {
return false;
}
if ((res[i_res - 1] == '?') ||
(i_res >= 2 && is_recursive_help(res[i_res - 2], res[i_res - 1])) ||
(i_res >= 3 && is_recursive_help_json(res[i_res - 3], res[i_res - 2], res[i_res - 1]))) {
if (i_res == 1) {
return false;
}
lexer->result_symbol = HELP_STMT;
} else {
if ((is_special_start(res[0]) && strcmp(res, "R=!")) || is_env_cmd(res) || is_at_cmd(res) || !valid_symbols[CMD_IDENTIFIER]) {
return false;
}
lexer->result_symbol = CMD_IDENTIFIER;
}
return true;
}
if (valid_symbols[FILE_DESCRIPTOR]) {
return scan_number (lexer, valid_symbols);
}
return false;
}