Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

744 lines
22 KiB
C
Generated

#include "tree_sitter/alloc.h"
#include "tree_sitter/parser.h"
#include <ctype.h>
#include <wctype.h>
#include <string.h>
enum TokenType {
LINE_CONTINUATION,
INTEGER_LITERAL,
FLOAT_LITERAL,
BOZ_LITERAL,
STRING_LITERAL,
STRING_LITERAL_KIND,
END_OF_STATEMENT,
PREPROC_UNARY_OPERATOR,
HOLLERITH_CONSTANT,
DO_LABEL,
DO_LABEL_VIRTUAL,
DO_LABEL_CONTINUE,
};
// at most 100 nested labeled do loops, should be sufficient
#define MAX_LABEL_STACK 100
typedef struct {
bool in_line_continuation;
// stack for tracking active DO labels and their counts
int32_t depth;
int32_t labels[MAX_LABEL_STACK];
int32_t counts[MAX_LABEL_STACK];
// counter for emitting virtual label token for closing labeled do statements
int32_t pending_label_virtual;
// flag for emitting eos tokens right after a virtual label token
bool is_pending_eos_virtual;
} Scanner;
// first parse number and only then decide what kind of token to emit,
// for example: we need to check label stack first before we can decide whether
// we have a do-label or an integer literal
typedef enum {
NUMBER_NONE,
NUMBER_INTEGER,
NUMBER_FLOAT
} NumberType;
typedef struct {
// type of number
NumberType type;
// value in case it turns out to be a label
int32_t value;
// count digits to abort value computation after 5 digits
// (maximal number for labels)
int digit_count;
} NumberResult;
// consume current character into current token and advance
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
// ignore current character and advance
static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
// is `chr` ok for an identifier?
static bool is_identifier_char(char chr) { return iswalnum(chr) || chr == '_'; }
static bool is_boz_sentinel(char chr) {
switch (chr) {
case 'B':
case 'b':
case 'O':
case 'o':
case 'Z':
case 'z':
return true;
default:
return false;
}
}
static bool is_exp_sentinel(char chr) {
switch (chr) {
case 'D':
case 'd':
case 'E':
case 'e':
case 'Q':
case 'q':
return true;
default:
return false;
}
}
// If in the middle of a literal, '&' is required in both lines
static bool skip_literal_continuation_sequence(TSLexer *lexer) {
if (lexer->lookahead != '&') {
return true;
}
advance(lexer);
while (iswspace(lexer->lookahead)) {
advance(lexer);
}
// second '&' technically required to continue the literal
if (lexer->lookahead == '&') {
advance(lexer);
return true;
}
return false;
}
// consume digits and compute value if requested
static bool scan_int(TSLexer *lexer, int32_t *value, int *count) {
if (!iswdigit(lexer->lookahead)) {
return false;
}
if (value && count) {
*value = 0;
*count = 0;
}
// outer loop for handling line continuations
while (true) {
// consume digits
while (iswdigit(lexer->lookahead)) {
if (value && count && *count < 7) {
*value = *value * 10 + (lexer->lookahead - '0');
(*count)++;
}
advance(lexer);
}
lexer->mark_end(lexer);
if (lexer->lookahead == '&') {
// with the lookahead check above, it returns true if in a continuation
// line and consumes both & and whitespace characters inbetween
if (skip_literal_continuation_sequence(lexer)) {
continue;
}
}
// no digits and no continuation found
break;
}
return true;
}
/// Scan integer or float of the forms 1XXX, 1.0XXX, 0.1XXX, 1.XDX, .1X etc.
static NumberResult scan_number(TSLexer *lexer) {
NumberResult result = {NUMBER_NONE, 0, 0};
// assume integer, but if no proper digits are found, reset to NUMBER_NONE
result.type = NUMBER_INTEGER;
// collect initial digits and compute value (specifically required to
// determine label value)
bool digits = scan_int(lexer, &result.value, &result.digit_count);
if (lexer->lookahead == '.') {
advance(lexer);
// exclude decimal if followed by any letter other than d/D and e/E
// if no leading digits are present and a non-digit follows
// the decimal it's a nonmatch.
if (digits && !iswalnum(lexer->lookahead)) {
lexer->mark_end(lexer); // add decimal to token
}
// this is not yet decided, we still need to find some digits
result.type = NUMBER_FLOAT;
}
// if next char isn't number return since we handle exp
// notation and precision identifiers separately. If there are
// no leading digit it's a nonmatch.
digits = scan_int(lexer, NULL, NULL) || digits;
if (digits) {
// process exp notation
if (is_exp_sentinel(lexer->lookahead)) {
advance(lexer);
if (lexer->lookahead == '+' || lexer->lookahead == '-') {
advance(lexer);
lexer->mark_end(lexer);
}
if (!scan_int(lexer, NULL, NULL)) {
result.type = NUMBER_INTEGER;
return result; // valid number token with junk after it
}
result.type = NUMBER_FLOAT;
}
}
if (!digits) {
result.type = NUMBER_NONE;
}
return result;
}
static bool scan_boz(TSLexer *lexer) {
lexer->result_symbol = BOZ_LITERAL;
bool boz_prefix = false;
char quote = '\0';
if (is_boz_sentinel(lexer->lookahead)) {
advance(lexer);
boz_prefix = true;
}
if (lexer->lookahead == '\'' || lexer->lookahead == '"') {
quote = lexer->lookahead;
advance(lexer);
if (!iswxdigit(lexer->lookahead)) {
return false;
}
while (iswxdigit(lexer->lookahead)) {
advance(lexer); // store all hex digits
}
if (lexer->lookahead != quote) {
return false;
}
advance(lexer); // store enclosing quote
if (!boz_prefix && !is_boz_sentinel(lexer->lookahead)) {
return false; // no boz suffix or prefix provided
}
lexer->mark_end(lexer);
return true;
}
return false;
}
/// Need to dynamically determining the length of the Hollerith constant
static bool scan_hollerith_constant(TSLexer *lexer) {
// Try to parse nH<text> where n is the number of characters in <text>
// Read integer prefix 'n'
unsigned length = 0;
while (iswdigit(lexer->lookahead)) {
unsigned new_length = length * 10 + (lexer->lookahead - '0');
// The number of characters has no limit but overflow has to be handled
if (new_length < length) {
return false;
}
length = new_length;
advance(lexer);
if (!skip_literal_continuation_sequence(lexer)) {
return false;
}
}
// 0 is invalid 'n' in Hollerith constants
if (length == 0) {
return false;
}
// Expect 'H' or 'h'
if (lexer->lookahead != 'H' && lexer->lookahead != 'h') {
return false;
}
advance(lexer);
// Read exactly 'n' characters
for (unsigned i = 0; i < length; i++) {
if (!lexer->lookahead || lexer->eof(lexer)) {
return false;
}
if (!skip_literal_continuation_sequence(lexer)) {
return false;
}
advance(lexer);
}
lexer->result_symbol = HOLLERITH_CONSTANT;
lexer->mark_end(lexer);
return true;
}
static bool scan_end_of_statement(Scanner *scanner, TSLexer *lexer) {
// Things that end statements in Fortran:
//
// - semicolons
// - end-of-line (various representations)
// - comments
//
// Comments are a bit surprising, but it turns out to be
// easier to handle line continuations if comments consume the
// newline
// Semicolons and EOF always end the statement
if (lexer->eof(lexer)) {
skip(lexer);
lexer->result_symbol = END_OF_STATEMENT;
return true;
}
// If we're in a line continuation, then don't end the statement
if (scanner->in_line_continuation) {
return false;
}
// Consume end of line characters, we allow '\n', '\r\n' and
// '\r' to cover unix, MSDOS and old style Macintosh.
// Handle comments here too, but don't consume them
if (lexer->lookahead == '\r') {
skip(lexer);
if (lexer->lookahead == '\n') {
skip(lexer);
}
} else {
if (lexer->lookahead == '\n') {
skip(lexer);
} else if (lexer->lookahead != '!') {
// Not a newline and not a comment, so not an
// end-of-statement
return false;
}
}
lexer->result_symbol = END_OF_STATEMENT;
return true;
}
static bool scan_start_line_continuation(Scanner *scanner, TSLexer *lexer) {
// Now see if we should start a line continuation
scanner->in_line_continuation = (lexer->lookahead == '&');
if (!scanner->in_line_continuation) {
return false;
}
// Consume the '&'
advance(lexer);
lexer->result_symbol = LINE_CONTINUATION;
return true;
}
static bool scan_end_line_continuation(Scanner *scanner, TSLexer *lexer) {
if (!scanner->in_line_continuation) {
return false;
}
// Everything except comments ends a line continuation
if (lexer->lookahead == '!') {
return false;
}
scanner->in_line_continuation = false;
// Consume any leading line continuation markers
if (lexer->lookahead == '&') {
advance(lexer);
}
lexer->result_symbol = LINE_CONTINUATION;
return true;
}
static bool scan_string_literal_kind(TSLexer *lexer) {
// Strictly, it's allowed for the kind to be an integer literal, in
// practice I've not seen it
if (!iswalpha(lexer->lookahead)) {
return false;
}
lexer->result_symbol = STRING_LITERAL_KIND;
// We need two characters of lookahead to see `_"`
char current_char = '\0';
while (is_identifier_char(lexer->lookahead) && !lexer->eof(lexer)) {
current_char = lexer->lookahead;
// Don't capture the trailing underscore as part of the kind identifier
if (lexer->lookahead == '_') {
lexer->mark_end(lexer);
}
advance(lexer);
}
if ((current_char != '_') || (lexer->lookahead != '"' && lexer->lookahead != '\'')) {
return false;
}
return true;
}
static bool scan_string_literal(TSLexer *lexer) {
const char opening_quote = lexer->lookahead;
if (opening_quote != '"' && opening_quote != '\'') {
return false;
}
advance(lexer);
lexer->result_symbol = STRING_LITERAL;
while (lexer->lookahead != '\n' && !lexer->eof(lexer)) {
// Handle line continuations: strictly speaking, we MUST have
// both trailing '&' on first line AND leading '&' on second
// line, though most compilers do accept string literals
// missing the second '&'. In practice, everyone does seem to
// include it.
// We need to handle this here because sometimes '&' is part
// of the literal and not a continuation marker, and otherwise
// the parser gets confused, especially if there's no
// whitespace before the '&' in the string
// The literal token will end up containing the line
// continuation as well as any blank or comment lines inside
// the quotes (yes, you can have comments _inside_ string
// literals if they contain a line continuation)
if (lexer->lookahead == '&') {
advance(lexer);
// Consume blanks up to the end of the line or non-blank
while (iswblank(lexer->lookahead)) {
advance(lexer);
}
// If we hit the end of the line, consume all whitespace,
// including new lines
if (lexer->lookahead == '\n' || lexer->lookahead == '\r') {
while (iswspace(lexer->lookahead)) {
advance(lexer);
}
}
continue;
}
// If we hit the same kind of quote that opened this literal,
// check to see if there's two in a row, and if so, consume
// both of them
if (lexer->lookahead == opening_quote) {
advance(lexer);
// It was just one quote, so we've successfully reached
// the end of the literal. We also need to check that an
// escaped quote isn't split in half by a line
// continuation -- people do this!
lexer->mark_end(lexer);
skip_literal_continuation_sequence(lexer);
if (lexer->lookahead != opening_quote) {
return true;
}
}
advance(lexer);
}
// We hit the end of the line without an '&', so this is an
// unclosed string literal (an error)
return false;
}
/// Need an external scanner to catch '!' before its parsed as a comment
static bool scan_preproc_unary_operator(TSLexer *lexer) {
const char next_char = lexer->lookahead;
if (next_char == '!' || next_char == '~' || next_char == '-' || next_char == '+') {
advance(lexer);
lexer->result_symbol = PREPROC_UNARY_OPERATOR;
return true;
}
return false;
}
static void track_labeled_do(Scanner *scanner, int32_t label) {
// check if label already exists
int i = scanner->depth - 1;
if (scanner->labels[i] == label) {
scanner->counts[i]++;
return;
}
// not at top of stack, assume new label, add it to stack
if (scanner->depth < MAX_LABEL_STACK) {
scanner->labels[scanner->depth] = label;
scanner->counts[scanner->depth] = 1;
scanner->depth++;
} else {
// should we properly abort here?
}
}
// check whether an end of statement token for virtual do labels is pending,
// emit END_OF_STATEMENT and update internal state accordingly
static inline bool scan_do_label_eos(Scanner *scanner, TSLexer *lexer) {
if (scanner->is_pending_eos_virtual) {
scanner->is_pending_eos_virtual = false;
lexer->result_symbol = END_OF_STATEMENT;
return true;
} else {
return false;
}
}
// check whether do labels are pending, emit DO_LABEL_VIRTUAL or DO_LABEL_CONTINUE
// and update internal state accordingly
static inline bool scan_do_label_pending(Scanner *scanner, TSLexer *lexer) {
if (scanner->pending_label_virtual > 0) {
if (scanner->pending_label_virtual > 1) {
scanner->pending_label_virtual--;
// schedule an eos for the next token to finish the virtual statement
scanner->is_pending_eos_virtual = true;
lexer->result_symbol = DO_LABEL_VIRTUAL;
} else {
// emit last termination symbol which is do_label_continue
scanner->pending_label_virtual = 0;
lexer->result_symbol = DO_LABEL_CONTINUE;
}
return true;
} else {
return false;
}
}
// only invoked after parser has found a "do" (and thus DO_LABEL is a valid
// token) and the scan has consumed a proper integer value provided as label
static void scan_do_label(Scanner *scanner, TSLexer *lexer, int32_t label) {
track_labeled_do(scanner, label);
lexer->result_symbol = DO_LABEL;
}
static bool scan_do_label_continue(Scanner *scanner, TSLexer *lexer, int32_t label) {
// determine whether this label belongs to the last labeled do,
// if it does, remove it from stack and determine how many loops it closes
int32_t loops_to_close = 0;
if (scanner->depth > 0) {
int i = scanner->depth - 1;
if (scanner->labels[i] == label) {
loops_to_close = scanner->counts[i];
// remove from stack
scanner->depth--;
}
}
// scanner->counts[i] is always greater than zero, if the label is on the stack,
// hence loops_to_close == 0 means depth=0 or label is not at top of stack
if (loops_to_close == 0) {
// label not on stack, hence this does not close a labeled do
return false;
}
scanner->pending_label_virtual = loops_to_close;
scanner->is_pending_eos_virtual = false;
scan_do_label_pending(scanner, lexer);
return true;
}
// check for label, number of boz token
static bool scan_label_number_boz(Scanner *scanner, TSLexer *lexer, const bool *valid_symbols) {
// extract out root number from expression (without kind if present)
NumberResult result = scan_number(lexer);
// check for a do-label, should have at most 5 digits and DO_LABEL
// or DO_LABEL_CONTINUE are valid symbols
if (result.type == NUMBER_INTEGER && result.digit_count < 6) {
if (valid_symbols[DO_LABEL]) {
scan_do_label(scanner, lexer, result.value);
return true;
}
if (valid_symbols[DO_LABEL_CONTINUE] &&
scan_do_label_continue(scanner, lexer, result.value)) {
return true;
}
}
// not a label
if (result.type == NUMBER_INTEGER) {
lexer->result_symbol = INTEGER_LITERAL;
return true;
} else if (result.type == NUMBER_FLOAT) {
lexer->result_symbol = FLOAT_LITERAL;
return true;
}
if (scan_boz(lexer)) {
return true;
}
return false;
}
static bool scan(Scanner *scanner, TSLexer *lexer, const bool *valid_symbols) {
// handle pending virtual labels and eos first
if (valid_symbols[END_OF_STATEMENT]) {
if (scan_do_label_eos(scanner, lexer)) {
return true;
}
}
if(valid_symbols[DO_LABEL_CONTINUE] || valid_symbols[DO_LABEL_VIRTUAL]) {
if (scan_do_label_pending(scanner, lexer)) {
return true;
}
}
// Consume any leading whitespace except newlines
while (iswblank(lexer->lookahead)) {
skip(lexer);
}
// Close the current statement if we can
if (valid_symbols[END_OF_STATEMENT]) {
if (scan_end_of_statement(scanner, lexer)) {
return true;
}
}
// We're now either in a line continuation or between
// statements, so we should eat all whitespace including
// newlines, until we come to something more interesting
while (iswspace(lexer->lookahead)) {
skip(lexer);
}
if (scan_end_line_continuation(scanner, lexer)) {
return true;
}
if (valid_symbols[STRING_LITERAL]) {
if (scan_string_literal(lexer)) {
return true;
}
}
if (valid_symbols[HOLLERITH_CONSTANT]) {
if (scan_hollerith_constant(lexer)) {
return true;
}
}
if (valid_symbols[INTEGER_LITERAL] ||
valid_symbols[FLOAT_LITERAL] ||
valid_symbols[BOZ_LITERAL] ||
valid_symbols[DO_LABEL] ||
valid_symbols[DO_LABEL_CONTINUE]) {
if (scan_label_number_boz(scanner, lexer, valid_symbols)) {
return true;
}
}
if (valid_symbols[PREPROC_UNARY_OPERATOR]) {
if (scan_preproc_unary_operator(lexer)) {
return true;
}
}
if (scan_start_line_continuation(scanner, lexer)) {
return true;
}
if (valid_symbols[STRING_LITERAL_KIND]) {
// This may need a lot of lookahead, so should (probably) always
// be the last token to look for
if (scan_string_literal_kind(lexer)) {
return true;
}
}
return false;
}
void *tree_sitter_fortran_external_scanner_create() {
Scanner *scanner = ts_calloc(1, sizeof(Scanner));
scanner->in_line_continuation = false;
scanner->depth = 0;
scanner->pending_label_virtual = 0;
scanner->is_pending_eos_virtual = false;
return scanner;
}
bool tree_sitter_fortran_external_scanner_scan(void *payload, TSLexer *lexer,
const bool *valid_symbols) {
Scanner *scanner = (Scanner *)payload;
return scan(scanner, lexer, valid_symbols);
}
unsigned tree_sitter_fortran_external_scanner_serialize(void *payload,
char *buffer) {
Scanner *scanner = (Scanner *)payload;
if (scanner->depth > MAX_LABEL_STACK) return 0;
size_t size = 0;
buffer[size] = (char)scanner->in_line_continuation;
size += 1;
memcpy(&buffer[size], &scanner->depth, sizeof(int32_t));
size += sizeof(int32_t);
memcpy(&buffer[size], scanner->labels, scanner->depth * sizeof(int32_t));
size += scanner->depth * sizeof(int32_t);
memcpy(&buffer[size], scanner->counts, scanner->depth * sizeof(int32_t));
size += scanner->depth * sizeof(int32_t);
memcpy(&buffer[size], &scanner->pending_label_virtual, sizeof(int32_t));
size += sizeof(int32_t);
buffer[size] = (char)scanner->is_pending_eos_virtual;
size += 1;
return size;
}
void tree_sitter_fortran_external_scanner_deserialize(void *payload,
const char *buffer,
unsigned length) {
Scanner *scanner = (Scanner *)payload;
if (length == 0) {
scanner->in_line_continuation = false;
scanner->depth = 0;
scanner->pending_label_virtual = 0;
scanner->is_pending_eos_virtual = false;
return;
}
size_t size = 0;
scanner->in_line_continuation = buffer[size];
size += 1;
memcpy(&scanner->depth, &buffer[size], sizeof(int32_t));
size += sizeof(int32_t);
if (scanner->depth > MAX_LABEL_STACK) {
scanner->depth = 0;
scanner->pending_label_virtual = 0;
scanner->is_pending_eos_virtual = false;
return;
}
memcpy(scanner->labels, &buffer[size], scanner->depth * sizeof(int32_t));
size += scanner->depth * sizeof(int32_t);
memcpy(scanner->counts, &buffer[size], scanner->depth * sizeof(int32_t));
size += scanner->depth * sizeof(int32_t);
memcpy(&scanner->pending_label_virtual, &buffer[size], sizeof(int32_t));
size += sizeof(int32_t);
scanner->is_pending_eos_virtual = buffer[size];
size += 1;
}
void tree_sitter_fortran_external_scanner_destroy(void *payload) {
Scanner *scanner = (Scanner *)payload;
ts_free(scanner);
}