Finch 2.4.0: methods, maps, errors as values; str() of any value; macOS and ARM64 docs; Kate support

- fn Struct.method(...) with self by reference, also on C structs
- map[K]V: insertion-ordered hash maps, literals, m[k] += 1, has/get/remove/keys/values
- -> T! / -> !, return error(...), or <value>, or { err }, try; fallible int/float/read_file/write_file/delete_file
- for i, x in list / for k, v in map
- print and str() share one text formatter
- LSP: method and map completion, method signatures; grammar and snippets for VS Code and Kate
- tests: 74 (methods, errors, maps + 11 failure cases); docs at all three levels, en and pl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-09 01:11:12 +02:00
commit b872be3176
55 changed files with 3400 additions and 336 deletions

View file

@ -1,5 +1,11 @@
# Changelog
## 2.4.0
- Highlighting for methods (`fn Point.move`), maps (`map[K]V`), `or`, `try`, `self` and `error(...)`.
- Completion for struct methods and map methods; parameter hints for method calls.
- Snippets: `method`, `fnf` (a function that can fail), `map`, `forkv`, `orb`.
## 2.3.0
- First release: highlighting, snippets, language server, Run and Build.

View file

@ -1,12 +1,12 @@
{
"name": "finch-lang",
"version": "2.3.0",
"version": "2.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "finch-lang",
"version": "2.3.0",
"version": "2.4.0",
"license": "Apache-2.0",
"dependencies": {
"vscode-languageclient": "^9.0.1"

View file

@ -2,7 +2,7 @@
"name": "finch-lang",
"displayName": "Finch",
"description": "The Finch programming language: highlighting, errors as you type, completion, hover, go to definition, and a Run button.",
"version": "2.3.0",
"version": "2.4.0",
"publisher": "nexoniarz",
"license": "Apache-2.0",
"icon": "images/icon.png",

View file

@ -1,16 +1,160 @@
{
"main": { "prefix": "main", "body": ["fn main() {", "\t$0", "}"], "description": "The program's start" },
"main with arguments": { "prefix": "mainargs", "body": ["fn main([]str args) {", "\t$0", "}"], "description": "main that gets the command-line arguments" },
"function": { "prefix": "fn", "body": ["fn ${1:name}(${2}) {", "\t$0", "}"], "description": "A function" },
"function with result": { "prefix": "fnr", "body": ["fn ${1:name}(${2}) -> ${3:int} {", "\t$0", "}"], "description": "A function that returns a value" },
"struct": { "prefix": "struct", "body": ["struct ${1:Name} {", "\t${2:int} ${3:field}", "}"], "description": "A struct" },
"if": { "prefix": "if", "body": ["if ${1:condition} {", "\t$0", "}"] },
"if else": { "prefix": "ife", "body": ["if ${1:condition} {", "\t$2", "} else {", "\t$0", "}"] },
"for range": { "prefix": "for", "body": ["for ${1:i} in ${2:0}..${3:n} {", "\t$0", "}"], "description": "Count from start to end-1" },
"for each": { "prefix": "foreach", "body": ["for ${1:item} in ${2:list} {", "\t$0", "}"], "description": "Every element of an array or str" },
"while": { "prefix": "while", "body": ["while ${1:condition} {", "\t$0", "}"] },
"print": { "prefix": "print", "body": ["print(${1})"] },
"input": { "prefix": "input", "body": ["${1:answer} := input(\"${2:Question? }\")"] },
"import C header": { "prefix": "importc", "body": ["import \"${1:stdio.h}\""] },
"defer": { "prefix": "defer", "body": ["defer ${1}"], "description": "Run this when the block ends" }
"main": {
"prefix": "main",
"body": [
"fn main() {",
"\t$0",
"}"
],
"description": "The program's start"
},
"main with arguments": {
"prefix": "mainargs",
"body": [
"fn main([]str args) {",
"\t$0",
"}"
],
"description": "main that gets the command-line arguments"
},
"function": {
"prefix": "fn",
"body": [
"fn ${1:name}(${2}) {",
"\t$0",
"}"
],
"description": "A function"
},
"function with result": {
"prefix": "fnr",
"body": [
"fn ${1:name}(${2}) -> ${3:int} {",
"\t$0",
"}"
],
"description": "A function that returns a value"
},
"struct": {
"prefix": "struct",
"body": [
"struct ${1:Name} {",
"\t${2:int} ${3:field}",
"}"
],
"description": "A struct"
},
"if": {
"prefix": "if",
"body": [
"if ${1:condition} {",
"\t$0",
"}"
]
},
"if else": {
"prefix": "ife",
"body": [
"if ${1:condition} {",
"\t$2",
"} else {",
"\t$0",
"}"
]
},
"for range": {
"prefix": "for",
"body": [
"for ${1:i} in ${2:0}..${3:n} {",
"\t$0",
"}"
],
"description": "Count from start to end-1"
},
"for each": {
"prefix": "foreach",
"body": [
"for ${1:item} in ${2:list} {",
"\t$0",
"}"
],
"description": "Every element of an array or str"
},
"while": {
"prefix": "while",
"body": [
"while ${1:condition} {",
"\t$0",
"}"
]
},
"print": {
"prefix": "print",
"body": [
"print(${1})"
]
},
"input": {
"prefix": "input",
"body": [
"${1:answer} := input(\"${2:Question? }\")"
]
},
"import C header": {
"prefix": "importc",
"body": [
"import \"${1:stdio.h}\""
]
},
"defer": {
"prefix": "defer",
"body": [
"defer ${1}"
],
"description": "Run this when the block ends"
},
"method": {
"prefix": "method",
"body": [
"fn ${1:Type}.${2:name}(${3}) {",
"\t$0",
"}"
],
"description": "A method: self is the struct it is called on"
},
"fallible function": {
"prefix": "fnf",
"body": [
"fn ${1:name}(${2}) -> ${3:int}! {",
"\t$0",
"}"
],
"description": "A function that can fail: return error(\"...\")"
},
"map": {
"prefix": "map",
"body": [
"map[${1:str}]${2:int} ${3:name}"
],
"description": "An empty map"
},
"or block": {
"prefix": "orb",
"body": [
"or {",
"\t${1:print(err)}",
"\t${2:return}",
"}"
],
"description": "Handle a failure: err is the message"
},
"for key, value": {
"prefix": "forkv",
"body": [
"for ${1:key}, ${2:value} in ${3:map} {",
"\t$0",
"}"
],
"description": "Every key and value of a map (or index and element of an array)"
}
}

View file

@ -85,12 +85,18 @@
}
},
"function": {
"match": "\\b(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"match": "\\b(fn)\\s+(?:([A-Za-z_][A-Za-z0-9_]*)(\\.))?([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": {
"name": "storage.type.function.finch"
},
"2": {
"name": "entity.name.type.finch"
},
"3": {
"name": "punctuation.accessor.finch"
},
"4": {
"name": "entity.name.function.finch"
}
}
@ -111,7 +117,7 @@
},
{
"name": "keyword.control.finch",
"match": "\\b(if|else|while|for|in|break|continue|return|defer)\\b"
"match": "\\b(if|else|while|for|in|break|continue|return|defer|or|try)\\b"
},
{
"name": "storage.type.function.finch",
@ -126,11 +132,15 @@
"match": "\\b(true|false|null)\\b"
},
{
"name": "storage.type.primitive.finch",
"match": "\\b(int|float|bool|char|str|ptr|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64)\\b"
"name": "variable.language.self.finch",
"match": "\\bself\\b"
},
{
"match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free)\\s*(?=\\()",
"name": "storage.type.primitive.finch",
"match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64)\\b"
},
{
"match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free|error)\\s*(?=\\()",
"captures": {
"1": {
"name": "support.function.builtin.finch"
@ -238,4 +248,4 @@
]
}
}
}
}

View file

@ -20,6 +20,12 @@ const code = [
' p := Point(x: 1)',
' return a + p.x',
'}',
'fn Point.move(int dx) -> int! {',
' map[str]int m',
' n := try parse(s) or 0',
' self.x += dx',
' return error("no")',
'}',
];
const expect = [ // [line, token text, scope that must be present]
[0, 'import', 'keyword.control.import'], [0, '"stdio.h"', 'string.quoted.double'],
@ -32,6 +38,11 @@ const expect = [ // [line, token text, scope that must be present]
[7, 'c', 'string.quoted.single'], [7, '3.5', 'constant.numeric.float'], [7, 'true', 'constant.language'], [7, 'null', 'constant.language'],
[8, 'Point', 'entity.name.type'],
[9, 'return', 'keyword.control'],
[11, 'Point', 'entity.name.type'], [11, 'move', 'entity.name.function'], [11, '!', 'keyword.operator'],
[12, 'map', 'storage.type.primitive'],
[13, 'try', 'keyword.control'], [13, 'or', 'keyword.control'],
[14, 'self', 'variable.language.self'],
[15, 'error', 'support.function.builtin'],
];
registry.loadGrammar('source.finch').then((g) => {
let state = vsctm.INITIAL, toks = [];