From 875dbf3140935d43bd996443fbe5bb05a5044395 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 22 Feb 2025 17:56:42 +0000 Subject: Disallow syntax: `proc(x,:T)` --- src/parser.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 94f8fd42c..a3e95f451 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4343,6 +4343,9 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl if (f->curr_token.kind == Token_Colon) { + if (f->prev_token.kind == Token_Comma) { + syntax_error(f->prev_token, "Trailing comma before a colon is not allowed"); + } Array names = convert_to_ident_list(f, list, true, allow_poly_names); // Copy for semantic reasons if (names.count == 0) { syntax_error(f->curr_token, "Empty field declaration"); -- cgit v1.2.3 From de755f95ecd5574f291d03b9488b5c4b8f07042c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 22 Feb 2025 17:58:14 +0000 Subject: Minor rearrange for parsing field lists --- src/parser.cpp | 229 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 116 insertions(+), 113 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index a3e95f451..f09590a55 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4342,33 +4342,132 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl } - if (f->curr_token.kind == Token_Colon) { - if (f->prev_token.kind == Token_Comma) { - syntax_error(f->prev_token, "Trailing comma before a colon is not allowed"); + if (f->curr_token.kind != Token_Colon) { + // NOTE(bill): proc(Type, Type, Type) + for (AstAndFlags const &item : list) { + Ast *type = item.node; + Token token = blank_token; + if (allowed_flags&FieldFlag_Results) { + // NOTE(bill): Make this nothing and not `_` + token.string = str_lit(""); + } + + auto names = array_make(ast_allocator(f), 1); + token.pos = ast_token(type).pos; + names[0] = ast_ident(f, token); + u32 flags = check_field_prefixes(f, list.count, allowed_flags, item.flags); + Token tag = {}; + Ast *param = ast_field(f, names, item.node, nullptr, flags, tag, docs, f->line_comment); + array_add(¶ms, param); + } + + if (name_count_) *name_count_ = total_name_count; + return ast_field_list(f, start_token, params); + } + + // NOTE(bill): proc(ident, ident, ident: Type) + + if (f->prev_token.kind == Token_Comma) { + syntax_error(f->prev_token, "Trailing comma before a colon is not allowed"); + } + Array names = convert_to_ident_list(f, list, true, allow_poly_names); // Copy for semantic reasons + if (names.count == 0) { + syntax_error(f->curr_token, "Empty field declaration"); + } + bool any_polymorphic_names = check_procedure_name_list(names); + u32 set_flags = 0; + if (list.count > 0) { + set_flags = list[0].flags; + } + set_flags = check_field_prefixes(f, names.count, allowed_flags, set_flags); + total_name_count += names.count; + + Ast *type = nullptr; + Ast *default_value = nullptr; + Token tag = {}; + + expect_token_after(f, Token_Colon, "field list"); + if (f->curr_token.kind != Token_Eq) { + type = parse_var_type(f, allow_ellipsis, allow_typeid_token); + Ast *tt = unparen_expr(type); + if (tt == nullptr) { + syntax_error(f->prev_token, "Invalid type expression in field list"); + } else if (is_signature && !any_polymorphic_names && tt->kind == Ast_TypeidType && tt->TypeidType.specialization != nullptr) { + syntax_error(type, "Specialization of typeid is not allowed without polymorphic names"); + } + } + + if (allow_token(f, Token_Eq)) { + default_value = parse_expr(f, false); + if (!allow_default_parameters) { + syntax_error(f->curr_token, "Default parameters are only allowed for procedures"); + default_value = nullptr; + } + } + + if (default_value != nullptr && names.count > 1) { + syntax_error(f->curr_token, "Default parameters can only be applied to single values"); + } + + if (allowed_flags == FieldFlag_Struct && default_value != nullptr) { + syntax_error(default_value, "Default parameters are not allowed for structs"); + default_value = nullptr; + } + + if (type != nullptr && type->kind == Ast_Ellipsis) { + if (seen_ellipsis) syntax_error(type, "Extra variadic parameter after ellipsis"); + seen_ellipsis = true; + if (names.count != 1) { + syntax_error(type, "Variadic parameters can only have one field name"); } - Array names = convert_to_ident_list(f, list, true, allow_poly_names); // Copy for semantic reasons + } else if (seen_ellipsis && default_value == nullptr) { + syntax_error(f->curr_token, "Extra parameter after ellipsis without a default value"); + } + + if (type != nullptr && default_value == nullptr) { + if (f->curr_token.kind == Token_String) { + tag = expect_token(f, Token_String); + if ((allowed_flags & FieldFlag_Tags) == 0) { + syntax_error(tag, "Field tags are only allowed within structures"); + } + } + } + + bool more_fields = allow_field_separator(f); + Ast *param = ast_field(f, names, type, default_value, set_flags, tag, docs, f->line_comment); + array_add(¶ms, param); + + if (!more_fields) { + if (name_count_) *name_count_ = total_name_count; + return ast_field_list(f, start_token, params); + } + + while (f->curr_token.kind != follow && + f->curr_token.kind != Token_EOF && + f->curr_token.kind != Token_Semicolon) { + CommentGroup *docs = f->lead_comment; + + if (!is_signature) parse_enforce_tabs(f); + u32 set_flags = parse_field_prefixes(f); + Token tag = {}; + Array names = parse_ident_list(f, allow_poly_names); if (names.count == 0) { syntax_error(f->curr_token, "Empty field declaration"); + break; } bool any_polymorphic_names = check_procedure_name_list(names); - u32 set_flags = 0; - if (list.count > 0) { - set_flags = list[0].flags; - } set_flags = check_field_prefixes(f, names.count, allowed_flags, set_flags); total_name_count += names.count; Ast *type = nullptr; Ast *default_value = nullptr; - Token tag = {}; - expect_token_after(f, Token_Colon, "field list"); if (f->curr_token.kind != Token_Eq) { type = parse_var_type(f, allow_ellipsis, allow_typeid_token); Ast *tt = unparen_expr(type); - if (tt == nullptr) { - syntax_error(f->prev_token, "Invalid type expression in field list"); - } else if (is_signature && !any_polymorphic_names && tt->kind == Ast_TypeidType && tt->TypeidType.specialization != nullptr) { + if (is_signature && !any_polymorphic_names && + tt != nullptr && + tt->kind == Ast_TypeidType && tt->TypeidType.specialization != nullptr) { syntax_error(type, "Specialization of typeid is not allowed without polymorphic names"); } } @@ -4385,11 +4484,6 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl syntax_error(f->curr_token, "Default parameters can only be applied to single values"); } - if (allowed_flags == FieldFlag_Struct && default_value != nullptr) { - syntax_error(default_value, "Default parameters are not allowed for structs"); - default_value = nullptr; - } - if (type != nullptr && type->kind == Ast_Ellipsis) { if (seen_ellipsis) syntax_error(type, "Extra variadic parameter after ellipsis"); seen_ellipsis = true; @@ -4409,105 +4503,14 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl } } - bool more_fields = allow_field_separator(f); + + bool ok = allow_field_separator(f); Ast *param = ast_field(f, names, type, default_value, set_flags, tag, docs, f->line_comment); array_add(¶ms, param); - if (!more_fields) { - if (name_count_) *name_count_ = total_name_count; - return ast_field_list(f, start_token, params); - } - - while (f->curr_token.kind != follow && - f->curr_token.kind != Token_EOF && - f->curr_token.kind != Token_Semicolon) { - CommentGroup *docs = f->lead_comment; - - if (!is_signature) parse_enforce_tabs(f); - u32 set_flags = parse_field_prefixes(f); - Token tag = {}; - Array names = parse_ident_list(f, allow_poly_names); - if (names.count == 0) { - syntax_error(f->curr_token, "Empty field declaration"); - break; - } - bool any_polymorphic_names = check_procedure_name_list(names); - set_flags = check_field_prefixes(f, names.count, allowed_flags, set_flags); - total_name_count += names.count; - - Ast *type = nullptr; - Ast *default_value = nullptr; - expect_token_after(f, Token_Colon, "field list"); - if (f->curr_token.kind != Token_Eq) { - type = parse_var_type(f, allow_ellipsis, allow_typeid_token); - Ast *tt = unparen_expr(type); - if (is_signature && !any_polymorphic_names && - tt != nullptr && - tt->kind == Ast_TypeidType && tt->TypeidType.specialization != nullptr) { - syntax_error(type, "Specialization of typeid is not allowed without polymorphic names"); - } - } - - if (allow_token(f, Token_Eq)) { - default_value = parse_expr(f, false); - if (!allow_default_parameters) { - syntax_error(f->curr_token, "Default parameters are only allowed for procedures"); - default_value = nullptr; - } - } - - if (default_value != nullptr && names.count > 1) { - syntax_error(f->curr_token, "Default parameters can only be applied to single values"); - } - - if (type != nullptr && type->kind == Ast_Ellipsis) { - if (seen_ellipsis) syntax_error(type, "Extra variadic parameter after ellipsis"); - seen_ellipsis = true; - if (names.count != 1) { - syntax_error(type, "Variadic parameters can only have one field name"); - } - } else if (seen_ellipsis && default_value == nullptr) { - syntax_error(f->curr_token, "Extra parameter after ellipsis without a default value"); - } - - if (type != nullptr && default_value == nullptr) { - if (f->curr_token.kind == Token_String) { - tag = expect_token(f, Token_String); - if ((allowed_flags & FieldFlag_Tags) == 0) { - syntax_error(tag, "Field tags are only allowed within structures"); - } - } - } - - - bool ok = allow_field_separator(f); - Ast *param = ast_field(f, names, type, default_value, set_flags, tag, docs, f->line_comment); - array_add(¶ms, param); - - if (!ok) { - break; - } - } - - if (name_count_) *name_count_ = total_name_count; - return ast_field_list(f, start_token, params); - } - - for (AstAndFlags const &item : list) { - Ast *type = item.node; - Token token = blank_token; - if (allowed_flags&FieldFlag_Results) { - // NOTE(bill): Make this nothing and not `_` - token.string = str_lit(""); + if (!ok) { + break; } - - auto names = array_make(ast_allocator(f), 1); - token.pos = ast_token(type).pos; - names[0] = ast_ident(f, token); - u32 flags = check_field_prefixes(f, list.count, allowed_flags, item.flags); - Token tag = {}; - Ast *param = ast_field(f, names, item.node, nullptr, flags, tag, docs, f->line_comment); - array_add(¶ms, param); } if (name_count_) *name_count_ = total_name_count; -- cgit v1.2.3 From d23453811d3b8f5518845b412b9044a5bb5e92fb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 24 Feb 2025 13:13:36 +0000 Subject: Improve semicolon checking rules when parsing --- src/parser.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index f09590a55..f38f79607 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3016,9 +3016,10 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { syntax_error(token, "Expected a type or range, got nothing"); } - if (allow_token(f, Token_Semicolon)) { + if (f->curr_token.kind == Token_Semicolon && f->curr_token.string == ";") { + expect_token(f, Token_Semicolon); underlying = parse_type(f); - } else if (allow_token(f, Token_Comma)) { + } else if (allow_token(f, Token_Comma) || allow_token(f, Token_Semicolon)) { String p = token_to_string(f->prev_token); syntax_error(token_end_of_line(f, f->prev_token), "Expected a semicolon, got a %.*s", LIT(p)); @@ -4578,6 +4579,9 @@ gb_internal Ast *parse_do_body(AstFile *f, Token const &token, char const *msg) gb_internal bool parse_control_statement_semicolon_separator(AstFile *f) { Token tok = peek_token(f); if (tok.kind != Token_OpenBrace) { + if (f->curr_token.kind == Token_Semicolon && f->curr_token.string != ";") { + syntax_error(token_end_of_line(f, f->prev_token), "Expected ';', got newline"); + } return allow_token(f, Token_Semicolon); } if (f->curr_token.string == ";") { -- cgit v1.2.3 From 1f814c33dcfccec9073d15214268457c0d6ea273 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 10 Apr 2025 11:36:03 +0100 Subject: Support subtargets in build tags: `#build darwin:generic` and `#build linux:android, darwin:ios` --- base/runtime/core.odin | 4 +++ core/odin/parser/file_tags.odin | 24 ++++++++++--- src/build_settings.cpp | 34 +++++++++++++++--- src/parser.cpp | 12 ++++--- src/string.cpp | 77 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 13 deletions(-) (limited to 'src/parser.cpp') diff --git a/base/runtime/core.odin b/base/runtime/core.odin index cd7d35540..5ab8a7008 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -559,10 +559,14 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{ Odin_Platform_Subtarget_Type :: enum int { Default, iOS, + Android, } */ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) +Odin_Platform_Subtarget_Types :: bit_set[Odin_Platform_Subtarget_Type] + + /* // Defined internally by the compiler Odin_Sanitizer_Flag :: enum u32 { diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin index c5c6637c3..fff59ee1e 100644 --- a/core/odin/parser/file_tags.odin +++ b/core/odin/parser/file_tags.odin @@ -30,14 +30,27 @@ File_Tags :: struct { } @require_results -get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type { +get_build_os_from_string :: proc(str: string) -> (found_os: runtime.Odin_OS_Type, found_subtarget: runtime.Odin_Platform_Subtarget_Type) { + str_os, _, str_subtarget := strings.partition(str, ":") + fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type) for os in fields { - if strings.equal_fold(os.name, str) { - return runtime.Odin_OS_Type(os.value) + if strings.equal_fold(os.name, str_os) { + found_os = runtime.Odin_OS_Type(os.value) + break } } - return .Unknown + if str_subtarget != "" { + fields := reflect.enum_fields_zipped(runtime.Odin_Platform_Subtarget_Type) + for subtarget in fields { + if strings.equal_fold(subtarget.name, str_subtarget) { + found_subtarget = runtime.Odin_Platform_Subtarget_Type(subtarget.value) + break + } + } + } + + return } @require_results get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { @@ -187,7 +200,8 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags if value == "ignore" { tags.ignore = true - } else if os := get_build_os_from_string(value); os != .Unknown { + } else if os, subtarget := get_build_os_from_string(value); os != .Unknown { + _ = subtarget // TODO(bill): figure out how to handle the subtarget logic if is_notted { os_negative += {os} } else { diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c2575e2d4..c941e0f68 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -847,13 +847,39 @@ gb_global NamedTargetMetrics *selected_target_metrics; gb_global Subtarget selected_subtarget; -gb_internal TargetOsKind get_target_os_from_string(String str) { +gb_internal TargetOsKind get_target_os_from_string(String str, Subtarget *subtarget_ = nullptr) { + String os_name = str; + String subtarget = {}; + auto part = string_partition(str, str_lit(":")); + if (part.match.len == 1) { + os_name = part.head; + subtarget = part.tail; + } + + TargetOsKind kind = TargetOs_Invalid; + for (isize i = 0; i < TargetOs_COUNT; i++) { - if (str_eq_ignore_case(target_os_names[i], str)) { - return cast(TargetOsKind)i; + if (str_eq_ignore_case(target_os_names[i], os_name)) { + kind = cast(TargetOsKind)i; + break; } } - return TargetOs_Invalid; + if (subtarget_) *subtarget_ = Subtarget_Default; + + if (subtarget.len != 0) { + if (str_eq_ignore_case(subtarget, "generic") || str_eq_ignore_case(subtarget, "default")) { + if (subtarget_) *subtarget_ = Subtarget_Default; + } else { + for (isize i = 1; i < Subtarget_COUNT; i++) { + if (str_eq_ignore_case(subtarget_strings[i], subtarget)) { + if (subtarget_) *subtarget_ = cast(Subtarget)i; + break; + } + } + } + } + + return kind; } gb_internal TargetArchKind get_target_arch_from_string(String str) { diff --git a/src/parser.cpp b/src/parser.cpp index f38f79607..a397585e8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6157,7 +6157,7 @@ gb_internal String build_tag_get_token(String s, String *out) { isize width = utf8_decode(&s[n], s.len-n, &rune); if (n == 0 && rune == '!') { - } else if (!rune_is_letter(rune) && !rune_is_digit(rune)) { + } else if (!rune_is_letter(rune) && !rune_is_digit(rune) && rune != ':') { isize k = gb_max(gb_max(n, width), 1); *out = substring(s, k, s.len); return substring(s, 0, k); @@ -6209,7 +6209,9 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) { continue; } - TargetOsKind os = get_target_os_from_string(p); + Subtarget subtarget = Subtarget_Default; + + TargetOsKind os = get_target_os_from_string(p, &subtarget); TargetArchKind arch = get_target_arch_from_string(p); num_tokens += 1; @@ -6223,11 +6225,13 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) { if (os != TargetOs_Invalid) { this_kind_os_seen = true; + bool same_subtarget = (subtarget == Subtarget_Default) || (subtarget == selected_subtarget); + GB_ASSERT(arch == TargetArch_Invalid); if (is_notted) { - this_kind_correct = this_kind_correct && (os != build_context.metrics.os); + this_kind_correct = this_kind_correct && (os != build_context.metrics.os || !same_subtarget); } else { - this_kind_correct = this_kind_correct && (os == build_context.metrics.os); + this_kind_correct = this_kind_correct && (os == build_context.metrics.os && same_subtarget); } } else if (arch != TargetArch_Invalid) { this_kind_arch_seen = true; diff --git a/src/string.cpp b/src/string.cpp index 88b679540..ae8d066b1 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -336,6 +336,83 @@ gb_internal Array split_lines_from_array(Array const &array, gbAlloc return lines; } +enum : u32 { PRIME_RABIN_KARP = 16777619u }; + +gb_internal u32 hash_str_rabin_karp(String const &s, u32 *pow_) { + u32 hash = 0; + u32 pow = 1; + for (isize i = 0; i < s.len; i++) { + hash = hash*PRIME_RABIN_KARP + cast(u32)s.text[i]; + } + u32 sq = PRIME_RABIN_KARP; + for (isize i = s.len; i > 0; i >>= 1) { + if ((i & 1) != 0) { + pow *= sq; + } + sq *= sq; + } + if (pow_) *pow_ = pow; + return hash; + +} + + +gb_internal isize string_index(String const &s, String const &substr) { + isize n = substr.len; + if (n == 0) { + return 0; + } else if (n == 1) { + return string_index_byte(s, substr[0]); + } else if (n == s.len) { + if (s == substr) { + return 0; + } + return -1; + } else if (n > s.len) { + return -1; + } + u32 pow = 1; + u32 hash = hash_str_rabin_karp(s, &pow); + u32 h = 0; + for (isize i = 0; i < n; i++) { + h = h*PRIME_RABIN_KARP + cast(u32)s.text[i]; + } + if (h == hash && substring(s, 0, n) == substr) { + return 0; + } + for (isize i = n; i < s.len; /**/) { + h *= PRIME_RABIN_KARP; + h += cast(u32)s.text[i]; + h -= pow * u32(s.text[i-n]); + i += 1; + if (h == hash && substring(s, i-n, i) == substr) { + return i - n; + } + } + return -1; +} + + +struct StringPartition { + String head; + String match; + String tail; +}; + +gb_internal StringPartition string_partition(String const &str, String const &sep) { + StringPartition res = {}; + isize i = string_index(str, sep); + if (i < 0) { + res.head = str; + return res; + } + + res.head = substring(str, 0, i); + res.match = substring(str, i, i+sep.len); + res.tail = substring(str, i+sep.len, str.len); + return res; +} + gb_internal bool string_contains_char(String const &s, u8 c) { isize i; for (i = 0; i < s.len; i++) { -- cgit v1.2.3 From 74bab6d42faf02b26fc6690957277842a9462694 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 29 May 2025 16:29:52 +0100 Subject: Fix #5232 by adding an edge case --- src/parser.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index a397585e8..0057ab611 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3274,6 +3274,8 @@ gb_internal Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { case Token_OpenBracket: { bool prev_allow_range = f->allow_range; f->allow_range = false; + defer (f->allow_range = prev_allow_range); + Token open = {}, close = {}, interval = {}; Ast *indices[2] = {}; @@ -3282,6 +3284,13 @@ gb_internal Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { f->expr_level++; open = expect_token(f, Token_OpenBracket); + if (f->curr_token.kind == Token_CloseBracket) { + error(f->curr_token, "Expected an operand, got ]"); + close = expect_token(f, Token_CloseBracket); + operand = ast_index_expr(f, operand, nullptr, open, close); + break; + } + switch (f->curr_token.kind) { case Token_Ellipsis: case Token_RangeFull: @@ -3331,7 +3340,6 @@ gb_internal Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { operand = ast_index_expr(f, operand, indices[0], open, close); } - f->allow_range = prev_allow_range; } break; case Token_Pointer: // Deference -- cgit v1.2.3 From ccdd14394efb4d5e8341491528f5fc141a6d9e14 Mon Sep 17 00:00:00 2001 From: Hayden Gray Date: Thu, 5 Jun 2025 11:24:50 -0400 Subject: [parser.cpp] - packages with `.odin` in the name no longer attempt to parse as odin files --- src/parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 0057ab611..942e83f29 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5794,7 +5794,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const for (FileInfo fi : list) { String name = fi.name; String ext = path_extension(name); - if (ext == FILE_EXT) { + if (ext == FILE_EXT && !path_is_directory(name)) { files_with_ext += 1; } if (ext == FILE_EXT && !is_excluded_target_filename(name)) { @@ -5819,7 +5819,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const for (FileInfo fi : list) { String name = fi.name; String ext = path_extension(name); - if (ext == FILE_EXT) { + if (ext == FILE_EXT && !path_is_directory(name)) { if (is_excluded_target_filename(name)) { continue; } -- cgit v1.2.3