├── grammar └── func │ ├── .gitignore │ ├── README.md │ ├── binding.gyp │ ├── grammar │ ├── utils.js │ ├── types.js │ ├── statements.js │ ├── functions.js │ └── expressions.js │ ├── bindings │ ├── node │ │ ├── index.js │ │ └── binding.cc │ └── rust │ │ ├── build.rs │ │ └── lib.rs │ ├── Cargo.toml │ ├── package.json │ ├── LICENSE │ ├── examples │ ├── nominator-code.fc │ └── stdlib.fc │ ├── queries │ └── highlights.scm │ ├── grammar.js │ └── src │ └── tree_sitter │ └── parser.h ├── logo.png ├── .yarnrc.yml ├── .gitattributes ├── server ├── tree-sitter-func.wasm ├── jest.config.js ├── src │ ├── connection.ts │ ├── queries │ │ ├── utils │ │ │ └── parserQuery.ts │ │ ├── globals.ts │ │ ├── directives.ts │ │ └── locals.ts │ ├── utils │ │ ├── crc32.spec.ts │ │ ├── batchExecute.ts │ │ ├── position.ts │ │ ├── crc32.ts │ │ ├── lruMap.ts │ │ └── trie.ts │ ├── features │ │ ├── references.ts │ │ ├── definitions.ts │ │ ├── formatting │ │ │ ├── index.ts │ │ │ └── formatters.ts │ │ ├── codeLens.ts │ │ ├── depsIndex.ts │ │ ├── rename.ts │ │ ├── typeInference.ts │ │ ├── documentSymbols.ts │ │ ├── diagnostics.ts │ │ ├── completion.ts │ │ └── symbolIndex.ts │ ├── parser.ts │ ├── config.ts │ ├── queries.spec.ts │ ├── documentStore.ts │ ├── server.ts │ ├── trees.ts │ └── __snapshots__ │ │ └── queries.spec.ts.snap ├── tsconfig.json └── package.json ├── .gitignore ├── .vscodeignore ├── client ├── tsconfig.json ├── package.json ├── yarn.lock └── src │ └── extension.ts ├── tsconfig.json ├── .vscode └── launch.json ├── language-configuration.json ├── snippets └── snippets.json ├── CHANGELOG.md ├── webpack.config.js ├── syntaxes └── .tmLanguage.json ├── README.md ├── package.json └── LICENSE /grammar/func/.gitignore: -------------------------------------------------------------------------------- 1 | tree-sitter-func.wasm -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-community/vscode-func/HEAD/logo.png -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-3.4.1.cjs 2 | nodeLinker: node-modules -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /server/tree-sitter-func.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-community/vscode-func/HEAD/server/tree-sitter-func.wasm -------------------------------------------------------------------------------- /grammar/func/README.md: -------------------------------------------------------------------------------- 1 | # tree-sitter-func 2 | FunC grammar for [tree-sitter](https://github.com/tree-sitter/tree-sitter). 3 | -------------------------------------------------------------------------------- /server/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | rootDir: 'src' 6 | }; -------------------------------------------------------------------------------- /server/src/connection.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createConnection, 3 | ProposedFeatures, 4 | } from 'vscode-languageserver/node'; 5 | 6 | 7 | export const connection = createConnection(ProposedFeatures.all); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.vsix 3 | .DS_Store 4 | dist/ 5 | 6 | # Yarn >2.0.0 7 | .yarn/* 8 | */.yarn 9 | 10 | !.yarn/patches 11 | !.yarn/plugins 12 | !.yarn/releases 13 | !.yarn/sdks 14 | !.yarn/version -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | **/* 2 | 3 | # Include only this files 4 | !dist/ 5 | !LICENSE 6 | !CHANGELOG.md 7 | !package.json 8 | !README.md 9 | !language-configuration.json 10 | !syntaxes/ 11 | !snippets/ 12 | !logo.png -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "sourceMap": true 7 | }, 8 | "include": ["src"], 9 | "exclude": ["node_modules", ".vscode-test"] 10 | } -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "sourceMap": true, 7 | "strict": true 8 | }, 9 | "include": ["src"], 10 | "exclude": ["node_modules", ".vscode-test"] 11 | } -------------------------------------------------------------------------------- /server/src/queries/utils/parserQuery.ts: -------------------------------------------------------------------------------- 1 | import * as Parser from 'web-tree-sitter'; 2 | import { language } from '../../parser'; 3 | 4 | export function parserQuery(text: TemplateStringsArray) { 5 | return () => { 6 | return language.query(text.toString()) 7 | } 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "sourceMap": true 7 | }, 8 | "include": [ 9 | "src" 10 | ], 11 | "exclude": [ 12 | "node_modules", 13 | ".vscode-test" 14 | ], 15 | "references": [ 16 | { "path": "./client" }, 17 | { "path": "./server" } 18 | ] 19 | } -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "description": "VSCode client part of the extension", 4 | "private": true, 5 | "engines": { 6 | "vscode": "^1.63.0" 7 | }, 8 | "dependencies": { 9 | "vscode-languageclient": "^8.0.2", 10 | "vscode-uri": "^3.0.7" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "^17.0.23", 14 | "@types/vscode": "^1.63.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/src/utils/crc32.spec.ts: -------------------------------------------------------------------------------- 1 | import { crc32 } from './crc32' 2 | 3 | describe('crc32', () => { 4 | it.each([ 5 | ['op::change_destination', 3136103846], 6 | ['op::upgrade', 3690657815], 7 | ['op::change_owner', 4058968892] 8 | ])('should calculate correct crc32', (input: string, output: number) => { 9 | expect(crc32(input)).toBe(output); 10 | }) 11 | }) -------------------------------------------------------------------------------- /server/src/features/references.ts: -------------------------------------------------------------------------------- 1 | import * as lsp from 'vscode-languageserver'; 2 | import { TextDocument } from 'vscode-languageserver-textdocument'; 3 | import { Trees } from '../trees'; 4 | 5 | export interface IUsage { 6 | name: string; 7 | range: lsp.Range; 8 | kind: lsp.SymbolKind; 9 | } 10 | 11 | export function getDocumentUsages(document: TextDocument, trees: Trees): IUsage[] { 12 | return []; 13 | } -------------------------------------------------------------------------------- /server/src/utils/batchExecute.ts: -------------------------------------------------------------------------------- 1 | export async function batchExecute(tasks: (() => Promise)[], factor: number): Promise { 2 | let result: R[] = []; 3 | let pos = 0; 4 | while (true) { 5 | const partTasks = tasks.slice(pos, pos + factor); 6 | if (partTasks.length === 0) { 7 | break; 8 | } 9 | const partResult = await Promise.all(partTasks.map(task => task())); 10 | pos += factor; 11 | result.push(...partResult); 12 | } 13 | return result; 14 | } -------------------------------------------------------------------------------- /grammar/func/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "tree_sitter_func_binding", 5 | "include_dirs": [ 6 | " sep(',', rule) 14 | const commaSep1 = rule => sep1(',', rule) 15 | const commaSep2 = rule => sep2(',', rule) 16 | 17 | module.exports = { 18 | sep, 19 | sep1, 20 | sep2, 21 | commaSep, 22 | commaSep1, 23 | commaSep2 24 | } 25 | -------------------------------------------------------------------------------- /grammar/func/bindings/node/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | module.exports = require("../../build/Release/tree_sitter_func_binding"); 3 | } catch (error1) { 4 | if (error1.code !== 'MODULE_NOT_FOUND') { 5 | throw error1; 6 | } 7 | try { 8 | module.exports = require("../../build/Debug/tree_sitter_func_binding"); 9 | } catch (error2) { 10 | if (error2.code !== 'MODULE_NOT_FOUND') { 11 | throw error2; 12 | } 13 | throw error1 14 | } 15 | } 16 | 17 | try { 18 | module.exports.nodeTypeInfo = require("../../src/node-types.json"); 19 | } catch (_) {} 20 | -------------------------------------------------------------------------------- /server/src/queries/globals.ts: -------------------------------------------------------------------------------- 1 | import { parserQuery } from './utils/parserQuery'; 2 | import * as Parser from 'web-tree-sitter'; 3 | import { asLspRange } from '../utils/position'; 4 | 5 | const query = parserQuery` 6 | (function_definition name: (function_name) @function) 7 | (global_var_declaration name: (identifier) @globalVar) 8 | (constant_declaration name: (identifier) @const) 9 | ` 10 | 11 | export function queryGlobals(node: Parser.SyntaxNode) { 12 | return query().captures(node).map(a => ({ 13 | text: a.node.text, 14 | type: a.name, 15 | range: asLspRange(a.node), 16 | node: a.node 17 | })); 18 | } -------------------------------------------------------------------------------- /grammar/func/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tree-sitter-c" 3 | description = "c grammar for the tree-sitter parsing library" 4 | version = "0.0.1" 5 | keywords = ["incremental", "parsing", "c"] 6 | categories = ["parsing", "text-editors"] 7 | repository = "https://github.com/tree-sitter/tree-sitter-javascript" 8 | edition = "2018" 9 | license = "MIT" 10 | 11 | build = "bindings/rust/build.rs" 12 | include = [ 13 | "bindings/rust/*", 14 | "grammar.js", 15 | "queries/*", 16 | "src/*", 17 | ] 18 | 19 | [lib] 20 | path = "bindings/rust/lib.rs" 21 | 22 | [dependencies] 23 | tree-sitter = "0.19.3" 24 | 25 | [build-dependencies] 26 | cc = "1.0" 27 | -------------------------------------------------------------------------------- /server/src/parser.ts: -------------------------------------------------------------------------------- 1 | import * as Parser from 'web-tree-sitter'; 2 | 3 | 4 | export let language: Parser.Language 5 | 6 | export const initParser = async (treeSitterUri: string, langUri: string) => { 7 | if (language) { 8 | return; 9 | } 10 | const options: object | undefined = { 11 | locateFile() { 12 | return treeSitterUri 13 | } 14 | }; 15 | await Parser.init(options) 16 | language = await Parser.Language.load(langUri) 17 | 18 | } 19 | 20 | export const createParser = () => { 21 | const parser = new Parser(); 22 | parser.setLanguage(language); 23 | parser.setTimeoutMicros(1000 * 1000); 24 | return parser 25 | } -------------------------------------------------------------------------------- /server/src/config.ts: -------------------------------------------------------------------------------- 1 | export const config: { 2 | symbolDiscovery: 'everything' | 'only #include', 3 | autocompleteAddParentheses: boolean, 4 | experimentalDiagnostics: boolean 5 | } = { 6 | symbolDiscovery: process.env.FUNC_SYMBOL_DISCOVERY as any, 7 | autocompleteAddParentheses: process.env.FUNC_AUTOCOMPLETE_ADD_PARENTHESES === 'true', 8 | experimentalDiagnostics: process.env.FUNC_EXPRERIMENTAL_DIAGNOSTICS === 'true' 9 | } 10 | 11 | 12 | export function mutateConfig(next: any) { 13 | config.symbolDiscovery = next.symbolDiscovery; 14 | config.autocompleteAddParentheses = next.autocompleteAddParentheses; 15 | config.experimentalDiagnostics = next.experimentalDiagnostics; 16 | } -------------------------------------------------------------------------------- /server/src/utils/crc32.ts: -------------------------------------------------------------------------------- 1 | const POLYNOMIAL = -306674912; 2 | 3 | let crc32_table: Int32Array | undefined = undefined; 4 | 5 | export function crc32(str: string, crc = 0xFFFFFFFF) { 6 | let bytes = Buffer.from(str); 7 | if (crc32_table === undefined) { 8 | calcTable(); 9 | } 10 | for (let i = 0; i < bytes.length; ++i) 11 | crc = crc32_table![(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8); 12 | return (crc ^ -1) >>> 0; 13 | } 14 | 15 | function calcTable() { 16 | crc32_table = new Int32Array(256); 17 | for (let i = 0; i < 256; i++) { 18 | let r = i; 19 | for (let bit = 8; bit > 0; --bit) 20 | r = ((r & 1) ? ((r >>> 1) ^ POLYNOMIAL) : (r >>> 1)); 21 | crc32_table[i] = r; 22 | } 23 | } -------------------------------------------------------------------------------- /grammar/func/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "func-language", 3 | "version": "0.1.2", 4 | "description": "FunC grammar for node-tree-sitter", 5 | "repository": "github:akifoq/tree-sitter-func", 6 | "main": "bindings/node", 7 | "keywords": [ 8 | "parser", 9 | "lexer" 10 | ], 11 | "author": "akifoq", 12 | "license": "MIT", 13 | "dependencies": { 14 | "nan": "^2.16.0" 15 | }, 16 | "devDependencies": { 17 | "tree-sitter-cli": "^0.20.6" 18 | }, 19 | "scripts": { 20 | "build": "tree-sitter generate && tree-sitter build-wasm --docker", 21 | "test": "tree-sitter test" 22 | }, 23 | "tree-sitter": [ 24 | { 25 | "scope": "source.func", 26 | "file-types": [ 27 | "fc" 28 | ] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /server/src/queries.spec.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { createParser, initParser } from './parser' 3 | import { queryGlobals } from './queries/globals'; 4 | 5 | beforeAll(async () => { 6 | await initParser(path.resolve(__dirname + '/../../node_modules/web-tree-sitter/tree-sitter.wasm'), path.resolve(__dirname, '..', 'tree-sitter-func.wasm')); 7 | }) 8 | 9 | it('should query globals', () => { 10 | let parser = createParser(); 11 | let tree = parser.parse(` 12 | global var id; 13 | 14 | () test() { 15 | if (id == 1) { 16 | int i = 0; 17 | } 18 | int a = 0; 19 | } 20 | `) 21 | 22 | let globals = queryGlobals(tree.rootNode); 23 | expect(globals).toMatchSnapshot(); 24 | }); -------------------------------------------------------------------------------- /server/src/queries/directives.ts: -------------------------------------------------------------------------------- 1 | import { parserQuery } from './utils/parserQuery'; 2 | import * as Parser from 'web-tree-sitter'; 3 | 4 | const query = parserQuery` 5 | (include_directive) @include 6 | (pragma_directive) @pragma 7 | ` 8 | 9 | export function queryDirectives(node: Parser.SyntaxNode) { 10 | let captures = query().captures(node); 11 | let includes = captures.filter(a => a.name === 'include'); 12 | let pramgas = captures.filter(a => a.name === 'pragma'); 13 | 14 | return { 15 | includes: includes.map(a => ({ 16 | path: a.node.childForFieldName('path')!.text, 17 | node: a.node, 18 | })), 19 | pramgas: pramgas.map(a => ({ 20 | key: a.node.childForFieldName('key')!.text, 21 | value: a.node.childForFieldName('value')?.text, 22 | node: a.node 23 | })) 24 | } 25 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach", 9 | "port": 6009, 10 | "request": "attach", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "type": "pwa-node" 15 | }, 16 | { 17 | "args": [ 18 | "--extensionDevelopmentPath=${workspaceFolder}" 19 | ], 20 | "name": "Launch Extension", 21 | "outFiles": [ 22 | "${workspaceFolder}/out/**/*.js" 23 | ], 24 | "request": "launch", 25 | "type": "pwa-extensionHost" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /server/src/utils/lruMap.ts: -------------------------------------------------------------------------------- 1 | export class LRUMap extends Map { 2 | 3 | constructor(private readonly _options: { size: number, dispose: (entries: [K, V][]) => void }) { 4 | super(); 5 | } 6 | 7 | set(key: K, value: V) { 8 | super.set(key, value); 9 | this._checkSize(); 10 | return this; 11 | } 12 | 13 | get(key: K): V | undefined { 14 | if (!this.has(key)) { 15 | return undefined; 16 | } 17 | const result = super.get(key); 18 | this.delete(key); 19 | this.set(key, result!); 20 | return result; 21 | } 22 | 23 | private _checkSize(): void { 24 | setTimeout(() => { 25 | 26 | const slack = Math.ceil(this._options.size * .3); 27 | 28 | if (this.size < this._options.size + slack) { 29 | return; 30 | } 31 | const result = Array.from(this.entries()).slice(0, slack); 32 | for (let [key] of result) { 33 | this.delete(key); 34 | } 35 | this._options.dispose(result); 36 | }, 0); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": ";;", 5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": [ "{-", "-}" ] 7 | }, 8 | "wordPattern": "(`.*`)|([a-zA-Z_$%](\\w|['?:$%])+)|([$%a-zA-Z])", 9 | // symbols used as brackets 10 | "brackets": [ 11 | ["{", "}"], 12 | ["[", "]"], 13 | ["(", ")"] 14 | ], 15 | // symbols that are auto closed when typing 16 | "autoClosingPairs": [ 17 | ["{", "}"], 18 | ["[", "]"], 19 | ["(", ")"], 20 | ["\"", "\""] 21 | ], 22 | // symbols that can be used to surround a selection 23 | "surroundingPairs": [ 24 | ["{", "}"], 25 | ["[", "]"], 26 | ["(", ")"], 27 | ["\"", "\""], 28 | ] 29 | } -------------------------------------------------------------------------------- /snippets/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "Recieve internal message": { 3 | "prefix": ["recv_internal", "main"], 4 | "body": ["() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {", "\t$0", "}"], 5 | "description": "A template for receiving internal message." 6 | }, 7 | "Recieve external message": { 8 | "prefix": ["recv_external"], 9 | "body": ["() recv_external(slice in_msg) impure {", "\t$0", "}"], 10 | "description": "A template for receiving external message." 11 | }, 12 | "Supported interfaces": { 13 | "prefix": ["supported_interfaces"], 14 | "body": ["_ supported_interfaces() method_id {", "\t;; see https://github.com/ton-foundation/specs/blob/main/specs/wtf-0001.md", "\treturn (123515602279859691144772641439386770278);", "}"], 15 | "description": "A template for WTF-01 compatible introspection. (ref: https://github.com/ton-foundation/specs/blob/main/specs/wtf-0001.md)" 16 | } 17 | } -------------------------------------------------------------------------------- /grammar/func/bindings/node/binding.cc: -------------------------------------------------------------------------------- 1 | #include "tree_sitter/parser.h" 2 | #include 3 | #include "nan.h" 4 | 5 | using namespace v8; 6 | 7 | extern "C" TSLanguage * tree_sitter_func(); 8 | 9 | namespace { 10 | 11 | NAN_METHOD(New) {} 12 | 13 | void Init(Local exports, Local module) { 14 | Local tpl = Nan::New(New); 15 | tpl->SetClassName(Nan::New("Language").ToLocalChecked()); 16 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 17 | 18 | Local constructor = Nan::GetFunction(tpl).ToLocalChecked(); 19 | Local instance = constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked(); 20 | Nan::SetInternalFieldPointer(instance, 0, tree_sitter_func()); 21 | 22 | Nan::Set(instance, Nan::New("name").ToLocalChecked(), Nan::New("func").ToLocalChecked()); 23 | Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance); 24 | } 25 | 26 | NODE_MODULE(tree_sitter_func_binding, Init) 27 | 28 | } // namespace 29 | -------------------------------------------------------------------------------- /grammar/func/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 akifoq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /grammar/func/grammar/types.js: -------------------------------------------------------------------------------- 1 | const {commaSep, commaSep1, commaSep2} = require('./utils.js') 2 | 3 | module.exports = { 4 | _type: $ => choice( 5 | $._atomic_type, 6 | $.function_type, 7 | ), 8 | 9 | function_type: $ => prec.right(100, seq( 10 | $._atomic_type, 11 | '->', 12 | $._type 13 | )), 14 | 15 | _atomic_type: $ => choice( 16 | $.primitive_type, 17 | $.var_type, 18 | $.hole_type, 19 | $.type_identifier, 20 | $.tensor_type, 21 | $.unit_type, 22 | $.tuple_type, 23 | $._parenthesized_type 24 | ), 25 | 26 | _parenthesized_type: $ => seq('(', $._type, ')'), 27 | 28 | primitive_type: $ => choice( 29 | 'int', 30 | 'cell', 31 | 'slice', 32 | 'builder', 33 | 'cont', 34 | 'tuple', 35 | ), 36 | 37 | constant_type: $ => choice( 38 | 'int', 39 | 'slice', 40 | ), 41 | 42 | tensor_type: $ => seq('(', commaSep2($._type), ')'), 43 | 44 | tuple_type: $ => seq('[', commaSep($._type), ']'), 45 | 46 | var_type: $ => 'var', 47 | hole_type: $ => alias($.underscore, $.hole_type), 48 | unit_type: $ => '()', 49 | 50 | type_identifier: $ => alias($.identifier, $.type_identifier), 51 | } 52 | -------------------------------------------------------------------------------- /grammar/func/examples/nominator-code.fc: -------------------------------------------------------------------------------- 1 | ;; Nominator pool 2 | 3 | () recv_external(slice in_msg) impure { 4 | ;; do nothing for external messages 5 | } 6 | 7 | () recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { 8 | 9 | var cs = in_msg_cell.begin_parse(); 10 | var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 11 | slice s_addr = cs~load_msg_addr(); 12 | slice elector = elector_address(); 13 | slice config = config_address(); 14 | 15 | int processed? = 0; 16 | 17 | if ( equal_slices(s_addr, elector) ) { 18 | process_elector_response(flags, in_msg, msg_value); 19 | processed? = -1; 20 | } 21 | 22 | if ( (~ processed?) & equal_slices(s_addr, config) ) { 23 | process_config_response(flags, in_msg); 24 | processed? = -1; 25 | } 26 | 27 | if (flags & 1) { 28 | ;; ignore all bounced messages 29 | return (); 30 | } 31 | 32 | tuple storage = storage_tuple(load_data()); 33 | slice validator_address = storage.car(); 34 | 35 | if ( (~ processed?) & equal_slices(s_addr, validator_address) ) { 36 | processed? = process_validator_request(in_msg, msg_value, storage, s_addr); 37 | } 38 | 39 | if( ~ processed?) { 40 | processed? = process_nominator_request(in_msg, msg_value, storage, s_addr); 41 | } 42 | throw_unless(404, processed?); 43 | } 44 | -------------------------------------------------------------------------------- /grammar/func/grammar/statements.js: -------------------------------------------------------------------------------- 1 | const {commaSep, commaSep1, commaSep2} = require('./utils.js') 2 | 3 | module.exports = { 4 | statement: $ => choice( 5 | $.return_statement, 6 | $.block_statement, 7 | $.expression_statement, 8 | $.empty_statement, 9 | $.repeat_statement, 10 | $.if_statement, 11 | $.do_statement, 12 | $.while_statement, 13 | ), 14 | 15 | return_statement: $ => seq('return', $.expression, ';'), 16 | block_statement: $ => seq('{', repeat($.statement), '}'), 17 | expression_statement: $ => seq($.expression, ';'), 18 | empty_statement: $ => ';', 19 | repeat_statement: $ => seq('repeat', field("count", $.expression), field("body", $.block_statement)), 20 | 21 | if_statement: $ => seq( 22 | choice('if', 'ifnot'), 23 | $._if_statement_contents 24 | ), 25 | _if_statement_contents: $ => seq( 26 | field("condition", $.expression), 27 | field("consequent", $.block_statement), 28 | field("alternative", 29 | optional(choice( 30 | seq("else", $.block_statement), 31 | seq(choice("elseif", "elseifnot"), $._if_statement_contents) 32 | ))) 33 | ), 34 | 35 | do_statement: $ => seq( 36 | "do", 37 | field("body", $.block_statement), 38 | "until", 39 | field("postcondition", $.expression) 40 | ), 41 | while_statement: $ => seq( 42 | "while", 43 | field("precondition", $.expression), 44 | field("body", $.block_statement) 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /grammar/func/bindings/rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src_dir = std::path::Path::new("src"); 3 | 4 | let mut c_config = cc::Build::new(); 5 | c_config.include(&src_dir); 6 | c_config 7 | .flag_if_supported("-Wno-unused-parameter") 8 | .flag_if_supported("-Wno-unused-but-set-variable") 9 | .flag_if_supported("-Wno-trigraphs"); 10 | let parser_path = src_dir.join("parser.c"); 11 | c_config.file(&parser_path); 12 | 13 | // If your language uses an external scanner written in C, 14 | // then include this block of code: 15 | 16 | /* 17 | let scanner_path = src_dir.join("scanner.c"); 18 | c_config.file(&scanner_path); 19 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 20 | */ 21 | 22 | c_config.compile("parser"); 23 | println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); 24 | 25 | // If your language uses an external scanner written in C++, 26 | // then include this block of code: 27 | 28 | /* 29 | let mut cpp_config = cc::Build::new(); 30 | cpp_config.cpp(true); 31 | cpp_config.include(&src_dir); 32 | cpp_config 33 | .flag_if_supported("-Wno-unused-parameter") 34 | .flag_if_supported("-Wno-unused-but-set-variable"); 35 | let scanner_path = src_dir.join("scanner.cc"); 36 | cpp_config.file(&scanner_path); 37 | cpp_config.compile("scanner"); 38 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 39 | */ 40 | } 41 | -------------------------------------------------------------------------------- /server/src/features/definitions.ts: -------------------------------------------------------------------------------- 1 | import * as lsp from 'vscode-languageserver'; 2 | import { config } from '../config'; 3 | import { DocumentStore } from '../documentStore'; 4 | import { Trees } from '../trees'; 5 | import { asParserPoint } from '../utils/position'; 6 | import { DepsIndex } from './depsIndex'; 7 | import { SymbolIndex } from './symbolIndex'; 8 | 9 | export class DefinitionProvider { 10 | 11 | constructor( 12 | private readonly _documents: DocumentStore, 13 | private readonly _trees: Trees, 14 | private readonly _symbols: SymbolIndex, 15 | private readonly _directives: DepsIndex 16 | ) { } 17 | 18 | register(connection: lsp.Connection) { 19 | connection.client.register(lsp.DefinitionRequest.type); 20 | connection.onRequest(lsp.DefinitionRequest.type, this.provideDefinitions.bind(this)); 21 | } 22 | 23 | async provideDefinitions(params: lsp.DefinitionParams): Promise { 24 | const document = await this._documents.retrieve(params.textDocument.uri); 25 | 26 | // find definition globally 27 | const tree = this._trees.getParseTree(document.document!); 28 | if (!tree) { 29 | return []; 30 | } 31 | 32 | let id = tree.rootNode.descendantForPosition(asParserPoint(params.position)); 33 | if (id.type !== 'identifier' && id.type !== 'function_name') { 34 | return []; 35 | } 36 | 37 | let files: string[] | undefined = undefined; 38 | if (config.symbolDiscovery === 'only #include') { 39 | files = this._directives.getIncludedDocuments(params.textDocument.uri); 40 | } 41 | 42 | const symbols = await this._symbols.getDefinitions(id.text, files); 43 | return symbols.map(s => s.location); 44 | } 45 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.1.7] - 2024-07-03 4 | 5 | ### Fixed 6 | - Added search keywords to manifest 7 | 8 | ## [1.1.6] - 2023-09-25 9 | ### Fixed 10 | - Updated tree-sitter to work with Node >18.1 11 | 12 | ## [1.1.5] - 2023-03-22 13 | ### Fixed 14 | - function declarations parsing 15 | - % in function names 16 | 17 | ## [1.1.4] - 2023-03-21 18 | 19 | ### Fixed 20 | - new types of #pragma 21 | - $ in the middle of function names 22 | 23 | ## [1.1.3] - 2023-03-21 24 | ### Fixed 25 | - $ in function and identifier names 26 | 27 | ## [1.1.2] - 2023-02-13 28 | ### Fixed 29 | - Windows url detection 30 | 31 | ## [1.1.1] - 2023-01-03 32 | 33 | ### Fixed 34 | - Better identifier regexp in grammar 35 | 36 | ## [1.1.0] 37 | - Show string literal numeric values 38 | - Markup fixes 39 | 40 | ## [1.0.1] 41 | - Hot-fix language server crash 42 | 43 | ## [1.0.0] 44 | - New FunC syntax: constants, strings and compiler directives 45 | - Simple includes analysis 46 | - Refactoring local variables 47 | - Variable type inference & type suggestions in completion 48 | - Code formatter 49 | - Minor grammar & markdown fixes 50 | 51 | ## [0.2.2] 52 | Fixed autocomplete with function arguments, minor highlighting bugs. 53 | 54 | ## [0.2.1] 55 | Added snippets for recv_external, recv_internal, supported_interfaces. 56 | 57 | ## [0.2.0] 58 | - Definitions 59 | - Fix Windows error related with paths 60 | - Fix completion after "." and "~" 61 | 62 | ## [0.1.0] 63 | - Symbols search 64 | - Completion 65 | - Syntax validation 66 | 67 | ## [0.0.3] 68 | 69 | - Fix elseif & else keywords 70 | 71 | ## [0.0.2] 72 | 73 | - Fix type syntax highlighting 74 | 75 | ## [0.0.1] 76 | 77 | - Initial Release 78 | -------------------------------------------------------------------------------- /grammar/func/queries/highlights.scm: -------------------------------------------------------------------------------- 1 | (impure) @keyword 2 | "inline" @keyword 3 | "inline_ref" @keyword 4 | "method_id" @keyword 5 | "asm" @keyword 6 | "global" @keyword 7 | "forall" @keyword 8 | "return" @keyword 9 | "repeat" @keyword 10 | "do" @keyword 11 | "until" @keyword 12 | "while" @keyword 13 | "if" @keyword 14 | "ifnot" @keyword 15 | "else" @keyword 16 | "elseif" @keyword 17 | "elseifnot" @keyword 18 | 19 | "=" @operator 20 | "+=" @operator 21 | "-=" @operator 22 | "*=" @operator 23 | "/=" @operator 24 | "~/=" @operator 25 | "^/=" @operator 26 | "%=" @operator 27 | "~%=" @operator 28 | "^%=" @operator 29 | "<<=" @operator 30 | ">>=" @operator 31 | "~>>=" @operator 32 | "^>>=" @operator 33 | "&=" @operator 34 | "|=" @operator 35 | "^=" @operator 36 | 37 | "==" @operator 38 | "<" @operator 39 | ">" @operator 40 | "<=" @operator 41 | ">=" @operator 42 | "!=" @operator 43 | "<=>" @operator 44 | "<<" @operator 45 | ">>" @operator 46 | "~>>" @operator 47 | "^>>" @operator 48 | "-" @operator 49 | "+" @operator 50 | "|" @operator 51 | "^" @operator 52 | "*" @operator 53 | "/" @operator 54 | "%" @operator 55 | "~/" @operator 56 | "^/" @operator 57 | "~%" @operator 58 | "^%" @operator 59 | "/%" @operator 60 | "&" @operator 61 | "~" @operator 62 | "." @operator 63 | 64 | "->" @operator 65 | 66 | 67 | (string_literal) @string 68 | (asm_instruction) @string 69 | (number_literal) @number 70 | 71 | (function_definition 72 | name: (function_name) @function) 73 | (function_application 74 | function: (identifier) @function) 75 | (method_call 76 | method_name: (identifier) @function) 77 | 78 | "type" @type 79 | (type_identifier) @type 80 | (primitive_type) @type 81 | (var_type) @type 82 | 83 | (identifier) @variable 84 | 85 | (comment) @comment 86 | -------------------------------------------------------------------------------- /server/src/features/formatting/index.ts: -------------------------------------------------------------------------------- 1 | import * as lsp from 'vscode-languageserver'; 2 | import { DocumentStore } from '../../documentStore'; 3 | import { Trees } from '../../trees'; 4 | import { formatNode } from './formatters'; 5 | 6 | export class FormattingProvider { 7 | constructor(private readonly _documents: DocumentStore, private readonly _trees: Trees) { } 8 | 9 | register(connection: lsp.Connection) { 10 | connection.client.register(lsp.DocumentFormattingRequest.type); 11 | connection.onRequest(lsp.DocumentFormattingRequest.type, this.provideDocumentFormattingEdits.bind(this)); 12 | } 13 | 14 | async provideDocumentFormattingEdits(params: lsp.DocumentFormattingParams): Promise { 15 | const tree = (await this._trees.getParseTree(params.textDocument.uri))!; 16 | const document = await this._documents.retrieve(params.textDocument.uri); 17 | let edits: lsp.TextEdit[] = []; 18 | console.log('Formatting document'); 19 | 20 | let indent = 0; 21 | let cursor = tree.rootNode.walk(); 22 | let hasAny = cursor.gotoFirstChild(); 23 | while (hasAny) { 24 | let node = cursor.currentNode(); 25 | 26 | if (node.type === '}' || node.type === ')' || node.type === ']') indent--; 27 | 28 | edits.push(...formatNode(node, document.document!, indent, params.options)); 29 | 30 | if (node.type === '{' || node.type === '(' || node.type === '[') indent++; 31 | 32 | // walk 33 | if (cursor.gotoFirstChild()) { 34 | continue; 35 | } 36 | while (!cursor.gotoNextSibling()) { 37 | hasAny = cursor.gotoParent(); 38 | if (!hasAny) { 39 | break; 40 | } 41 | } 42 | } 43 | 44 | // TODO: process final new line and trailing whitespaces 45 | 46 | return edits; 47 | } 48 | } -------------------------------------------------------------------------------- /server/src/features/codeLens.ts: -------------------------------------------------------------------------------- 1 | import * as lsp from 'vscode-languageserver'; 2 | import { DocumentStore } from '../documentStore'; 3 | import { Trees } from '../trees'; 4 | import { asLspRange } from '../utils/position'; 5 | import { createHash } from 'crypto'; 6 | import { crc32 } from '../utils/crc32'; 7 | 8 | export class CodeLensProvider { 9 | 10 | constructor( 11 | private readonly _documents: DocumentStore, 12 | private readonly _trees: Trees, 13 | ) { } 14 | 15 | register(connection: lsp.Connection) { 16 | connection.client.register(lsp.CodeLensRequest.type); 17 | connection.onRequest(lsp.CodeLensRequest.type, this.provideCodeLens.bind(this)); 18 | } 19 | 20 | async provideCodeLens(params: lsp.CodeLensParams): Promise { 21 | const document = await this._documents.retrieve(params.textDocument.uri); 22 | 23 | // find definition globally 24 | const tree = this._trees.getParseTree(document.document!); 25 | if (!tree) { 26 | return []; 27 | } 28 | 29 | let numberLiterals = tree.rootNode.descendantsOfType('number_string_literal'); 30 | return numberLiterals.map(a => { 31 | let numberTag = a.text[a.text.length - 1]; 32 | let text = a.text.slice(1, a.text.length - 2); 33 | 34 | let result: string; 35 | if (numberTag === 'H') { 36 | result = createHash('sha256').update(text).digest('hex'); 37 | } else if (numberTag === 'h') { 38 | result = createHash('sha256') 39 | .update(text).digest() 40 | .slice(0, 8).toString('hex');; 41 | } else if (numberTag === 'u') { 42 | result = Buffer.from(text).toString('hex'); 43 | } else if (numberTag === 'c') { 44 | result = crc32(text).toString(16); 45 | } else { 46 | result = text; 47 | } 48 | 49 | return { 50 | range: asLspRange(a), 51 | command: { 52 | title: `Copy ${result} to clipboard`, 53 | command: "func.copyToClipboard", 54 | arguments: [result] 55 | }, 56 | } 57 | }); 58 | } 59 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const webpack = require('webpack'); 7 | const CopyPlugin = require("copy-webpack-plugin"); 8 | 9 | 10 | const distDir = path.resolve(__dirname, 'dist'); 11 | 12 | /**@type {import('webpack').Configuration}*/ 13 | const config = { 14 | mode: 'production', 15 | 16 | target: 'node', // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target 17 | 18 | entry: { 19 | server: './server/src/server.ts', 20 | client: './client/src/extension.ts', 21 | }, // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 22 | output: { 23 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 24 | path: distDir, 25 | filename: '[name].js', 26 | libraryTarget: 'commonjs2', 27 | devtoolModuleFilenameTemplate: '../[resource-path]' 28 | }, 29 | devtool: 'source-map', 30 | externals: { 31 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 32 | }, 33 | 34 | resolve: { 35 | extensions: ['.ts', '.js'], 36 | alias: { 37 | // provides alternate implementation for node module and source files 38 | }, 39 | fallback: {} 40 | }, 41 | module: { 42 | rules: [ 43 | { 44 | test: /\.ts$/, 45 | exclude: /node_modules/, 46 | use: [ 47 | { 48 | loader: 'ts-loader' 49 | } 50 | ] 51 | } 52 | ] 53 | }, 54 | plugins: [ 55 | new CopyPlugin({ 56 | patterns: [ 57 | { from: "./node_modules/web-tree-sitter/tree-sitter.wasm", to: distDir }, 58 | { from: "./server/tree-sitter-func.wasm", to: distDir }, 59 | ], 60 | }), 61 | ], 62 | }; 63 | module.exports = config; -------------------------------------------------------------------------------- /grammar/func/bindings/rust/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides func language support for the [tree-sitter][] parsing library. 2 | //! 3 | //! Typically, you will use the [language][language func] function to add this language to a 4 | //! tree-sitter [Parser][], and then use the parser to parse some code: 5 | //! 6 | //! ``` 7 | //! let code = ""; 8 | //! let mut parser = tree_sitter::Parser::new(); 9 | //! parser.set_language(tree_sitter_func::language()).expect("Error loading func grammar"); 10 | //! let tree = parser.parse(code, None).unwrap(); 11 | //! ``` 12 | //! 13 | //! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html 14 | //! [language func]: fn.language.html 15 | //! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html 16 | //! [tree-sitter]: https://tree-sitter.github.io/ 17 | 18 | use tree_sitter::Language; 19 | 20 | extern "C" { 21 | fn tree_sitter_func() -> Language; 22 | } 23 | 24 | /// Get the tree-sitter [Language][] for this grammar. 25 | /// 26 | /// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html 27 | pub fn language() -> Language { 28 | unsafe { tree_sitter_func() } 29 | } 30 | 31 | /// The content of the [`node-types.json`][] file for this grammar. 32 | /// 33 | /// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types 34 | pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json"); 35 | 36 | // Uncomment these to include any queries that this grammar contains 37 | 38 | // pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm"); 39 | // pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm"); 40 | // pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm"); 41 | // pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm"); 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | #[test] 46 | fn test_can_load_grammar() { 47 | let mut parser = tree_sitter::Parser::new(); 48 | parser 49 | .set_language(super::language()) 50 | .expect("Error loading func language"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /server/src/features/depsIndex.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument } from 'vscode-languageserver-textdocument'; 2 | import * as Parser from 'web-tree-sitter'; 3 | import { DocumentStore } from '../documentStore'; 4 | import { queryDirectives } from '../queries/directives'; 5 | import { Trees } from '../trees'; 6 | import { Utils, URI } from 'vscode-uri'; 7 | 8 | function resolvePathSegment(documentUri: string, path: string) { 9 | let result = documentUri.split('/').slice(0, -1).join('/').substring('file://'.length); 10 | 11 | return Utils.joinPath(URI.parse(result), '/', path).toString(); 12 | } 13 | 14 | export class DepsIndex { 15 | private _cache = new Map(); 16 | 17 | constructor(private readonly _trees: Trees, private readonly _documents: DocumentStore) {} 18 | 19 | async update(document: TextDocument, tree: Parser.Tree) { 20 | let query = queryDirectives(tree.rootNode); 21 | 22 | let notFound: { node: Parser.SyntaxNode, path: string }[] = []; 23 | let includes: string[] = []; 24 | 25 | for (let include of query.includes) { 26 | let path = resolvePathSegment(document.uri, include.path.slice(1).slice(0, -1)); 27 | let found = await this._documents.retrieve(path); 28 | if (!found.exists) { 29 | notFound.push({ 30 | node: include.node, 31 | path, 32 | }); 33 | } else { 34 | includes.push(path); 35 | } 36 | } 37 | 38 | this._cache.set(document.uri, { includes, notFound }); 39 | } 40 | 41 | getIncludedDocuments(documentUri: string): string[] { 42 | let visited = new Set(); 43 | let queue: string[] = [documentUri]; 44 | while (queue.length > 0) { 45 | let current = queue.pop()!; 46 | if (visited.has(current)) { 47 | continue; 48 | } 49 | visited.add(current); 50 | 51 | let cache = this._cache.get(current); 52 | if (cache) { 53 | queue.push(...cache.includes); 54 | } 55 | } 56 | 57 | return [...visited]; 58 | } 59 | 60 | getNotFound(documentUri: string): { node: Parser.SyntaxNode, path: string }[] { 61 | let root = this._cache.get(documentUri); 62 | return root?.notFound || []; 63 | } 64 | 65 | dispose(): void { 66 | } 67 | }; -------------------------------------------------------------------------------- /grammar/func/grammar/functions.js: -------------------------------------------------------------------------------- 1 | const { commaSep, commaSep1, commaSep2 } = require('./utils.js') 2 | 3 | module.exports = { 4 | function_definition: $ => seq( 5 | field("type_variables", optional($.type_variables_list)), 6 | field("return_type", $._type), 7 | field("name", $.function_name), 8 | choice( 9 | seq( 10 | field("arguments", $.parameter_list), 11 | field("specifiers", optional($.specifiers_list)), 12 | choice( 13 | field("body", $.block_statement), 14 | field("asm_body", $.asm_function_body) 15 | ) 16 | ), 17 | seq( 18 | field("arguments", $.parameter_list_relaxed), 19 | field("specifiers", optional($.specifiers_list)), 20 | ';', 21 | ) 22 | ) 23 | ), 24 | 25 | function_name: $ => /(`.*`)|((\.|~)?(([$%a-zA-Z_](\w|['?:$%])+)|([a-zA-Z%$])))/, 26 | 27 | impure: $ => "impure", 28 | inline: $ => choice("inline", "inline_ref"), 29 | method_id: $ => seq("method_id", optional( 30 | seq('(', choice($.number_literal, $.string_literal), ')') 31 | )), 32 | 33 | specifiers_list: $ => choice( 34 | seq($.impure, optional($.inline), optional($.method_id)), 35 | seq($.inline, optional($.method_id)), 36 | $.method_id 37 | ), 38 | 39 | type_variables_list: $ => seq( 40 | "forall", 41 | commaSep(seq(optional("type"), $.type_identifier)), 42 | "->" 43 | ), 44 | 45 | parameter_list: $ => seq( 46 | '(', 47 | commaSep($.parameter_declaration), 48 | ')' 49 | ), 50 | 51 | parameter_list_relaxed: $ => seq( 52 | '(', 53 | commaSep( 54 | choice( 55 | $.parameter_declaration, 56 | field('name', $.identifier), 57 | $.underscore 58 | ) 59 | ), 60 | ')' 61 | ), 62 | 63 | parameter_declaration: $ => seq( 64 | field('type', $._type), 65 | optional( 66 | choice( 67 | field('name', $.identifier), 68 | $.underscore 69 | ) 70 | ) 71 | ), 72 | 73 | asm_function_body: $ => seq( 74 | $.asm_specifier, 75 | repeat1($.asm_instruction), 76 | ';' 77 | ), 78 | 79 | asm_specifier: $ => seq( 80 | 'asm', 81 | optional(seq( 82 | '(', 83 | repeat($.identifier), 84 | optional(seq( 85 | '->', 86 | repeat($.number_literal) 87 | )), 88 | ')' 89 | )) 90 | ), 91 | asm_instruction: $ => alias($.string_literal, $.asm_instruction), 92 | } 93 | -------------------------------------------------------------------------------- /server/src/queries/locals.ts: -------------------------------------------------------------------------------- 1 | import * as Parser from 'web-tree-sitter'; 2 | import { FuncType, inferVariableTypeFromDeclaration } from '../features/typeInference'; 3 | 4 | export function findLocals(rootNode: Parser.SyntaxNode, cursorPosition: Parser.Point) { 5 | let descendant: Parser.SyntaxNode | null = rootNode.descendantForPosition(cursorPosition); 6 | 7 | let result: { 8 | node: Parser.SyntaxNode, 9 | declaration: Parser.SyntaxNode, 10 | kind: 'variable', 11 | type: FuncType, 12 | text: string 13 | }[] = []; 14 | 15 | // navigate through parents and find their variables declared higher than cursor 16 | while (descendant) { 17 | while (descendant && descendant.type !== 'block_statement') { 18 | descendant = descendant.parent; 19 | } 20 | if (!descendant) { 21 | continue; 22 | } 23 | for (let child of descendant.children) { 24 | if (child.type === 'statement' && child.children[0].type === 'expression_statement') { 25 | child = child.children[0]; 26 | 27 | let variableDeclarations = child.descendantsOfType('variable_declaration', undefined, cursorPosition); 28 | for (let varDec of variableDeclarations) { 29 | let identifiers = varDec.descendantsOfType('identifier', undefined, cursorPosition); 30 | result.push(...identifiers.map(a => ({ 31 | node: a, 32 | declaration: varDec, 33 | kind: 'variable' as 'variable', // Typescript wtf??? 34 | text: a.text, 35 | type: inferVariableTypeFromDeclaration(varDec) 36 | }))) 37 | } 38 | } 39 | } 40 | 41 | descendant = descendant.parent; 42 | if (descendant && descendant.type === 'function_definition') { 43 | let parameters = descendant.childForFieldName('arguments')?.descendantsOfType('parameter_declaration') || []; 44 | for (let param of parameters) { 45 | let node = param.childForFieldName('name'); 46 | if (!node) continue; 47 | 48 | result.push({ 49 | node: node, 50 | declaration: param, 51 | kind: 'variable' as 'variable', // Typescript wtf??? 52 | text: node.text, 53 | type: inferVariableTypeFromDeclaration(param) 54 | }); 55 | } 56 | } 57 | } 58 | return result; 59 | } -------------------------------------------------------------------------------- /syntaxes/.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "FunC", 4 | "foldingStartMarker": "\\{\\s*$", 5 | "foldingStopMarker": "^\\s*\\}", 6 | "patterns": [ 7 | { 8 | "include": "#keywords" 9 | }, 10 | { 11 | "include": "#strings" 12 | }, 13 | { 14 | "include": "#directives" 15 | }, 16 | { 17 | "include": "#numeric" 18 | }, 19 | { 20 | "include": "#comments" 21 | }, 22 | { 23 | "include": "#storage" 24 | }, 25 | { 26 | "include": "#functions" 27 | }, 28 | { 29 | "include": "#variables" 30 | } 31 | ], 32 | "repository": { 33 | "keywords": { 34 | "patterns": [{ 35 | "name": "keyword.control.", 36 | "match": "\\b(if|ifnot|else|elseif|elseifnot|while|do|until|repeat|return|impure|method_id|forall|asm|inline|inline_ref)\\b" 37 | }, { 38 | "name": "keyword.operator", 39 | "match": "(?<=\\s)(<=>|>=|<=|!=|==|\\^>>|\\~>>|>>|<<|\\/%|\\^%|\\~%|\\^\\/|\\~\\/|\\+=|-=|\\*=|\\/=|~\\/=|\\^\\/=|%=|\\^%=|<<=|>>=|~>>=|\\^>>=|&=|\\|=|\\^=|\\^|=|~|\\/|%|-|\\*|\\+|>|<|&|\\||:|\\?)(?=\\s)" 40 | }, { 41 | "name": "keyword.other", 42 | "match": "\\b(false|true)\\b" 43 | }] 44 | }, 45 | "directives": { 46 | "name": "storage.modifier.import", 47 | "begin": "#include|#pragma", 48 | "end": ";", 49 | "patterns": [{ 50 | "begin": "\"", 51 | "end": "\"", 52 | "name": "string.quoted.double" 53 | }, { 54 | "match": "(>=|<=|=|>|<|\\^)?([0-9]+)(.[0-9]+)?(.[0-9]+)?", 55 | "name": "constant.numeric" 56 | }] 57 | }, 58 | "strings": { 59 | "name": "string.quoted.double.", 60 | "begin": "\"", 61 | "end": "\"(H|h|c|u|s|a)?" 62 | }, 63 | "numeric": { 64 | "name": "constant.numeric", 65 | "match": "(-?([\\d]+|0x[\\da-fA-F]+))\\b" 66 | }, 67 | "comments": { 68 | "patterns": [ 69 | { 70 | "name": "comment.line", 71 | "match": ";;(.*)" 72 | }, 73 | { 74 | "name": "comment.block", 75 | "begin": "{-", 76 | "end": "-}" 77 | } 78 | ] 79 | }, 80 | "storage": { 81 | "patterns": [{ 82 | "name": "storage.type", 83 | "match": "\\b(var|int|slice|tuple|cell|builder|cont|_)(?=[\\s\\),\\[\\]])" 84 | }, { 85 | "name": "storage.modifier", 86 | "match": "\\b(global|const)\\s" 87 | }] 88 | }, 89 | "variables": { 90 | "patterns": [ 91 | { 92 | "match": "(?!\")(`([^`]+)`|((?=_)_|(?={){|(?=})}|(?![_`{}]))([^;,\\[\\]\\(\\)\\s~.]+))", 93 | "name": "variable.name" 94 | } 95 | ] 96 | }, 97 | "functions": { 98 | "patterns": [ 99 | { 100 | "match": "(?!\")(`([^`]+)`|(\\.|~)?((?=_)_|(?={){|(?=})}|(?![_`{}]))([^;,\\[\\]\\(\\)\\s~.]+))(?=[\\(])", 101 | "name": "entity.name.function" 102 | } 103 | ] 104 | } 105 | }, 106 | "scopeName": "source.func" 107 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FunC Language Support for VS Code 2 | 3 | > [!WARNING] 4 | > This extension has been deprecated in favor of the extension 5 | > [`TON`](https://marketplace.visualstudio.com/items?itemName=ton-core.vscode-ton), please 6 | > see [details here](https://github.com/ton-blockchain/ton-language-server/issues/167). 7 | > 8 | > You can learn more about the [`TON`](https://marketplace.visualstudio.com/items?itemName=ton-core.vscode-ton) 9 | > extension on [`GitHub`](https://github.com/ton-blockchain/ton-language-server) or 10 | > the [`VS Code Marketplace`](https://marketplace.visualstudio.com/items?itemName=ton-core.vscode-ton). 11 | 12 | This extension enables support for FunC in VS Code. 13 | 14 | ## Features 15 | ### Syntax highlighting 16 | 17 | 18 | 19 | ### Symbols search 20 | Press *Cmd+Shift+O* to locate through symbols in file. 21 | 22 | 23 | 24 | ### Completion 25 | Get contextual completion hints while typing. Extension shows functions defined in workspace, global and local variables. 26 | 27 | 28 | 29 | ### Definitions 30 | View definitions for function or global variable using *Cmd+Click*. 31 | 32 | ### Formatting 33 | Format code using *Cmd+Option+F* 34 | 35 | ## Building & running 36 | 37 | 1. install deps using `yarn install` 38 | 2. run `yarn watch` in terminal 39 | 3. use debug menu in VS Code to test extension 40 | 41 | 42 | ## What to improve? 43 | - [ ] Add project configuration file parsing (discussion of standard is [here](https://github.com/ton-blockchain/TIPs/issues/83)) 44 | - [ ] Work with includes like compiler, use entrypoints from project configuration 45 | - [ ] Add compiler built-ins to known definitions 46 | - [ ] Highlight unknown identifiers as errors 47 | - [ ] Uncover var and hole types like compiler does 48 | 49 | ## Release Notes 50 | 51 | #### **1.1.0** 52 | Added CodeLens for numeric string literals. 53 | 54 | #### **1.0.1** 55 | Hotfix language server crash. 56 | 57 | #### **1.0.0** 58 | Large release supported by [ton-society](https://github.com/ton-society/ton-footsteps/issues/18) 🚀 59 | Added new FunC syntax, formatter and improved completion. Find more at [changelog](./CHANGELOG.md). 60 | 61 | #### **0.2.2** 62 | Fixed autocomplete with function arguments, minor highlighting bugs. 63 | 64 | #### **0.2.1** 65 | Added snippets for recv_external, recv_internal, supported_interfaces. 66 | 67 | #### **0.2.0** 68 | Fixed bugs related with paths in Windows, fixed completion after "." and "~", added definitions. 69 | 70 | #### **0.1.0** 71 | Added symbols search & completion. 72 | 73 | #### **0.0.3** 74 | Some minor improvements. 75 | 76 | #### **0.0.2** 77 | Small highlighting fixes. 78 | 79 | #### **0.0.1** 80 | Draft release, highlighting & basic code snippets. 81 | -------------------------------------------------------------------------------- /server/src/documentStore.ts: -------------------------------------------------------------------------------- 1 | 2 | import { TextDecoder } from 'util'; 3 | import * as lsp from 'vscode-languageserver'; 4 | import { TextDocuments } from 'vscode-languageserver'; 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import { LRUMap } from './utils/lruMap'; 7 | 8 | export interface TextDocumentChange2 { 9 | document: TextDocument, 10 | changes: { 11 | range: lsp.Range; 12 | rangeOffset: number; 13 | rangeLength: number; 14 | text: string; 15 | }[] 16 | } 17 | 18 | type DocumentEntry = { exists: true, document: TextDocument } | { exists: false, document: undefined }; 19 | 20 | export class DocumentStore extends TextDocuments { 21 | 22 | private readonly _onDidChangeContent2 = new lsp.Emitter(); 23 | readonly onDidChangeContent2 = this._onDidChangeContent2.event; 24 | 25 | private readonly _decoder = new TextDecoder(); 26 | private readonly _fileDocuments: LRUMap>; 27 | 28 | constructor(private readonly _connection: lsp.Connection) { 29 | super({ 30 | create: TextDocument.create, 31 | update: (doc, changes, version) => { 32 | let result: TextDocument; 33 | let incremental = true; 34 | let event: TextDocumentChange2 = { document: doc, changes: [] }; 35 | 36 | for (const change of changes) { 37 | if (!lsp.TextDocumentContentChangeEvent.isIncremental(change)) { 38 | incremental = false; 39 | break; 40 | } 41 | const rangeOffset = doc.offsetAt(change.range.start); 42 | event.changes.push({ 43 | text: change.text, 44 | range: change.range, 45 | rangeOffset, 46 | rangeLength: change.rangeLength ?? doc.offsetAt(change.range.end) - rangeOffset, 47 | }); 48 | } 49 | result = TextDocument.update(doc, changes, version); 50 | if (incremental) { 51 | this._onDidChangeContent2.fire(event); 52 | } 53 | return result; 54 | } 55 | }); 56 | 57 | this._fileDocuments = new LRUMap>({ 58 | size: 200, 59 | dispose: _entries => { } 60 | }); 61 | 62 | super.listen(_connection); 63 | 64 | _connection.onNotification('file-cache/remove', uri => this._fileDocuments.delete(uri)); 65 | } 66 | 67 | async retrieve(uri: string): Promise { 68 | let result = this.get(uri); 69 | if (result) { 70 | return { exists: true, document: result }; 71 | } 72 | let promise = this._fileDocuments.get(uri); 73 | if (!promise) { 74 | promise = this._requestDocument(uri); 75 | this._fileDocuments.set(uri, promise); 76 | } 77 | return promise; 78 | } 79 | 80 | private async _requestDocument(uri: string): Promise { 81 | const reply = await this._connection.sendRequest<{ type: 'Buffer', data: any } | { type: 'not-found' }>('file/read', uri); 82 | if (reply.type === 'not-found') { 83 | return { exists: false, document: undefined }; 84 | } 85 | let decoded = this._decoder.decode(new Uint8Array(reply.data)); 86 | return { exists: true, document: TextDocument.create(uri, 'func', 1, decoded) }; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /server/src/server.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InitializeParams, 3 | TextDocumentSyncKind, 4 | Connection 5 | } from 'vscode-languageserver/node'; 6 | import { connection } from './connection'; 7 | import { DepsIndex } from './features/depsIndex'; 8 | import { DocumentStore } from './documentStore'; 9 | import { CompletionItemProvider } from './features/completion'; 10 | import { DefinitionProvider } from './features/definitions'; 11 | import { DiagnosticsProvider } from './features/diagnostics'; 12 | import { DocumentSymbols } from './features/documentSymbols'; 13 | import { FormattingProvider } from './features/formatting'; 14 | import { SymbolIndex } from './features/symbolIndex'; 15 | import { initParser } from './parser'; 16 | import { Trees } from './trees'; 17 | import { RenameProvider } from './features/rename'; 18 | import { mutateConfig } from './config'; 19 | import { CodeLensProvider } from './features/codeLens'; 20 | 21 | 22 | const features: { register(connection: Connection): any }[] = []; 23 | 24 | connection.onInitialize(async (params: InitializeParams) => { 25 | await initParser(params.initializationOptions.treeSitterWasmUri, params.initializationOptions.langUri); 26 | 27 | const documents = new DocumentStore(connection); 28 | const trees = new Trees(documents); 29 | 30 | const symbolIndex = new SymbolIndex(trees, documents); 31 | const depsIndex = new DepsIndex(trees, documents); 32 | 33 | const diagnosticsProvider = new DiagnosticsProvider(depsIndex, symbolIndex); 34 | 35 | features.push(diagnosticsProvider); 36 | features.push(new DocumentSymbols(documents, trees)); 37 | features.push(new CompletionItemProvider(documents, trees, symbolIndex, depsIndex)); 38 | features.push(new DefinitionProvider(documents, trees, symbolIndex, depsIndex)); 39 | features.push(new FormattingProvider(documents, trees)); 40 | features.push(new RenameProvider(documents, trees, symbolIndex)); 41 | features.push(new CodeLensProvider(documents, trees)); 42 | 43 | // manage configuration 44 | connection.onNotification('configuration/change', (config) => { 45 | mutateConfig(config); 46 | }); 47 | 48 | // manage symbol index. add/remove files as they are disovered and edited 49 | documents.all().forEach(doc => symbolIndex.addFile(doc.uri)); 50 | documents.onDidOpen(event => symbolIndex.addFile(event.document.uri)); 51 | documents.onDidChangeContent(event => symbolIndex.addFile(event.document.uri)); 52 | connection.onNotification('queue/remove', uri => symbolIndex.removeFile(uri)); 53 | connection.onNotification('queue/add', uri => symbolIndex.addFile(uri)); 54 | connection.onRequest('queue/init', uris => { 55 | return symbolIndex.initFiles(uris); 56 | }); 57 | 58 | // on parse done 59 | trees.onParseDone(async (event) => { 60 | await depsIndex.update(event.document, event.tree); 61 | diagnosticsProvider.provideDiagnostics(event.document, event.tree); 62 | }) 63 | 64 | console.log('FunC language server is READY'); 65 | 66 | return { 67 | capabilities: { 68 | textDocumentSync: TextDocumentSyncKind.Incremental 69 | } 70 | }; 71 | }); 72 | 73 | connection.onInitialized(() => { 74 | for (let feature of features) { 75 | feature.register(connection); 76 | } 77 | }); 78 | 79 | // Listen on the connection 80 | connection.listen(); -------------------------------------------------------------------------------- /server/src/features/rename.ts: -------------------------------------------------------------------------------- 1 | import { DocumentStore } from '../documentStore'; 2 | import { Trees } from '../trees'; 3 | import * as lsp from 'vscode-languageserver'; 4 | import { SymbolIndex } from './symbolIndex'; 5 | import { asLspRange, asParserPoint } from '../utils/position'; 6 | import { findLocals } from '../queries/locals'; 7 | import * as Parser from 'web-tree-sitter'; 8 | 9 | function findParentBlock(node: Parser.SyntaxNode) { 10 | let parent = node.parent; 11 | while (parent && parent.type !== 'block_statement') { 12 | parent = parent.parent; 13 | } 14 | return parent; 15 | } 16 | 17 | function compare(point1: Parser.Point, point2: Parser.Point) { 18 | if (point1.row < point2.row) { 19 | return -1; 20 | } 21 | if (point1.row > point2.row) { 22 | return 1; 23 | } 24 | if (point1.column < point2.column) { 25 | return -1; 26 | } 27 | if (point1.column > point2.column) { 28 | return 1; 29 | } 30 | return 0; 31 | } 32 | 33 | function findChildrenWithType(node: Parser.SyntaxNode, type: string, startPosition?: Parser.Point, endPosition?: Parser.Point) { 34 | let result: Parser.SyntaxNode[] = []; 35 | const visit = (node: Parser.SyntaxNode) => { 36 | if ( 37 | node.type === type && 38 | (!startPosition || (compare(startPosition, node.startPosition) <= 0)) && 39 | (!endPosition || (compare(endPosition, node.endPosition) >= 0)) 40 | ) { 41 | result.push(node); 42 | } 43 | for (let child of node.children) { 44 | visit(child); 45 | } 46 | } 47 | visit(node); 48 | return result; 49 | } 50 | 51 | export class RenameProvider { 52 | constructor(private readonly _documents: DocumentStore, private readonly _trees: Trees, private readonly _symbols: SymbolIndex) { } 53 | 54 | register(connection: lsp.Connection) { 55 | connection.client.register(lsp.RenameRequest.type); 56 | connection.onRequest(lsp.RenameRequest.type, this.performRename.bind(this)); 57 | } 58 | 59 | async performRename(params: lsp.RenameParams): Promise { 60 | let tree = await this._trees.getParseTree(params.textDocument.uri); 61 | let oldIdentifier = tree!.rootNode.descendantForPosition(asParserPoint(params.position)); 62 | 63 | // try to find declaration 64 | let locals = findLocals(tree!.rootNode, oldIdentifier.endPosition); 65 | let localDeclaration = locals.find(a => a.text === oldIdentifier.text); 66 | if (localDeclaration) { 67 | // rename to the end of block 68 | let parentBlock = findParentBlock(localDeclaration.node)!; 69 | let identifiers = findChildrenWithType(parentBlock, 'identifier', localDeclaration.node.startPosition, parentBlock.endPosition); 70 | let needToRename = identifiers.filter(a => a.text === oldIdentifier.text); 71 | return { 72 | changes: { 73 | [params.textDocument.uri]: needToRename.map(a => ({ 74 | range: asLspRange(a), 75 | newText: params.newName 76 | })) 77 | } 78 | } 79 | } else { 80 | return null; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /grammar/func/grammar.js: -------------------------------------------------------------------------------- 1 | const { commaSep1 } = require('./grammar/utils.js') 2 | const types = require('./grammar/types.js') 3 | const expressions = require('./grammar/expressions.js') 4 | const functions = require('./grammar/functions.js') 5 | const statements = require('./grammar/statements.js') 6 | 7 | module.exports = grammar({ 8 | name: 'func', 9 | 10 | extras: $ => [ 11 | /\s/, 12 | $.comment, 13 | ], 14 | 15 | word: $ => $.identifier, 16 | 17 | rules: { 18 | translation_unit: $ => repeat($._top_level_item), 19 | 20 | _top_level_item: $ => choice( 21 | $.function_definition, 22 | $.global_var_declarations, 23 | $.compiler_directive, 24 | $.constant_declarations, 25 | ), 26 | 27 | compiler_directive: $ => seq(choice($.include_directive, $.pragma_directive), ';'), 28 | include_directive: $ => seq('#include', repeat1(' '), field('path', $.string_literal)), 29 | 30 | version_identifier: $ => /(>=|<=|=|>|<|\^)?([0-9]+)(.[0-9]+)?(.[0-9]+)?/, 31 | pragma_directive: $ => seq( 32 | '#pragma', 33 | repeat1(' '), 34 | choice( 35 | seq( 36 | field('key', choice('version', 'not-version')), 37 | repeat1(' '), 38 | field('value', $.version_identifier) 39 | ), 40 | field('key', choice('allow-post-modification', 'compute-asm-ltr')), 41 | ), 42 | ), 43 | 44 | global_var_declarations: $ => seq( 45 | 'global', 46 | commaSep1($.global_var_declaration), 47 | ';' 48 | ), 49 | global_var_declaration: $ => seq( 50 | field('type', optional($._type)), 51 | field('name', $.identifier), 52 | ), 53 | 54 | constant_declarations: $ => seq( 55 | 'const', 56 | commaSep1($.constant_declaration), 57 | ';' 58 | ), 59 | constant_declaration: $ => seq( 60 | field('type', optional($.constant_type)), 61 | field('name', $.identifier), 62 | '=', 63 | field('value', choice($.expression)) // todo: make constant expression 64 | ), 65 | 66 | ...types, 67 | ...expressions, 68 | ...functions, 69 | ...statements, 70 | 71 | number_literal: $ => choice( 72 | token(seq( 73 | optional('-'), 74 | choice( 75 | seq('0x', /[0-9a-fA-F]+/), 76 | /[0-9]+/ 77 | ) 78 | )), 79 | $.number_string_literal 80 | ), 81 | 82 | string_literal: $ => /"[^"]*"/, 83 | number_string_literal: $ => /"[^"]*"[Hhcu]/, 84 | slice_string_literal: $ => /"[^"]*"[sa]/, 85 | 86 | // actually FunC identifiers are much more flexible 87 | identifier: $ => /`[^`]+`|[a-zA-Z_\$][^\s\+\-\*\/%,\.;\(\)\{\}\[\]=<>\|\^\~]*/, 88 | underscore: $ => '_', 89 | 90 | // multiline_comment: $ => seq('{-', repeat(choice(/./, $.multiline_comment)), '-}'), 91 | // unfortunately getting panic while generating parser with support for nested comments 92 | comment: $ => { 93 | var multiline_comment = seq('{-', /[^-]*-+([^-}][^-]*-+)*/, '}') // C-style multiline comments (without nesting) 94 | // manually support some nesting 95 | for (var i = 0; i < 5; i++) { 96 | multiline_comment = seq('{-', repeat(choice(/[^-{]/, /-[^}]/, /\{[^-]/, multiline_comment)), '-}') 97 | } 98 | return token(choice( 99 | seq(';;', /[^\n]*/), // single-line comment 100 | multiline_comment 101 | )); 102 | } 103 | }, 104 | 105 | conflicts: $ => [ 106 | [$.parameter_list_relaxed, $.type_identifier], 107 | [$.parameter_list_relaxed, $.hole_type], 108 | [$.parameter_list_relaxed, $.parameter_list] 109 | ] 110 | }); 111 | -------------------------------------------------------------------------------- /server/src/features/formatting/formatters.ts: -------------------------------------------------------------------------------- 1 | import * as Parser from 'web-tree-sitter'; 2 | import * as lsp from 'vscode-languageserver'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | import { asLspRange } from '../../utils/position'; 5 | 6 | type Formatter = (node: Parser.SyntaxNode, document: TextDocument, indent: number, options: lsp.FormattingOptions) => lsp.TextEdit[]; 7 | 8 | let formatters: { 9 | [key: string]: Formatter[] 10 | } = {} 11 | 12 | function rule(name: string, formatter: Formatter) { 13 | formatters[name] = formatters[name] || []; 14 | formatters[name].push(formatter); 15 | } 16 | 17 | const indent: Formatter = (node, document, indent, options: lsp.FormattingOptions) => { 18 | let edits: lsp.TextEdit[] = []; 19 | 20 | let alignedIndent = options.insertSpaces ? options.tabSize * indent : indent; 21 | if (node.startPosition.column === alignedIndent) { 22 | return []; 23 | } 24 | 25 | // ignore if something is same line 26 | let prevText = document.getText({ 27 | start: { 28 | line: node.startPosition.row, 29 | character: 0 30 | }, 31 | end: asLspRange(node).start, 32 | }); 33 | if (prevText.trim().length > 0) { 34 | return []; 35 | } 36 | 37 | let spaceCharacter = options.insertSpaces ? ' ' : '\t'; 38 | 39 | // check if there are correct space characters 40 | if ((options.insertSpaces && prevText.includes('\t')) || (!options.insertSpaces && prevText.includes(' '))) { 41 | edits.push( 42 | lsp.TextEdit.replace( 43 | lsp.Range.create( 44 | lsp.Position.create(node.startPosition.row, 0), 45 | lsp.Position.create(node.startPosition.row, node.startPosition.column) 46 | ), 47 | spaceCharacter.repeat(alignedIndent) 48 | ) 49 | ); 50 | return edits; 51 | } 52 | 53 | // check if the space charecters are aligned well 54 | if (node.startPosition.column > alignedIndent) { 55 | edits.push(lsp.TextEdit.del(lsp.Range.create( 56 | lsp.Position.create(node.startPosition.row, alignedIndent), 57 | lsp.Position.create(node.startPosition.row, node.startPosition.column) 58 | ))); 59 | } else { 60 | edits.push(lsp.TextEdit.insert( 61 | lsp.Position.create(node.startPosition.row, 0), 62 | spaceCharacter.repeat(alignedIndent - node.startPosition.column) 63 | )); 64 | } 65 | return edits; 66 | } 67 | 68 | const ifParentNot = (types: string[], formatter: Formatter) => (node: Parser.SyntaxNode, document: TextDocument, indent: number, options: lsp.FormattingOptions) => { 69 | if (types.includes(node.parent!.type)) { 70 | return []; 71 | } 72 | return formatter(node, document, indent, options); 73 | } 74 | 75 | rule('function_definition', indent); 76 | rule('compiler_directive', indent); 77 | rule('global_var_declarations', indent); 78 | rule('comment', indent); 79 | rule('statement', indent); 80 | rule('expression', ifParentNot(['expression_statement'], indent)); 81 | rule(')', indent); 82 | rule('(', indent); 83 | rule('{', indent); 84 | rule('}', indent); 85 | rule('[', indent); 86 | rule(']', indent); 87 | rule('&', indent); 88 | rule('parameter_declaration', indent); 89 | 90 | 91 | export function formatNode(node: Parser.SyntaxNode, document: TextDocument, indent: number, options: lsp.FormattingOptions) { 92 | let fmts = formatters[node.type]; 93 | if (!fmts) { 94 | return []; 95 | } 96 | let edits: lsp.TextEdit[] = []; 97 | for (let rule of fmts) { 98 | edits.push(...rule(node, document, indent, options)); 99 | } 100 | return edits; 101 | } -------------------------------------------------------------------------------- /grammar/func/grammar/expressions.js: -------------------------------------------------------------------------------- 1 | const {commaSep, commaSep1, commaSep2} = require('./utils.js') 2 | 3 | module.exports = { 4 | // non-terminals are mostly borrowed from original parse-func.cpp (to be changed later) 5 | // original parser assigns some flags (_IsLvalue, _IsRvalue, _IsNewVar, _IsType, ...) to every non-terminal symbol match and sometimes checks their fitness 6 | // we mostly ignore this aspect, so we actually accept wider class of expression-like strings 7 | // and ofcourse we don't do typechecking (it's not grammar task to do so) 8 | expression: $ => $._expr10, 9 | 10 | _expr10: $ => prec(10, seq( 11 | $._expr13, 12 | optional(seq( 13 | choice('=', '+=', '-=', '*=', '/=', '~/=', '^/=', '%=', '~%=', '^%=', 14 | '<<=', '>>=', '~>>=', '^>>=', '&=', '|=', '^='), 15 | $._expr10 16 | )), 17 | )), 18 | 19 | _expr13: $ => prec(13, seq( 20 | $._expr15, 21 | optional(seq( 22 | '?', 23 | $.expression, 24 | ':', 25 | $._expr13 26 | )) 27 | )), 28 | 29 | _expr15: $ => prec(15, seq( 30 | $._expr17, 31 | optional(seq( 32 | choice('==', '<', '>', '<=', '>=', '!=', '<=>'), 33 | $._expr17 34 | )) 35 | )), 36 | 37 | _expr17: $ => prec.left(17, seq( 38 | $._expr20, 39 | repeat(seq( 40 | choice('<<', '>>', '~>>', '^>>'), 41 | $._expr20 42 | )) 43 | )), 44 | 45 | _expr20: $ => prec.left(20, seq( 46 | optional('-'), 47 | $._expr30, 48 | repeat(seq( 49 | choice('-', '+', '|', '^'), 50 | $._expr30 51 | )) 52 | )), 53 | 54 | _expr30: $ => prec.left(30, seq( 55 | $._expr75, 56 | repeat(seq( 57 | choice('*', '/', '%', '~/', '^/', '~%', '^%', '/%', '&'), 58 | $._expr75 59 | )) 60 | )), 61 | 62 | _expr75: $ => prec(75, seq( 63 | optional('~'), 64 | $._expr80 65 | )), 66 | 67 | _expr80: $ => prec.left(80, seq( 68 | $._expr90, 69 | repeat($.method_call) 70 | )), 71 | method_call: $ => prec.left(80, seq( 72 | choice('.', '~'), 73 | field("method_name", $.identifier), 74 | field("arguments", $._expr100) 75 | )), 76 | 77 | _expr90: $ => prec.left(90, choice( 78 | $._expr100, 79 | $.variable_declaration, 80 | $.function_application 81 | )), 82 | function_application: $ => prec.left(90, seq( 83 | field("function", $._nontype_expr100), 84 | field("agruments", repeat1(choice( 85 | $.identifier, 86 | $.parenthesized_expression, 87 | $.tensor_expression, 88 | $.unit_literal 89 | ))) 90 | )), 91 | variable_declaration: $ => prec.left(90, seq( 92 | field("type", $.type_expression), 93 | field("variable", choice( 94 | $.identifier, 95 | $.tuple_expression, 96 | $.tensor_expression, 97 | $.parenthesized_expression 98 | )) 99 | )), 100 | 101 | type_expression: $ => prec(101, choice( 102 | $.primitive_type, 103 | $.var_type, 104 | $.parenthesized_type_expression, 105 | $.tensor_type_expression, 106 | $.tuple_type_expression 107 | )), 108 | parenthesized_type_expression: $ => prec(101, seq('(', $.type_expression, ')')), 109 | tensor_type_expression: $ => prec(101, seq('(', commaSep2($.type_expression), ')')), 110 | tuple_type_expression: $ => prec(101, seq('[', commaSep1($.type_expression), ']')), 111 | 112 | _nontype_expr100 : $ => prec(100, choice( 113 | $.parenthesized_expression, 114 | $.tensor_expression, 115 | $.tuple_expression, 116 | $.unit_literal, 117 | $.primitive_type, 118 | $.identifier, 119 | $.number_literal, 120 | $.string_literal, 121 | $.slice_string_literal, 122 | $.underscore 123 | )), 124 | 125 | _expr100: $ => prec(100, choice( 126 | $.type_expression, 127 | $._nontype_expr100 128 | )), 129 | 130 | unit_literal: $ => '()', 131 | 132 | parenthesized_expression: $ => seq('(', $.expression, ')'), 133 | tensor_expression: $ => seq('(', commaSep2($.expression), ')'), 134 | tuple_expression: $ => seq('[', commaSep($.expression), ']'), 135 | } 136 | -------------------------------------------------------------------------------- /server/src/features/typeInference.ts: -------------------------------------------------------------------------------- 1 | import { type } from 'os'; 2 | import * as Parser from 'web-tree-sitter'; 3 | 4 | type PrimitiveType = { 5 | kind: 'primitive', 6 | name: 'int' | 'cell' | 'slice' | 'builder' | 'cont' | 'tuple' 7 | } 8 | 9 | type CompositeType = { 10 | kind: 'tensor' | 'tuple', 11 | shape: FuncType[] 12 | } 13 | 14 | export type FunctionType = { 15 | kind: 'function', 16 | arguments: { 17 | name?: string, 18 | type: FuncType 19 | }[], 20 | returns: FuncType 21 | } 22 | 23 | type GenericType = { 24 | kind: 'generic', 25 | name: string 26 | } 27 | 28 | type UnitType = { kind: 'unit' } 29 | type VarType = { kind: 'var' } 30 | type HoleType = { kind: 'hole' } 31 | 32 | export type AtomicType = PrimitiveType | UnitType | VarType | HoleType | GenericType | CompositeType; 33 | 34 | export type FuncType = AtomicType | FunctionType; 35 | 36 | export function extractType(typeNode: Parser.SyntaxNode): FuncType { 37 | if (typeNode.type === 'primitive_type') { 38 | return { 39 | kind: 'primitive', 40 | name: typeNode.text as PrimitiveType['name'] 41 | }; 42 | } else if (typeNode.type === 'tensor_type' || typeNode.type === 'tuple_type') { 43 | return { 44 | kind: typeNode.type === 'tensor_type' ? 'tensor' : 'tuple', 45 | shape: typeNode.children.map(a => extractType(a)) 46 | } 47 | } else if (typeNode.type === 'var_type') { 48 | return { kind: 'var' } 49 | } else if (typeNode.type === 'hole_type') { 50 | return { kind: 'var' } 51 | } else if (typeNode.type === 'unit_type') { 52 | return { kind: 'unit' } 53 | } 54 | 55 | // fallback to var 56 | return { kind: 'var' }; 57 | } 58 | 59 | /** 60 | * Supports paremeter declaration and variable declaration 61 | */ 62 | export function inferVariableTypeFromDeclaration(node: Parser.SyntaxNode): FuncType { 63 | if (node.type === 'variable_declaration') { 64 | let variableType = node.childForFieldName('type')!.child(0); 65 | return extractType(variableType!); 66 | } else if (node.type === 'parameter_declaration' || node.type === 'global_var_declaration' || node.type === 'constant_declaration') { 67 | let type = node.childForFieldName('type'); 68 | return extractType(type!); 69 | } 70 | return { kind: 'var' }; 71 | } 72 | 73 | export function inferFunctionType(node: Parser.SyntaxNode): FuncType { 74 | if (node.type === 'function_definition') { 75 | // return_type 76 | let returnType = node.childForFieldName('return_type'); 77 | let parameters = node.descendantsOfType('parameter_declaration'); 78 | return { 79 | kind: 'function', 80 | arguments: parameters.map(param => ({ 81 | name: param.childForFieldName('name')!.text, 82 | type: extractType(param) 83 | })), 84 | returns: extractType(returnType!) 85 | } 86 | } 87 | 88 | // fallback to hole 89 | return { kind: 'hole' }; 90 | } 91 | 92 | export function stringifyType(type: FuncType): string { 93 | if (type.kind === 'primitive') { 94 | return type.name; 95 | } else if (type.kind === 'function') { 96 | let args = type.arguments.map(a => { 97 | let type = stringifyType(a.type); 98 | if (a.name) return `${type} ${a.name}`; 99 | return type; 100 | }).join(', '); 101 | return `(${args}) -> ${stringifyType(type.returns)}`; 102 | } else if (type.kind === 'var') { 103 | return 'var'; 104 | } else if (type.kind === 'unit') { 105 | return '()'; 106 | } else if (type.kind === 'hole') { 107 | return '_'; 108 | } else if (type.kind === 'generic') { 109 | return type.name; 110 | } else if (type.kind === 'tensor') { 111 | return `(${type.shape.map(a => stringifyType(a)).join(', ')})`; 112 | } else if (type.kind === 'tuple') { 113 | return `(${type.shape.map(a => stringifyType(a)).join(', ')})`; 114 | } 115 | return '_'; 116 | } -------------------------------------------------------------------------------- /server/src/features/documentSymbols.ts: -------------------------------------------------------------------------------- 1 | import * as lsp from 'vscode-languageserver'; 2 | import { TextDocument } from 'vscode-languageserver-textdocument'; 3 | import { DocumentStore } from '../documentStore'; 4 | import { Trees } from '../trees'; 5 | import { queryGlobals } from '../queries/globals'; 6 | import { asLspRange } from '../utils/position'; 7 | import * as Parser from 'web-tree-sitter'; 8 | import { FuncType, inferFunctionType, inferVariableTypeFromDeclaration, stringifyType } from './typeInference'; 9 | 10 | export class DocumentSymbols { 11 | constructor(private readonly _documents: DocumentStore, private readonly _trees: Trees) { } 12 | 13 | register(connection: lsp.Connection) { 14 | connection.client.register(lsp.DocumentSymbolRequest.type); 15 | connection.onRequest(lsp.DocumentSymbolRequest.type, this.provideDocumentSymbols.bind(this)); 16 | } 17 | 18 | async provideDocumentSymbols(params: lsp.DocumentSymbolParams): Promise { 19 | const document = await this._documents.retrieve(params.textDocument.uri); 20 | let symbols = getDocumentSymbols(document.document!, this._trees); 21 | return symbols.map(a => a.symbol); 22 | } 23 | } 24 | 25 | 26 | export type SymbolMeta = { 27 | symbol: lsp.DocumentSymbol, 28 | node: Parser.SyntaxNode, 29 | funcType: FuncType 30 | } 31 | export function getDocumentSymbols(document: TextDocument, trees: Trees): SymbolMeta[] { 32 | const tree = trees.getParseTree(document); 33 | if (!tree) { 34 | return []; 35 | } 36 | const globals = queryGlobals(tree.rootNode); 37 | 38 | const result: SymbolMeta[] = []; 39 | for (let declaration of globals) { 40 | let children: lsp.DocumentSymbol[] = [] 41 | if (declaration.type == 'function') { 42 | let body = declaration.node.parent!.childForFieldName('body'); 43 | if (body) { 44 | let declarations = body.descendantsOfType('variable_declaration') 45 | for (let node of declarations) { 46 | let identifiers = node.descendantsOfType('identifier'); 47 | children.push(...identifiers.map(localVar => { 48 | let range = asLspRange(localVar) 49 | return lsp.DocumentSymbol.create( 50 | localVar.text, 51 | '', 52 | lsp.SymbolKind.Variable, 53 | range, 54 | range, 55 | ) 56 | })) 57 | } 58 | } 59 | result.push({ 60 | node: declaration.node.parent!, 61 | symbol: lsp.DocumentSymbol.create( 62 | declaration.text, 63 | '', 64 | lsp.SymbolKind.Function, 65 | asLspRange(declaration.node.parent!), 66 | declaration.range, 67 | children 68 | ), 69 | funcType: inferFunctionType(declaration.node.parent!) 70 | }); 71 | } else if (declaration.type == 'globalVar') { 72 | result.push({ 73 | node: declaration.node.parent!, 74 | symbol: lsp.DocumentSymbol.create( 75 | declaration.text, 76 | '', 77 | lsp.SymbolKind.Variable, 78 | declaration.range, 79 | declaration.range, 80 | children, 81 | ), 82 | funcType: inferVariableTypeFromDeclaration(declaration.node.parent!) 83 | }); 84 | } else if (declaration.type == 'const') { 85 | result.push({ 86 | node: declaration.node!, 87 | symbol: lsp.DocumentSymbol.create( 88 | declaration.text, 89 | '', 90 | lsp.SymbolKind.Constant, 91 | declaration.range, 92 | declaration.range 93 | ), 94 | funcType: inferVariableTypeFromDeclaration(declaration.node!) 95 | }); 96 | } 97 | } 98 | return result; 99 | } -------------------------------------------------------------------------------- /server/src/trees.ts: -------------------------------------------------------------------------------- 1 | import { LRUMap } from './utils/lruMap'; 2 | import * as lsp from 'vscode-languageserver'; 3 | import * as Parser from 'web-tree-sitter'; 4 | import { Disposable, Position } from 'vscode-languageserver'; 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import { DocumentStore, TextDocumentChange2 } from './documentStore'; 7 | import { createParser } from './parser'; 8 | 9 | class Entry { 10 | constructor( 11 | public version: number, 12 | public tree: Parser.Tree, 13 | public edits: Parser.Edit[][] 14 | ) { } 15 | } 16 | 17 | export type ParseDone = { 18 | tree: Parser.Tree, 19 | document: TextDocument 20 | } 21 | 22 | export class Trees { 23 | 24 | private readonly _cache = new LRUMap({ 25 | size: 200, 26 | dispose(entries) { 27 | for (let [, value] of entries) { 28 | value.tree.delete(); 29 | } 30 | } 31 | }); 32 | 33 | private readonly _listener: Disposable[] = []; 34 | private readonly _parser = createParser(); 35 | private readonly _onParseDone = new lsp.Emitter(); 36 | readonly onParseDone = this._onParseDone.event; 37 | 38 | constructor(private readonly _documents: DocumentStore) { 39 | // build edits when document changes 40 | this._listener.push(_documents.onDidChangeContent2(e => { 41 | const info = this._cache.get(e.document.uri); 42 | if (info) { 43 | info.edits.push(Trees._asEdits(e)); 44 | } 45 | })); 46 | } 47 | 48 | dispose(): void { 49 | for (let item of this._cache.values()) { 50 | item.tree.delete(); 51 | } 52 | for (let item of this._listener) { 53 | item.dispose(); 54 | } 55 | } 56 | 57 | // --- tree/parse 58 | 59 | getParseTree(documentOrUri: string): Promise; 60 | getParseTree(documentOrUri: TextDocument): Parser.Tree | undefined; 61 | getParseTree(documentOrUri: TextDocument | string): Promise | Parser.Tree | undefined { 62 | if (typeof documentOrUri === 'string') { 63 | return this._documents.retrieve(documentOrUri).then(doc => { 64 | if (!doc.exists) { 65 | return undefined; 66 | } 67 | return this._parse(doc.document); 68 | }); 69 | } else { 70 | return this._parse(documentOrUri); 71 | } 72 | } 73 | 74 | private _parse(documentOrUri: TextDocument): Parser.Tree | undefined { 75 | let info = this._cache.get(documentOrUri.uri); 76 | if (info?.version === documentOrUri.version) { 77 | return info.tree; 78 | } 79 | 80 | try { 81 | const version = documentOrUri.version; 82 | const text = documentOrUri.getText(); 83 | 84 | if (!info) { 85 | // never seen before, parse fresh 86 | const tree = this._parser.parse(text); 87 | info = new Entry(version, tree, []); 88 | this._cache.set(documentOrUri.uri, info); 89 | 90 | } else { 91 | // existing entry, apply deltas and parse incremental 92 | const oldTree = info.tree; 93 | const deltas = info.edits.flat(); 94 | deltas.forEach(delta => oldTree.edit(delta)); 95 | info.edits.length = 0; 96 | 97 | info.tree = this._parser.parse(text, oldTree); 98 | info.version = version; 99 | oldTree.delete(); 100 | } 101 | 102 | this._onParseDone.fire({ 103 | document: documentOrUri, 104 | tree: info.tree 105 | }) 106 | 107 | return info.tree; 108 | 109 | } catch (e) { 110 | this._cache.delete(documentOrUri.uri); 111 | return undefined; 112 | } 113 | } 114 | 115 | private static _asEdits(event: TextDocumentChange2): Parser.Edit[] { 116 | return event.changes.map(change => ({ 117 | startPosition: this._asTsPoint(change.range.start), 118 | oldEndPosition: this._asTsPoint(change.range.end), 119 | newEndPosition: this._asTsPoint(event.document.positionAt(change.rangeOffset + change.text.length)), 120 | startIndex: change.rangeOffset, 121 | oldEndIndex: change.rangeOffset + change.rangeLength, 122 | newEndIndex: change.rangeOffset + change.text.length 123 | })); 124 | } 125 | 126 | private static _asTsPoint(position: Position): Parser.Point { 127 | const { line: row, character: column } = position; 128 | return { row, column }; 129 | } 130 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "func-vscode", 3 | "displayName": "FunC Language Support", 4 | "description": "This extension enables support for FunC in VS Code.", 5 | "keywords": [ 6 | "TON", 7 | "The Open Network", 8 | "FunC", 9 | "Smart contract" 10 | ], 11 | "icon": "logo.png", 12 | "version": "1.1.7", 13 | "engines": { 14 | "vscode": "^1.63.0" 15 | }, 16 | "activationEvents": [ 17 | "onLanguage:func" 18 | ], 19 | "categories": [ 20 | "Programming Languages", 21 | "Snippets" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/tonwhales/vscode-func" 26 | }, 27 | "author": { 28 | "name": "Danila Volkov (dvlkv)", 29 | "email": "danila@whalescorp.com", 30 | "url": "https://github.com/dvlkv" 31 | }, 32 | "publisher": "tonwhales", 33 | "scripts": { 34 | "vscode:prepublish": "yarn compile", 35 | "compile": "webpack", 36 | "grammar:compile": "yarn workspace func-language build && cp grammar/func/tree-sitter-func.wasm server/tree-sitter-func.wasm", 37 | "watch": "webpack --watch", 38 | "release": "release-it", 39 | "lint": "eslint ./client/src ./server/src --ext .ts,.tsx" 40 | }, 41 | "workspaces": [ 42 | "./grammar/func", 43 | "./server", 44 | "./client" 45 | ], 46 | "main": "./dist/client", 47 | "contributes": { 48 | "languages": [ 49 | { 50 | "id": "func", 51 | "aliases": [ 52 | "FunC", 53 | "func" 54 | ], 55 | "extensions": [ 56 | ".fc", 57 | ".func" 58 | ], 59 | "configuration": "./language-configuration.json" 60 | } 61 | ], 62 | "grammars": [ 63 | { 64 | "language": "func", 65 | "scopeName": "source.func", 66 | "path": "./syntaxes/.tmLanguage.json" 67 | } 68 | ], 69 | "snippets": [ 70 | { 71 | "language": "func", 72 | "path": "./snippets/snippets.json" 73 | } 74 | ], 75 | "configuration": { 76 | "title": "FunC", 77 | "type": "object", 78 | "properties": { 79 | "func.symbolDiscovery": { 80 | "description": "Set up how extension discovers symbols.", 81 | "type": "string", 82 | "enum": [ 83 | "everything", 84 | "only #include" 85 | ], 86 | "default": "only #include" 87 | }, 88 | "func.autocompleteAddParentheses": { 89 | "description": "Add parentheses to autocomplete function suggestions.", 90 | "type": "boolean", 91 | "default": true 92 | }, 93 | "func.experimentalDiagnostics": { 94 | "description": "Enable experimental diagnostics.", 95 | "type": "boolean", 96 | "default": false 97 | } 98 | } 99 | } 100 | }, 101 | "devDependencies": { 102 | "@release-it/keep-a-changelog": "^3.1.0", 103 | "copy-webpack-plugin": "^11.0.0", 104 | "path-browserify": "^1.0.1", 105 | "release-it": "^15.6.0", 106 | "tree-sitter-cli": "^0.20.8", 107 | "ts-loader": "^9.3.1", 108 | "typescript": "^4.7.4", 109 | "vscode": "^1.1.37", 110 | "webpack": "^5.74.0", 111 | "webpack-cli": "^4.10.0" 112 | }, 113 | "release-it": { 114 | "git": { 115 | "commitMessage": "chore: release v${version}" 116 | }, 117 | "github": { 118 | "release": true 119 | }, 120 | "npm": { 121 | "publish": false 122 | }, 123 | "plugins": { 124 | "@release-it/keep-a-changelog": { 125 | "filename": "CHANGELOG.md" 126 | } 127 | } 128 | }, 129 | "packageManager": "yarn@3.4.1" 130 | } 131 | -------------------------------------------------------------------------------- /server/src/features/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import * as lsp from 'vscode-languageserver'; 2 | import { connection } from '../connection'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | import * as Parser from 'web-tree-sitter'; 5 | import { asLspRange } from '../utils/position'; 6 | import { DepsIndex } from './depsIndex'; 7 | import { SymbolIndex } from './symbolIndex'; 8 | import { config } from '../config'; 9 | import { FuncType } from './typeInference'; 10 | import { findLocals } from '../queries/locals'; 11 | 12 | export class DiagnosticsProvider { 13 | constructor(private readonly _deps: DepsIndex, private readonly _symbols: SymbolIndex) { 14 | } 15 | 16 | register(connection: lsp.Connection) { 17 | // do nothing 18 | } 19 | 20 | private async retrieveAvailableGlobalSymbols(uri: string) { 21 | await this._symbols.update(); 22 | 23 | let deps = this._deps.getIncludedDocuments(uri); 24 | 25 | let result: { kind: lsp.SymbolKind, type: FuncType, name: string }[] = []; 26 | for (let [label, occurencies] of this._symbols.index) { 27 | for (let [doc, symbol] of occurencies.entries()) { 28 | if (symbol.definitions.size === 0) { 29 | continue; 30 | } 31 | if ( 32 | config.symbolDiscovery === 'only #include' && 33 | doc !== uri && 34 | !deps.includes(doc) 35 | ) { 36 | continue; 37 | } 38 | 39 | for (let [def, type] of symbol.definitions.entries()) { 40 | result.push({ 41 | kind: def, 42 | type, 43 | name: label, 44 | }) 45 | } 46 | } 47 | } 48 | return result; 49 | } 50 | 51 | async provideDiagnostics(document: TextDocument, tree: Parser.Tree) { 52 | let diagnostics: lsp.Diagnostic[] = [] 53 | let globalSymbols = await this.retrieveAvailableGlobalSymbols(document.uri); 54 | const visitTree = (node: Parser.SyntaxNode) => { 55 | if (node.isMissing()) { 56 | diagnostics.push({ 57 | message: 'Missing ' + node.type, 58 | range: asLspRange(node), 59 | }) 60 | } else if (node.hasError() && node.children.every(a => !a.hasError())) { 61 | diagnostics.push({ 62 | message: 'Syntax error', 63 | range: asLspRange(node), 64 | }) 65 | } 66 | 67 | if (config.experimentalDiagnostics && node.type === 'block_statement' && config.symbolDiscovery === 'only #include') { 68 | let identifiers = node.descendantsOfType('identifier'); 69 | for (let identifier of identifiers) { 70 | let locals = findLocals(tree.rootNode, identifier.endPosition); 71 | let symbol = globalSymbols.find(a => { 72 | if (a.name.startsWith('~')) { 73 | return a.name.slice(1) === identifier.text; 74 | } 75 | return a.name === identifier.text; 76 | }) || locals.find(a => a.text === identifier.text); 77 | if (!symbol) { 78 | diagnostics.push({ 79 | message: `Сannot find symbol with name '${identifier.text}'`, 80 | range: asLspRange(identifier), 81 | }) 82 | } 83 | } 84 | } 85 | 86 | for (let child of node.children) { 87 | visitTree(child); 88 | } 89 | } 90 | visitTree(tree.rootNode); 91 | 92 | let errors = this._deps.getNotFound(document.uri); 93 | for (let error of errors) { 94 | diagnostics.push({ 95 | message: 'Dependency not found: ' + error.path, 96 | range: asLspRange(error.node), 97 | }) 98 | } 99 | 100 | connection.sendDiagnostics({ 101 | diagnostics, 102 | uri: document.uri, 103 | version: document.version 104 | }) 105 | } 106 | } -------------------------------------------------------------------------------- /server/src/utils/trie.ts: -------------------------------------------------------------------------------- 1 | class Entry { 2 | constructor(readonly key: string, public value: E) { } 3 | } 4 | 5 | export interface ReadonlyTrie { 6 | size: number; 7 | get(str: string): E | undefined; 8 | query(str: string[]): IterableIterator<[string, E]>; 9 | [Symbol.iterator](): IterableIterator<[string, E]>; 10 | } 11 | 12 | export class Trie implements ReadonlyTrie { 13 | 14 | static create(): Trie { 15 | return new Trie('', undefined); 16 | } 17 | 18 | private _size: number = 0; 19 | private _depth: number = 0; 20 | private readonly _children = new Map>(); 21 | 22 | private constructor(readonly ch: string, public element: Entry | undefined) { } 23 | 24 | get size() { 25 | return this._size; 26 | } 27 | 28 | get depth() { 29 | return this._depth; 30 | } 31 | 32 | set(str: string, element: E): void { 33 | let chars = Array.from(str); 34 | let node: Trie = this; 35 | for (let pos = 0; pos < chars.length; pos++) { 36 | node._depth = Math.max(chars.length - pos, node._depth); 37 | const ch = chars[pos]; 38 | let child = node._children.get(ch); 39 | if (!child) { 40 | child = new Trie(ch, undefined); 41 | node._children.set(ch, child); 42 | } 43 | node = child; 44 | } 45 | if (!node.element) { 46 | this._size += 1; 47 | node.element = new Entry(str, element); 48 | } else { 49 | node.element.value = element; 50 | } 51 | } 52 | 53 | get(str: string): E | undefined { 54 | let chars = Array.from(str); 55 | let node: Trie = this; 56 | for (let pos = 0; pos < chars.length; pos++) { 57 | const ch = chars[pos]; 58 | let child = node._children.get(ch); 59 | if (!child) { 60 | return undefined; 61 | } 62 | node = child; 63 | } 64 | return node.element?.value; 65 | } 66 | 67 | delete(str: string): boolean { 68 | let chars = Array.from(str); 69 | let node: Trie = this; 70 | let path: [string, Trie][] = []; 71 | for (let pos = 0; pos < chars.length; pos++) { 72 | const ch = chars[pos]; 73 | let child = node._children.get(ch); 74 | if (!child) { 75 | return false; 76 | } 77 | path.push([ch, node]); 78 | node = child; 79 | } 80 | 81 | if (!node.element) { 82 | return false; 83 | } 84 | 85 | // unset element 86 | node.element = undefined; 87 | this._size -= 1; 88 | 89 | // cleanup parents and update depths 90 | while (path.length > 0) { 91 | // parent 92 | const [nodeCh, parent] = path.pop()!; 93 | if (node._children.size === 0 && !node.element) { 94 | parent._children.delete(nodeCh); 95 | } 96 | node = parent; 97 | 98 | if (node._children.size === 0) { 99 | node._depth = 0; 100 | } else { 101 | let newDepth = 0; 102 | for (let child of node._children.values()) { 103 | newDepth = Math.max(newDepth, child.depth); 104 | } 105 | node._depth = 1 + newDepth; 106 | } 107 | } 108 | 109 | return true; 110 | } 111 | 112 | *query(str: string[]): IterableIterator<[string, E]> { 113 | 114 | const bucket = new Set>(); 115 | const cache = new Map, Map>(); 116 | 117 | const _query = (node: Trie, str: string[], pos: number, skipped: number, lastCh: string) => { 118 | 119 | if (bucket.has(node)) { 120 | return; 121 | } 122 | 123 | if (skipped > 12) { 124 | return; 125 | } 126 | 127 | const map = cache.get(node); 128 | if (map?.get(pos)) { 129 | return; 130 | } 131 | 132 | if (map) { 133 | map.set(pos, true); 134 | } else { 135 | cache.set(node, new Map([[pos, true]])); 136 | } 137 | 138 | if (pos >= str.length) { 139 | // till 'node' all characters have matched 140 | bucket.add(node); 141 | return; 142 | } 143 | 144 | if (str.length - pos > node._depth) { 145 | // there is more characters left than there are nodes 146 | return; 147 | } 148 | 149 | // match & recurse 150 | for (let [ch, child] of node._children) { 151 | if (ch.toLowerCase() === str[pos].toLowerCase()) { 152 | // consume query character if 153 | _query(child, str, pos + 1, skipped, ch); 154 | } 155 | _query(child, str, pos, skipped + 1, ch); 156 | } 157 | }; 158 | 159 | _query(this, str, 0, 0, this.ch); 160 | 161 | for (let item of bucket) { 162 | yield* item; 163 | } 164 | } 165 | 166 | *[Symbol.iterator](): IterableIterator<[string, E]> { 167 | const stack: Trie[] = [this]; 168 | while (stack.length > 0) { 169 | const node = stack.shift()!; 170 | if (node.element) { 171 | yield [node.element.key, node.element.value]; 172 | } 173 | for (let child of node._children.values()) { 174 | stack.push(child); 175 | } 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /client/yarn.lock: -------------------------------------------------------------------------------- 1 | # This file is generated by running "yarn install" inside your project. 2 | # Manual changes might be lost - proceed with caution! 3 | 4 | __metadata: 5 | version: 6 6 | cacheKey: 8 7 | 8 | "@types/node@npm:^17.0.23": 9 | version: 17.0.45 10 | resolution: "@types/node@npm:17.0.45" 11 | checksum: aa04366b9103b7d6cfd6b2ef64182e0eaa7d4462c3f817618486ea0422984c51fc69fd0d436eae6c9e696ddfdbec9ccaa27a917f7c2e8c75c5d57827fe3d95e8 12 | languageName: node 13 | linkType: hard 14 | 15 | "@types/vscode@npm:^1.63.0": 16 | version: 1.70.0 17 | resolution: "@types/vscode@npm:1.70.0" 18 | checksum: fda7b48d93a8c2164abb393224572e201a995518a223c6d1fb130e66baf5878235901a078ee31f8f12f89854935ca431a9b78c335abc78672b4e25e5b9d32584 19 | languageName: node 20 | linkType: hard 21 | 22 | "balanced-match@npm:^1.0.0": 23 | version: 1.0.2 24 | resolution: "balanced-match@npm:1.0.2" 25 | checksum: 9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 26 | languageName: node 27 | linkType: hard 28 | 29 | "brace-expansion@npm:^1.1.7": 30 | version: 1.1.11 31 | resolution: "brace-expansion@npm:1.1.11" 32 | dependencies: 33 | balanced-match: ^1.0.0 34 | concat-map: 0.0.1 35 | checksum: faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 36 | languageName: node 37 | linkType: hard 38 | 39 | "client@workspace:.": 40 | version: 0.0.0-use.local 41 | resolution: "client@workspace:." 42 | dependencies: 43 | "@types/node": ^17.0.23 44 | "@types/vscode": ^1.63.0 45 | vscode-languageclient: ^8.0.2 46 | vscode-uri: ^3.0.7 47 | languageName: unknown 48 | linkType: soft 49 | 50 | "concat-map@npm:0.0.1": 51 | version: 0.0.1 52 | resolution: "concat-map@npm:0.0.1" 53 | checksum: 902a9f5d8967a3e2faf138d5cb784b9979bad2e6db5357c5b21c568df4ebe62bcb15108af1b2253744844eb964fc023fbd9afbbbb6ddd0bcc204c6fb5b7bf3af 54 | languageName: node 55 | linkType: hard 56 | 57 | "lru-cache@npm:^6.0.0": 58 | version: 6.0.0 59 | resolution: "lru-cache@npm:6.0.0" 60 | dependencies: 61 | yallist: ^4.0.0 62 | checksum: f97f499f898f23e4585742138a22f22526254fdba6d75d41a1c2526b3b6cc5747ef59c5612ba7375f42aca4f8461950e925ba08c991ead0651b4918b7c978297 63 | languageName: node 64 | linkType: hard 65 | 66 | "minimatch@npm:^3.0.4": 67 | version: 3.1.2 68 | resolution: "minimatch@npm:3.1.2" 69 | dependencies: 70 | brace-expansion: ^1.1.7 71 | checksum: c154e566406683e7bcb746e000b84d74465b3a832c45d59912b9b55cd50dee66e5c4b1e5566dba26154040e51672f9aa450a9aef0c97cfc7336b78b7afb9540a 72 | languageName: node 73 | linkType: hard 74 | 75 | "semver@npm:^7.3.5": 76 | version: 7.3.7 77 | resolution: "semver@npm:7.3.7" 78 | dependencies: 79 | lru-cache: ^6.0.0 80 | bin: 81 | semver: bin/semver.js 82 | checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 83 | languageName: node 84 | linkType: hard 85 | 86 | "vscode-jsonrpc@npm:8.0.2": 87 | version: 8.0.2 88 | resolution: "vscode-jsonrpc@npm:8.0.2" 89 | checksum: 9d055fd4c87ef1093b0eecb5370bfaf3402179b6639149b6d0f7e0bde60cf580091c7e07b0caff868f10f90331b17e7383c087217c077fdd1b5ae7bc23b72f77 90 | languageName: node 91 | linkType: hard 92 | 93 | "vscode-languageclient@npm:^8.0.2": 94 | version: 8.0.2 95 | resolution: "vscode-languageclient@npm:8.0.2" 96 | dependencies: 97 | minimatch: ^3.0.4 98 | semver: ^7.3.5 99 | vscode-languageserver-protocol: 3.17.2 100 | checksum: 3a145605a3cdb2855a2a771d6b8e75e72a1ca5aa32e091933c2f0e6f7b8fc1a45a9ea79b434001ad8af6b8cdb4ef88ff92fbb128b37988ebb21cac1b60cd44c3 101 | languageName: node 102 | linkType: hard 103 | 104 | "vscode-languageserver-protocol@npm:3.17.2": 105 | version: 3.17.2 106 | resolution: "vscode-languageserver-protocol@npm:3.17.2" 107 | dependencies: 108 | vscode-jsonrpc: 8.0.2 109 | vscode-languageserver-types: 3.17.2 110 | checksum: f4a05d3a631af315a32a3700953c2117fa4e5c44bc03764154c6605da9cbbcb50a1b01b46f11b2f6948916d01b4948bebf1a84c135fc73b27fa839c58d0847ab 111 | languageName: node 112 | linkType: hard 113 | 114 | "vscode-languageserver-types@npm:3.17.2": 115 | version: 3.17.2 116 | resolution: "vscode-languageserver-types@npm:3.17.2" 117 | checksum: ef2d862d22f622b64de0f428773d50a5928ec6cdd485960a7564ebe4fd4a3c8bcd956f29eb15bc45a0f353846e62f39f6c764d2ab85ce774b8724411ba84342f 118 | languageName: node 119 | linkType: hard 120 | 121 | "vscode-uri@npm:^3.0.7": 122 | version: 3.0.7 123 | resolution: "vscode-uri@npm:3.0.7" 124 | checksum: c899a0334f9f6ba53021328e083f6307978c09b94407d7e5fe86fcd8fcb8f1da0cb344123a335e55769055007a46d51aff83f9ee1dfc0296ee54b78f34ef0e4f 125 | languageName: node 126 | linkType: hard 127 | 128 | "yallist@npm:^4.0.0": 129 | version: 4.0.0 130 | resolution: "yallist@npm:4.0.0" 131 | checksum: 343617202af32df2a15a3be36a5a8c0c8545208f3d3dfbc6bb7c3e3b7e8c6f8e7485432e4f3b88da3031a6e20afa7c711eded32ddfb122896ac5d914e75848d5 132 | languageName: node 133 | linkType: hard 134 | -------------------------------------------------------------------------------- /server/src/features/completion.ts: -------------------------------------------------------------------------------- 1 | import * as lsp from 'vscode-languageserver'; 2 | import * as Parser from 'web-tree-sitter'; 3 | import { config } from '../config'; 4 | import { connection } from '../connection'; 5 | import { DocumentStore } from '../documentStore'; 6 | import { findLocals } from '../queries/locals'; 7 | import { Trees } from '../trees'; 8 | import { asParserPoint } from '../utils/position'; 9 | import { DepsIndex } from './depsIndex'; 10 | import { SymbolIndex } from './symbolIndex'; 11 | import { stringifyType } from './typeInference'; 12 | 13 | export class CompletionItemProvider { 14 | constructor( 15 | private readonly _documents: DocumentStore, 16 | private readonly _trees: Trees, 17 | private readonly _symbols: SymbolIndex, 18 | private readonly _deps: DepsIndex 19 | ) {} 20 | 21 | register(connection: lsp.Connection) { 22 | connection.client.register(lsp.CompletionRequest.type, { 23 | documentSelector: [{ language: 'func' }], 24 | triggerCharacters: ['.', '~'] 25 | }); 26 | connection.onRequest(lsp.CompletionRequest.type, this.provideCompletionItems.bind(this)); 27 | } 28 | 29 | 30 | async provideIncludeCompletionItems(params: lsp.CompletionParams, node: Parser.SyntaxNode): Promise { 31 | let matching: string[] = await connection.sendRequest('completion/matching-files', { pathPrefix: node.text.slice(1).slice(0, -1), uri: params.textDocument.uri }); 32 | let result: lsp.CompletionItem[] = []; 33 | for (let match of matching) { 34 | let item = lsp.CompletionItem.create(match); 35 | item.kind = lsp.CompletionItemKind.File; 36 | result.push(item); 37 | } 38 | return result; 39 | } 40 | 41 | async provideCompletionItems(params: lsp.CompletionParams): Promise { 42 | const document = await this._documents.retrieve(params.textDocument.uri); 43 | const tree = this._trees.getParseTree(document.document!); 44 | if (!tree) { 45 | return []; 46 | } 47 | 48 | let cursorPosition = asParserPoint(params.position); 49 | let cursorNode = tree.rootNode.descendantForPosition(cursorPosition); 50 | if (cursorNode.type === 'string_literal' && cursorNode.parent && cursorNode.parent.type === 'include_directive') { 51 | return this.provideIncludeCompletionItems(params, cursorNode); 52 | } 53 | 54 | let isFunctionApplication = false; 55 | if (params.context?.triggerCharacter === '.' || params.context?.triggerCharacter === '~') { 56 | isFunctionApplication = true; 57 | } 58 | 59 | let result: lsp.CompletionItem[] = []; 60 | 61 | // local symbols 62 | if (!isFunctionApplication) { 63 | result.push(...findLocals(tree.rootNode, cursorPosition).map(a => { 64 | let item = lsp.CompletionItem.create(a.text); 65 | item.kind = lsp.CompletionItemKind.Variable; 66 | item.detail = stringifyType(a.type); 67 | return item; 68 | })) 69 | } 70 | 71 | let deps = this._deps.getIncludedDocuments(params.textDocument.uri); 72 | 73 | // global symbols 74 | await this._symbols.update(); 75 | let symbols = new Set(); 76 | for (let [label, occurencies] of this._symbols.index) { 77 | for (let [doc, symbol] of occurencies.entries()) { 78 | if (symbol.definitions.size === 0) { 79 | continue; 80 | } 81 | 82 | if ( 83 | config.symbolDiscovery === 'only #include' && 84 | doc !== params.textDocument.uri && 85 | !deps.includes(doc) 86 | ) { 87 | continue; 88 | } 89 | 90 | for (let [def, type] of symbol.definitions.entries()) { 91 | if (symbols.has(`${label}_${def}`)) { 92 | continue; 93 | } 94 | symbols.add(`${label}_${def}`); 95 | let item = lsp.CompletionItem.create(label); 96 | if (def === lsp.SymbolKind.Function) { 97 | item.kind = lsp.CompletionItemKind.Function; 98 | if (config.autocompleteAddParentheses && type.kind === 'function') { 99 | let fArgs = [...type.arguments]; 100 | if (isFunctionApplication) { 101 | let firstArg = fArgs.shift(); 102 | if (!firstArg) { 103 | continue; 104 | } 105 | }; 106 | item.insertText = `${label}(${fArgs.map((a, i) => `$\{${i+1}:${a.name}}`)})`; 107 | item.insertTextFormat = lsp.InsertTextFormat.Snippet; 108 | } 109 | } else if (def === lsp.SymbolKind.Variable && !isFunctionApplication) { 110 | item.kind = lsp.CompletionItemKind.Variable; 111 | } else if (def === lsp.SymbolKind.Constant && !isFunctionApplication) { 112 | item.kind = lsp.CompletionItemKind.Constant; 113 | } else { 114 | continue; 115 | } 116 | item.detail = stringifyType(type); 117 | // item.documentation = stringifyType(type); 118 | result.push(item); 119 | } 120 | } 121 | } 122 | 123 | return result; 124 | } 125 | } -------------------------------------------------------------------------------- /grammar/func/src/tree_sitter/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_PARSER_H_ 2 | #define TREE_SITTER_PARSER_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define ts_builtin_sym_error ((TSSymbol)-1) 13 | #define ts_builtin_sym_end 0 14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 15 | 16 | typedef uint16_t TSStateId; 17 | 18 | #ifndef TREE_SITTER_API_H_ 19 | typedef uint16_t TSSymbol; 20 | typedef uint16_t TSFieldId; 21 | typedef struct TSLanguage TSLanguage; 22 | #endif 23 | 24 | typedef struct { 25 | TSFieldId field_id; 26 | uint8_t child_index; 27 | bool inherited; 28 | } TSFieldMapEntry; 29 | 30 | typedef struct { 31 | uint16_t index; 32 | uint16_t length; 33 | } TSFieldMapSlice; 34 | 35 | typedef struct { 36 | bool visible; 37 | bool named; 38 | bool supertype; 39 | } TSSymbolMetadata; 40 | 41 | typedef struct TSLexer TSLexer; 42 | 43 | struct TSLexer { 44 | int32_t lookahead; 45 | TSSymbol result_symbol; 46 | void (*advance)(TSLexer *, bool); 47 | void (*mark_end)(TSLexer *); 48 | uint32_t (*get_column)(TSLexer *); 49 | bool (*is_at_included_range_start)(const TSLexer *); 50 | bool (*eof)(const TSLexer *); 51 | }; 52 | 53 | typedef enum { 54 | TSParseActionTypeShift, 55 | TSParseActionTypeReduce, 56 | TSParseActionTypeAccept, 57 | TSParseActionTypeRecover, 58 | } TSParseActionType; 59 | 60 | typedef union { 61 | struct { 62 | uint8_t type; 63 | TSStateId state; 64 | bool extra; 65 | bool repetition; 66 | } shift; 67 | struct { 68 | uint8_t type; 69 | uint8_t child_count; 70 | TSSymbol symbol; 71 | int16_t dynamic_precedence; 72 | uint16_t production_id; 73 | } reduce; 74 | uint8_t type; 75 | } TSParseAction; 76 | 77 | typedef struct { 78 | uint16_t lex_state; 79 | uint16_t external_lex_state; 80 | } TSLexMode; 81 | 82 | typedef union { 83 | TSParseAction action; 84 | struct { 85 | uint8_t count; 86 | bool reusable; 87 | } entry; 88 | } TSParseActionEntry; 89 | 90 | struct TSLanguage { 91 | uint32_t version; 92 | uint32_t symbol_count; 93 | uint32_t alias_count; 94 | uint32_t token_count; 95 | uint32_t external_token_count; 96 | uint32_t state_count; 97 | uint32_t large_state_count; 98 | uint32_t production_id_count; 99 | uint32_t field_count; 100 | uint16_t max_alias_sequence_length; 101 | const uint16_t *parse_table; 102 | const uint16_t *small_parse_table; 103 | const uint32_t *small_parse_table_map; 104 | const TSParseActionEntry *parse_actions; 105 | const char * const *symbol_names; 106 | const char * const *field_names; 107 | const TSFieldMapSlice *field_map_slices; 108 | const TSFieldMapEntry *field_map_entries; 109 | const TSSymbolMetadata *symbol_metadata; 110 | const TSSymbol *public_symbol_map; 111 | const uint16_t *alias_map; 112 | const TSSymbol *alias_sequences; 113 | const TSLexMode *lex_modes; 114 | bool (*lex_fn)(TSLexer *, TSStateId); 115 | bool (*keyword_lex_fn)(TSLexer *, TSStateId); 116 | TSSymbol keyword_capture_token; 117 | struct { 118 | const bool *states; 119 | const TSSymbol *symbol_map; 120 | void *(*create)(void); 121 | void (*destroy)(void *); 122 | bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); 123 | unsigned (*serialize)(void *, char *); 124 | void (*deserialize)(void *, const char *, unsigned); 125 | } external_scanner; 126 | const TSStateId *primary_state_ids; 127 | }; 128 | 129 | /* 130 | * Lexer Macros 131 | */ 132 | 133 | #define START_LEXER() \ 134 | bool result = false; \ 135 | bool skip = false; \ 136 | bool eof = false; \ 137 | int32_t lookahead; \ 138 | goto start; \ 139 | next_state: \ 140 | lexer->advance(lexer, skip); \ 141 | start: \ 142 | skip = false; \ 143 | lookahead = lexer->lookahead; 144 | 145 | #define ADVANCE(state_value) \ 146 | { \ 147 | state = state_value; \ 148 | goto next_state; \ 149 | } 150 | 151 | #define SKIP(state_value) \ 152 | { \ 153 | skip = true; \ 154 | state = state_value; \ 155 | goto next_state; \ 156 | } 157 | 158 | #define ACCEPT_TOKEN(symbol_value) \ 159 | result = true; \ 160 | lexer->result_symbol = symbol_value; \ 161 | lexer->mark_end(lexer); 162 | 163 | #define END_STATE() return result; 164 | 165 | /* 166 | * Parse Table Macros 167 | */ 168 | 169 | #define SMALL_STATE(id) id - LARGE_STATE_COUNT 170 | 171 | #define STATE(id) id 172 | 173 | #define ACTIONS(id) id 174 | 175 | #define SHIFT(state_value) \ 176 | {{ \ 177 | .shift = { \ 178 | .type = TSParseActionTypeShift, \ 179 | .state = state_value \ 180 | } \ 181 | }} 182 | 183 | #define SHIFT_REPEAT(state_value) \ 184 | {{ \ 185 | .shift = { \ 186 | .type = TSParseActionTypeShift, \ 187 | .state = state_value, \ 188 | .repetition = true \ 189 | } \ 190 | }} 191 | 192 | #define SHIFT_EXTRA() \ 193 | {{ \ 194 | .shift = { \ 195 | .type = TSParseActionTypeShift, \ 196 | .extra = true \ 197 | } \ 198 | }} 199 | 200 | #define REDUCE(symbol_val, child_count_val, ...) \ 201 | {{ \ 202 | .reduce = { \ 203 | .type = TSParseActionTypeReduce, \ 204 | .symbol = symbol_val, \ 205 | .child_count = child_count_val, \ 206 | __VA_ARGS__ \ 207 | }, \ 208 | }} 209 | 210 | #define RECOVER() \ 211 | {{ \ 212 | .type = TSParseActionTypeRecover \ 213 | }} 214 | 215 | #define ACCEPT_INPUT() \ 216 | {{ \ 217 | .type = TSParseActionTypeAccept \ 218 | }} 219 | 220 | #ifdef __cplusplus 221 | } 222 | #endif 223 | 224 | #endif // TREE_SITTER_PARSER_H_ 225 | -------------------------------------------------------------------------------- /client/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Utils } from 'vscode-uri'; 3 | import * as path from 'path'; 4 | import { workspace } from 'vscode'; 5 | import { 6 | LanguageClient, 7 | LanguageClientOptions, 8 | RevealOutputChannelOn, 9 | ServerOptions, 10 | TransportKind 11 | } from 'vscode-languageclient/node'; 12 | import { TextEncoder } from 'util'; 13 | 14 | 15 | let client: LanguageClient; 16 | 17 | 18 | export function activate(context: vscode.ExtensionContext) { 19 | vscode.commands.registerCommand('func.copyToClipboard', (str: string) => { 20 | vscode.env.clipboard.writeText(str); 21 | vscode.window.showInformationMessage(`Copied ${str} to clipboard`); 22 | }) 23 | 24 | startServer(context) 25 | } 26 | 27 | export function deactivate(): Thenable | undefined { 28 | if (!client) { 29 | return undefined; 30 | } 31 | return client.stop(); 32 | } 33 | 34 | async function startServer(context: vscode.ExtensionContext): Promise { 35 | const disposables: vscode.Disposable[] = []; 36 | const databaseName = context.workspaceState.get('dbName', `func_${Math.random().toString(32).slice(2)}`); 37 | context.workspaceState.update('dbName', databaseName); 38 | 39 | const clientOptions: LanguageClientOptions = { 40 | outputChannelName: 'FunC', 41 | revealOutputChannelOn: RevealOutputChannelOn.Never, 42 | documentSelector: [{ scheme: 'file', language: 'func' }], 43 | synchronize: { 44 | fileEvents: workspace.createFileSystemWatcher('**/.func.yml') 45 | }, 46 | initializationOptions: { 47 | treeSitterWasmUri: Utils.joinPath(context.extensionUri, './dist/tree-sitter.wasm').fsPath, 48 | langUri: Utils.joinPath(context.extensionUri, './dist/tree-sitter-func.wasm').fsPath, 49 | databaseName 50 | } 51 | }; 52 | 53 | const serverModule = context.asAbsolutePath( 54 | path.join('dist', 'server.js') 55 | ); 56 | 57 | // pass initial configuration to env 58 | const extConfig = vscode.workspace.getConfiguration('func'); 59 | const options = { 60 | env: { 61 | FUNC_SYMBOL_DISCOVERY: extConfig.get('symbolDiscovery'), 62 | FUNC_AUTOCOMPLETE_ADD_PARENTHESES: extConfig.get('autocompleteAddParentheses'), 63 | FUNC_EXPRERIMENTAL_DIAGNOSTICS: extConfig.get('experimentalDiagnostics'), 64 | } 65 | } 66 | const debugOptions = { ...options, execArgv: ['--nolazy', '--inspect=6009'] }; 67 | const serverOptions: ServerOptions = { 68 | run: { 69 | module: serverModule, 70 | transport: TransportKind.ipc, 71 | options: options 72 | }, 73 | debug: { 74 | module: serverModule, 75 | transport: TransportKind.ipc, 76 | options: debugOptions 77 | } 78 | }; 79 | client = new LanguageClient( 80 | 'funcServer', 81 | 'FunC Language Server', 82 | serverOptions, 83 | clientOptions 84 | ); 85 | 86 | await client.start(); 87 | 88 | // serve fileRead request 89 | client.onRequest('file/read', async raw => { 90 | const uri = vscode.Uri.parse(raw); 91 | 92 | if (uri.scheme === 'vscode-notebook-cell') { 93 | // we are dealing with a notebook 94 | try { 95 | const doc = await vscode.workspace.openTextDocument(uri); 96 | return new TextEncoder().encode(doc.getText()); 97 | } catch (err) { 98 | console.warn(err); 99 | return { type: 'not-found' }; 100 | } 101 | } 102 | 103 | if (vscode.workspace.fs.isWritableFileSystem(uri.scheme) === undefined) { 104 | // undefined means we don't know anything about these uris 105 | return { type: 'not-found' }; 106 | } 107 | 108 | let data: Uint8Array; 109 | try { 110 | const stat = await vscode.workspace.fs.stat(uri); 111 | if (stat.size > 1024 ** 2) { 112 | console.warn(`IGNORING "${uri.toString()}" because it is too large (${stat.size}bytes)`); 113 | data = Buffer.from(new Uint8Array()); 114 | } else { 115 | data = await vscode.workspace.fs.readFile(uri); 116 | } 117 | return data; 118 | } catch (err) { 119 | if (err instanceof vscode.FileSystemError) { 120 | return { type: 'not-found' }; 121 | } 122 | // graceful 123 | console.warn(err); 124 | return { type: 'not-found' }; 125 | } 126 | }); 127 | 128 | client.onRequest('completion/matching-files', async (raw: { pathPrefix: string, uri: string }) => { 129 | const uri = vscode.Uri.parse(raw.uri); 130 | let searchDirName = Utils.joinPath(uri, '..', raw.pathPrefix, (raw.pathPrefix.trim().length === 0 || raw.pathPrefix.endsWith(path.sep)) ? '' : '..'); 131 | let toSearch = raw.pathPrefix.split(path.sep).pop() ?? ''; 132 | 133 | try { 134 | let files = await vscode.workspace.fs.readDirectory(searchDirName); 135 | return files 136 | .filter(([path, type]) => { 137 | if (path === toSearch) return false; 138 | 139 | return path.startsWith(toSearch) && (type !== vscode.FileType.File || path.endsWith('.fc')); 140 | }) 141 | .map(([segment, type]) => { 142 | if (type === vscode.FileType.Directory) { 143 | return segment + path.sep; 144 | } 145 | return segment; 146 | }); 147 | } catch { 148 | return []; 149 | } 150 | }); 151 | 152 | // notify at configuration change 153 | vscode.workspace.onDidChangeConfiguration((change) => { 154 | if (change.affectsConfiguration('func')) { 155 | client.sendNotification('configuration/change', { 156 | symbolDiscovery: vscode.workspace.getConfiguration('func').get('symbolDiscovery'), 157 | autocompleteAddParentheses: vscode.workspace.getConfiguration('func').get('autocompleteAddParentheses'), 158 | experimentalDiagnostics: vscode.workspace.getConfiguration('func').get('experimentalDiagnostics'), 159 | }); 160 | } 161 | }) 162 | 163 | const langPattern = `**/*.fc`; 164 | const watcher = vscode.workspace.createFileSystemWatcher(langPattern); 165 | disposables.push(watcher); 166 | 167 | // file discover and watching. in addition to text documents we annouce and provide 168 | // all matching files 169 | 170 | // workaround for https://github.com/microsoft/vscode/issues/48674 171 | const exclude = `{${[ 172 | ...Object.keys(vscode.workspace.getConfiguration('search', null).get('exclude') ?? {}), 173 | ...Object.keys(vscode.workspace.getConfiguration('files', null).get('exclude') ?? {}) 174 | ].join(',')}}`; 175 | 176 | let size: number = Math.max(0, vscode.workspace.getConfiguration('func').get('symbolIndexSize', 500)); 177 | 178 | const init = async () => { 179 | let all = await vscode.workspace.findFiles(langPattern, exclude); 180 | 181 | const uris = all.slice(0, size); 182 | console.info(`USING ${uris.length} of ${all.length} files for ${langPattern}`); 183 | 184 | await client.sendRequest('queue/init', uris.map(String)); 185 | }; 186 | 187 | const initCancel = new Promise(resolve => disposables.push(new vscode.Disposable(resolve))); 188 | vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: '[FunC] Building Index...' }, () => Promise.race([init(), initCancel])); 189 | 190 | disposables.push(watcher.onDidCreate(uri => { 191 | client.sendNotification('queue/add', uri.toString()); 192 | })); 193 | disposables.push(watcher.onDidDelete(uri => { 194 | client.sendNotification('queue/remove', uri.toString()); 195 | client.sendNotification('file-cache/remove', uri.toString()); 196 | })); 197 | disposables.push(watcher.onDidChange(uri => { 198 | client.sendNotification('queue/add', uri.toString()); 199 | client.sendNotification('file-cache/remove', uri.toString()); 200 | })); 201 | 202 | return new vscode.Disposable(() => disposables.forEach(d => d.dispose())); 203 | } -------------------------------------------------------------------------------- /server/src/features/symbolIndex.ts: -------------------------------------------------------------------------------- 1 | import { performance } from 'perf_hooks'; 2 | import * as lsp from 'vscode-languageserver'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | import { DocumentStore } from '../documentStore'; 5 | import { Trees } from '../trees'; 6 | import { batchExecute } from '../utils/batchExecute'; 7 | import { Trie } from '../utils/trie'; 8 | import { getDocumentSymbols, SymbolMeta } from './documentSymbols'; 9 | import { getDocumentUsages, IUsage } from './references'; 10 | import { FuncType } from './typeInference'; 11 | 12 | class Queue { 13 | 14 | private readonly _queue = new Set(); 15 | 16 | enqueue(uri: string): void { 17 | if (!this._queue.has(uri)) { 18 | this._queue.add(uri); 19 | } 20 | } 21 | 22 | dequeue(uri: string): void { 23 | this._queue.delete(uri); 24 | } 25 | 26 | consume(n?: number): string[] { 27 | if (n === undefined) { 28 | const result = Array.from(this._queue.values()); 29 | this._queue.clear(); 30 | return result; 31 | } 32 | 33 | const result: string[] = []; 34 | const iter = this._queue.values(); 35 | for (; n > 0; n--) { 36 | const r = iter.next(); 37 | if (r.done) { 38 | break; 39 | } 40 | const uri = r.value; 41 | result.push(uri); 42 | this._queue.delete(uri); 43 | } 44 | return result; 45 | } 46 | } 47 | 48 | interface SymbolInfo { 49 | definitions: Map 50 | usages: Set 51 | } 52 | 53 | class Index { 54 | 55 | private readonly _index = Trie.create>(); 56 | private readonly _cleanup = new Map(); 57 | 58 | get(text: string) { 59 | return this._index.get(text); 60 | } 61 | 62 | query(query: string): IterableIterator<[string, Map]> { 63 | return this._index.query(Array.from(query)); 64 | } 65 | 66 | [Symbol.iterator](): IterableIterator<[string, Map]> { 67 | return this._index[Symbol.iterator](); 68 | } 69 | 70 | update(uri: lsp.DocumentUri, value: Map) { 71 | 72 | // (1) remove old symbol information 73 | this._cleanup.get(uri)?.(); 74 | 75 | // (2) insert new symbol information 76 | for (const [name, kinds] of value) { 77 | const all = this._index.get(name); 78 | if (all) { 79 | all.set(uri, kinds); 80 | } else { 81 | this._index.set(name, new Map([[uri, kinds]])); 82 | } 83 | } 84 | 85 | // (3) register clean-up by uri 86 | this._cleanup.set(uri, () => { 87 | for (const name of value.keys()) { 88 | const all = this._index.get(name); 89 | if (all) { 90 | if (all.delete(uri) && all.size === 0) { 91 | this._index.delete(name); 92 | } 93 | } 94 | } 95 | }); 96 | } 97 | 98 | delete(uri: lsp.DocumentUri): boolean { 99 | const cleanupFn = this._cleanup.get(uri); 100 | if (cleanupFn) { 101 | cleanupFn(); 102 | this._cleanup.delete(uri); 103 | return true; 104 | } 105 | return false; 106 | } 107 | } 108 | 109 | export class SymbolIndex { 110 | 111 | readonly index = new Index(); 112 | 113 | private readonly _syncQueue = new Queue(); 114 | private readonly _asyncQueue = new Queue(); 115 | 116 | constructor( 117 | private readonly _trees: Trees, 118 | private readonly _documents: DocumentStore 119 | ) { } 120 | 121 | addFile(uri: string): void { 122 | this._syncQueue.enqueue(uri); 123 | this._asyncQueue.dequeue(uri); 124 | } 125 | 126 | removeFile(uri: string): void { 127 | this._syncQueue.dequeue(uri); 128 | this._asyncQueue.dequeue(uri); 129 | this.index.delete(uri); 130 | } 131 | 132 | private _currentUpdate: Promise | undefined; 133 | 134 | async update(): Promise { 135 | await this._currentUpdate; 136 | this._currentUpdate = this._doUpdate(this._syncQueue.consume()).catch(e => console.error(e)); 137 | return this._currentUpdate; 138 | } 139 | 140 | private async _doUpdate(uris: string[], silent?: boolean): Promise { 141 | if (uris.length !== 0) { 142 | // schedule a new task to update the cache for changed uris 143 | let startedAt = Date.now(); 144 | const tasks = uris.map(this._createIndexTask, this); 145 | const stats = await batchExecute(tasks, 50); 146 | 147 | let totalRetrieve = 0; 148 | let totalIndex = 0; 149 | for (let stat of stats) { 150 | totalRetrieve += stat.durationRetrieve; 151 | totalIndex += stat.durationIndex; 152 | } 153 | 154 | if (!silent) { 155 | console.log(`[index] added ${uris.length} files ${Date.now() - startedAt}ms\n\tretrieval: ${Math.round(totalRetrieve)}ms\n\tindexing: ${Math.round(totalIndex)}ms`); 156 | } 157 | } 158 | } 159 | 160 | private _createIndexTask(uri: string): () => Promise<{ durationRetrieve: number, durationIndex: number }> { 161 | return async () => { 162 | // fetch document 163 | const _t1Retrieve = performance.now(); 164 | const document = await this._documents.retrieve(uri); 165 | const durationRetrieve = performance.now() - _t1Retrieve; 166 | 167 | // remove current data 168 | this.index.delete(uri); 169 | 170 | // update index 171 | const _t1Index = performance.now(); 172 | try { 173 | this._doIndex(document.document!); 174 | } catch (e) { 175 | console.log(`FAILED to index ${uri}`, e); 176 | } 177 | const durationIndex = performance.now() - _t1Index; 178 | 179 | return { durationRetrieve, durationIndex }; 180 | }; 181 | } 182 | 183 | private _doIndex(document: TextDocument, symbols?: SymbolMeta[], usages?: IUsage[]): void { 184 | 185 | const symbolInfo = new Map(); 186 | 187 | // definitions 188 | if (!symbols) { 189 | symbols = getDocumentSymbols(document, this._trees); 190 | } 191 | for (const { symbol, funcType } of symbols) { 192 | const all = symbolInfo.get(symbol.name); 193 | if (all) { 194 | all.definitions.set(symbol.kind, funcType); 195 | } else { 196 | symbolInfo.set(symbol.name, { definitions: new Map([[symbol.kind, funcType]]), usages: new Set() }); 197 | } 198 | } 199 | 200 | // usages 201 | if (!usages) { 202 | usages = getDocumentUsages(document, this._trees); 203 | } 204 | for (const usage of usages) { 205 | const all = symbolInfo.get(usage.name); 206 | if (all) { 207 | all.usages.add(usage.kind); 208 | } else { 209 | symbolInfo.set(usage.name, { definitions: new Map(), usages: new Set([usage.kind]) }); 210 | } 211 | } 212 | 213 | // update in-memory index and persisted index 214 | this.index.update(document.uri, symbolInfo); 215 | } 216 | 217 | async initFiles(_uris: string[]) { 218 | const uris = new Set(_uris); 219 | uris.forEach(this.addFile, this); 220 | await this.update(); 221 | } 222 | 223 | // --- 224 | 225 | async getDefinitions(ident: string, documents?: string[]) { 226 | 227 | await this.update(); 228 | 229 | const result: lsp.SymbolInformation[] = []; 230 | 231 | const all = this.index.get(ident) ?? []; 232 | const work: Promise[] = []; 233 | 234 | for (const [uri, value] of all) { 235 | 236 | if (value.definitions.size === 0) { 237 | // only usages 238 | continue; 239 | } 240 | 241 | if (documents !== undefined && !documents.includes(uri)) { 242 | continue; 243 | } 244 | 245 | work.push(this._documents.retrieve(uri).then(document => { 246 | const symbols = getDocumentSymbols(document.document!, this._trees); 247 | for (const { symbol: item } of symbols) { 248 | if (item.name === ident) { 249 | const info = lsp.SymbolInformation.create(item.name, item.kind, item.selectionRange, uri); 250 | result.unshift(info); 251 | } 252 | } 253 | 254 | // update index 255 | setTimeout(() => { 256 | this._asyncQueue.dequeue(document.document!.uri); 257 | this._doIndex(document.document!, symbols); 258 | }); 259 | 260 | }).catch(err => { 261 | console.log(err); 262 | })); 263 | } 264 | 265 | await Promise.allSettled(work); 266 | return result; 267 | } 268 | 269 | async getUsages(ident: string) { 270 | 271 | await this.update(); 272 | 273 | const result: lsp.Location[] = []; 274 | 275 | const all = this.index.get(ident) ?? []; 276 | const work: Promise[] = []; 277 | 278 | for (const [uri, value] of all) { 279 | 280 | if (value.usages.size === 0) { 281 | // only definitions 282 | continue; 283 | } 284 | 285 | work.push(this._documents.retrieve(uri).then(document => { 286 | const usages = getDocumentUsages(document.document!, this._trees); 287 | for (const item of usages) { 288 | if (item.name === ident) { 289 | const location = lsp.Location.create(uri, item.range); 290 | result.unshift(location); 291 | } 292 | } 293 | 294 | // update index 295 | setTimeout(() => { 296 | this._asyncQueue.dequeue(document.document!.uri); 297 | this._doIndex(document.document!, undefined, usages); 298 | }); 299 | 300 | }).catch(err => { 301 | console.log(err); 302 | })); 303 | } 304 | 305 | await Promise.allSettled(work); 306 | return result; 307 | } 308 | } -------------------------------------------------------------------------------- /server/src/__snapshots__/queries.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should query globals 1`] = ` 4 | Array [ 5 | Object { 6 | "node": Node { 7 | "0": 0, 8 | "id": 5308648, 9 | "startIndex": 20, 10 | "startPosition": Object { 11 | "column": 19, 12 | "row": 1, 13 | }, 14 | "tree": Tree { 15 | "0": 5316704, 16 | "language": Language { 17 | "0": 5294464, 18 | "fields": Array [ 19 | null, 20 | "agruments", 21 | "alternative", 22 | "arguments", 23 | "asm_body", 24 | "body", 25 | "condition", 26 | "consequent", 27 | "count", 28 | "function", 29 | "method_name", 30 | "name", 31 | "postcondition", 32 | "precondition", 33 | "return_type", 34 | "specifiers", 35 | "type", 36 | "type_variables", 37 | "variable", 38 | ], 39 | "types": Array [ 40 | , 41 | "identifier", 42 | "global", 43 | ",", 44 | ";", 45 | "->", 46 | "(", 47 | ")", 48 | "int", 49 | "cell", 50 | "slice", 51 | "builder", 52 | "cont", 53 | "tuple", 54 | "[", 55 | "]", 56 | "var_type", 57 | "()", 58 | "=", 59 | "+=", 60 | "-=", 61 | "*=", 62 | "/=", 63 | "~/=", 64 | "^/=", 65 | "%=", 66 | "~%=", 67 | "^%=", 68 | "<<=", 69 | ">>=", 70 | "~>>=", 71 | "^>>=", 72 | "&=", 73 | "|=", 74 | "^=", 75 | "?", 76 | ":", 77 | "==", 78 | "<", 79 | ">", 80 | "<=", 81 | ">=", 82 | "!=", 83 | "<=>", 84 | "<<", 85 | ">>", 86 | "~>>", 87 | "^>>", 88 | "-", 89 | "+", 90 | "|", 91 | "^", 92 | "*", 93 | "/", 94 | "%", 95 | "~/", 96 | "^/", 97 | "~%", 98 | "^%", 99 | "/%", 100 | "&", 101 | "~", 102 | ".", 103 | "type", 104 | "function_name", 105 | "impure", 106 | "inline", 107 | "inline_ref", 108 | "method_id", 109 | "forall", 110 | "asm", 111 | "return", 112 | "{", 113 | "}", 114 | "repeat", 115 | "if", 116 | "ifnot", 117 | "else", 118 | "elseif", 119 | "elseifnot", 120 | "do", 121 | "until", 122 | "while", 123 | "number_literal", 124 | "string_literal", 125 | "underscore", 126 | "comment", 127 | "translation_unit", 128 | , 129 | "global_var_declarations", 130 | , 131 | , 132 | "function_type", 133 | , 134 | , 135 | "primitive_type", 136 | "tensor_type", 137 | "tuple_type", 138 | "hole_type", 139 | "unit_type", 140 | "type_identifier", 141 | , 142 | , 143 | , 144 | , 145 | , 146 | , 147 | , 148 | , 149 | , 150 | "method_call", 151 | , 152 | "function_application", 153 | "variable_declaration", 154 | "type_expression", 155 | "parenthesized_type_expression", 156 | "tensor_type_expression", 157 | "tuple_type_expression", 158 | , 159 | , 160 | "unit_literal", 161 | "parenthesized_expression", 162 | "tensor_expression", 163 | "tuple_expression", 164 | "function_definition", 165 | "inline", 166 | "method_id", 167 | "type_variables_list", 168 | "parameter_list", 169 | "parameter_declaration", 170 | "asm_function_body", 171 | "asm_specifier", 172 | "asm_instruction", 173 | , 174 | "return_statement", 175 | "block_statement", 176 | "expression_statement", 177 | "empty_statement", 178 | "repeat_statement", 179 | "if_statement", 180 | , 181 | "do_statement", 182 | "while_statement", 183 | , 184 | , 185 | , 186 | , 187 | , 188 | , 189 | , 190 | , 191 | , 192 | , 193 | , 194 | , 195 | , 196 | , 197 | , 198 | , 199 | ], 200 | }, 201 | "textCallback": [Function], 202 | }, 203 | }, 204 | "range": Object { 205 | "end": Object { 206 | "character": 21, 207 | "line": 1, 208 | }, 209 | "start": Object { 210 | "character": 19, 211 | "line": 1, 212 | }, 213 | }, 214 | "text": "id", 215 | "type": "globalVar", 216 | }, 217 | Object { 218 | "node": Node { 219 | "0": 0, 220 | "id": 5316504, 221 | "startIndex": 36, 222 | "startPosition": Object { 223 | "column": 11, 224 | "row": 3, 225 | }, 226 | "tree": Tree { 227 | "0": 5316704, 228 | "language": Language { 229 | "0": 5294464, 230 | "fields": Array [ 231 | null, 232 | "agruments", 233 | "alternative", 234 | "arguments", 235 | "asm_body", 236 | "body", 237 | "condition", 238 | "consequent", 239 | "count", 240 | "function", 241 | "method_name", 242 | "name", 243 | "postcondition", 244 | "precondition", 245 | "return_type", 246 | "specifiers", 247 | "type", 248 | "type_variables", 249 | "variable", 250 | ], 251 | "types": Array [ 252 | , 253 | "identifier", 254 | "global", 255 | ",", 256 | ";", 257 | "->", 258 | "(", 259 | ")", 260 | "int", 261 | "cell", 262 | "slice", 263 | "builder", 264 | "cont", 265 | "tuple", 266 | "[", 267 | "]", 268 | "var_type", 269 | "()", 270 | "=", 271 | "+=", 272 | "-=", 273 | "*=", 274 | "/=", 275 | "~/=", 276 | "^/=", 277 | "%=", 278 | "~%=", 279 | "^%=", 280 | "<<=", 281 | ">>=", 282 | "~>>=", 283 | "^>>=", 284 | "&=", 285 | "|=", 286 | "^=", 287 | "?", 288 | ":", 289 | "==", 290 | "<", 291 | ">", 292 | "<=", 293 | ">=", 294 | "!=", 295 | "<=>", 296 | "<<", 297 | ">>", 298 | "~>>", 299 | "^>>", 300 | "-", 301 | "+", 302 | "|", 303 | "^", 304 | "*", 305 | "/", 306 | "%", 307 | "~/", 308 | "^/", 309 | "~%", 310 | "^%", 311 | "/%", 312 | "&", 313 | "~", 314 | ".", 315 | "type", 316 | "function_name", 317 | "impure", 318 | "inline", 319 | "inline_ref", 320 | "method_id", 321 | "forall", 322 | "asm", 323 | "return", 324 | "{", 325 | "}", 326 | "repeat", 327 | "if", 328 | "ifnot", 329 | "else", 330 | "elseif", 331 | "elseifnot", 332 | "do", 333 | "until", 334 | "while", 335 | "number_literal", 336 | "string_literal", 337 | "underscore", 338 | "comment", 339 | "translation_unit", 340 | , 341 | "global_var_declarations", 342 | , 343 | , 344 | "function_type", 345 | , 346 | , 347 | "primitive_type", 348 | "tensor_type", 349 | "tuple_type", 350 | "hole_type", 351 | "unit_type", 352 | "type_identifier", 353 | , 354 | , 355 | , 356 | , 357 | , 358 | , 359 | , 360 | , 361 | , 362 | "method_call", 363 | , 364 | "function_application", 365 | "variable_declaration", 366 | "type_expression", 367 | "parenthesized_type_expression", 368 | "tensor_type_expression", 369 | "tuple_type_expression", 370 | , 371 | , 372 | "unit_literal", 373 | "parenthesized_expression", 374 | "tensor_expression", 375 | "tuple_expression", 376 | "function_definition", 377 | "inline", 378 | "method_id", 379 | "type_variables_list", 380 | "parameter_list", 381 | "parameter_declaration", 382 | "asm_function_body", 383 | "asm_specifier", 384 | "asm_instruction", 385 | , 386 | "return_statement", 387 | "block_statement", 388 | "expression_statement", 389 | "empty_statement", 390 | "repeat_statement", 391 | "if_statement", 392 | , 393 | "do_statement", 394 | "while_statement", 395 | , 396 | , 397 | , 398 | , 399 | , 400 | , 401 | , 402 | , 403 | , 404 | , 405 | , 406 | , 407 | , 408 | , 409 | , 410 | , 411 | ], 412 | }, 413 | "textCallback": [Function], 414 | }, 415 | }, 416 | "range": Object { 417 | "end": Object { 418 | "character": 15, 419 | "line": 3, 420 | }, 421 | "start": Object { 422 | "character": 11, 423 | "line": 3, 424 | }, 425 | }, 426 | "text": "test", 427 | "type": "function", 428 | }, 429 | ] 430 | `; 431 | -------------------------------------------------------------------------------- /grammar/func/examples/stdlib.fc: -------------------------------------------------------------------------------- 1 | ;; Standard library for funC 2 | ;; 3 | 4 | forall X -> tuple cons(X head, tuple tail) asm "CONS"; 5 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; 6 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; 7 | forall X -> X car(tuple list) asm "CAR"; 8 | tuple cdr(tuple list) asm "CDR"; 9 | tuple empty_tuple() asm "NIL"; 10 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; 11 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; 12 | forall X -> [X] single(X x) asm "SINGLE"; 13 | forall X -> X unsingle([X] t) asm "UNSINGLE"; 14 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; 15 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; 16 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; 17 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; 18 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; 19 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; 20 | forall X -> X first(tuple t) asm "FIRST"; 21 | forall X -> X second(tuple t) asm "SECOND"; 22 | forall X -> X third(tuple t) asm "THIRD"; 23 | forall X -> X fourth(tuple t) asm "3 INDEX"; 24 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; 25 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; 26 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; 27 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; 28 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; 29 | forall X -> X null() asm "PUSHNULL"; 30 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; 31 | 32 | int now() asm "NOW"; 33 | slice my_address() asm "MYADDR"; 34 | [int, cell] get_balance() asm "BALANCE"; 35 | int cur_lt() asm "LTIME"; 36 | int block_lt() asm "BLOCKLT"; 37 | 38 | int cell_hash(cell c) asm "HASHCU"; 39 | int slice_hash(slice s) asm "HASHSU"; 40 | int string_hash(slice s) asm "SHA256U"; 41 | 42 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; 43 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; 44 | 45 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; 46 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; 47 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 48 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 49 | 50 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; 51 | 52 | () dump_stack() impure asm "DUMPSTK"; 53 | 54 | cell get_data() asm "c4 PUSH"; 55 | () set_data(cell c) impure asm "c4 POP"; 56 | cont get_c3() impure asm "c3 PUSH"; 57 | () set_c3(cont c) impure asm "c3 POP"; 58 | cont bless(slice s) impure asm "BLESS"; 59 | 60 | () accept_message() impure asm "ACCEPT"; 61 | () set_gas_limit(int limit) impure asm "SETGASLIMIT"; 62 | () commit() impure asm "COMMIT"; 63 | () buy_gas(int gram) impure asm "BUYGAS"; 64 | 65 | int min(int x, int y) asm "MIN"; 66 | int max(int x, int y) asm "MAX"; 67 | (int, int) minmax(int x, int y) asm "MINMAX"; 68 | int abs(int x) asm "ABS"; 69 | 70 | slice begin_parse(cell c) asm "CTOS"; 71 | () end_parse(slice s) impure asm "ENDS"; 72 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; 73 | cell preload_ref(slice s) asm "PLDREF"; 74 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; 75 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; 76 | ;; int preload_int(slice s, int len) asm "PLDIX"; 77 | ;; int preload_uint(slice s, int len) asm "PLDUX"; 78 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; 79 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; 80 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; 81 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; 82 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; 83 | slice first_bits(slice s, int len) asm "SDCUTFIRST"; 84 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 85 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 86 | slice slice_last(slice s, int len) asm "SDCUTLAST"; 87 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; 88 | cell preload_dict(slice s) asm "PLDDICT"; 89 | slice skip_dict(slice s) asm "SKIPDICT"; 90 | 91 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; 92 | cell preload_maybe_ref(slice s) asm "PLDOPTREF"; 93 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; 94 | 95 | int cell_depth(cell c) asm "CDEPTH"; 96 | 97 | int slice_refs(slice s) asm "SREFS"; 98 | int slice_bits(slice s) asm "SBITS"; 99 | (int, int) slice_bits_refs(slice s) asm "SBITREFS"; 100 | int slice_empty?(slice s) asm "SEMPTY"; 101 | int slice_data_empty?(slice s) asm "SDEMPTY"; 102 | int slice_refs_empty?(slice s) asm "SREMPTY"; 103 | int slice_depth(slice s) asm "SDEPTH"; 104 | 105 | int builder_refs(builder b) asm "BREFS"; 106 | int builder_bits(builder b) asm "BBITS"; 107 | int builder_depth(builder b) asm "BDEPTH"; 108 | 109 | builder begin_cell() asm "NEWC"; 110 | cell end_cell(builder b) asm "ENDC"; 111 | builder store_ref(builder b, cell c) asm(c b) "STREF"; 112 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; 113 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; 114 | builder store_slice(builder b, slice s) asm "STSLICER"; 115 | builder store_grams(builder b, int x) asm "STGRAMS"; 116 | builder store_dict(builder b, cell c) asm(c b) "STDICT"; 117 | 118 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; 119 | tuple parse_addr(slice s) asm "PARSEMSGADDR"; 120 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; 121 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; 122 | 123 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 124 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 125 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 126 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 127 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; 128 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; 129 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; 130 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; 131 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; 132 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; 133 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; 134 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; 135 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; 136 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 137 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 138 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 139 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 140 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 141 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 142 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 143 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 144 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 145 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 146 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; 147 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; 148 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; 149 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; 150 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 151 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 152 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 153 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 154 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 155 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 156 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; 157 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; 158 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; 159 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; 160 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 161 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 162 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 163 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 164 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 165 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 166 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 167 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 168 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 169 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 170 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 171 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 172 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; 173 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; 174 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; 175 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; 176 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; 177 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; 178 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; 179 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; 180 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; 181 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; 182 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; 183 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; 184 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; 185 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; 186 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; 187 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; 188 | cell new_dict() asm "NEWDICT"; 189 | int dict_empty?(cell c) asm "DICTEMPTY"; 190 | 191 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; 192 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; 193 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; 194 | 195 | cell config_param(int x) asm "CONFIGOPTPARAM"; 196 | int cell_null?(cell c) asm "ISNULL"; 197 | 198 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; 199 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; 200 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; 201 | () set_code(cell new_code) impure asm "SETCODE"; 202 | 203 | int random() impure asm "RANDU256"; 204 | int rand(int range) impure asm "RAND"; 205 | int get_seed() impure asm "RANDSEED"; 206 | int set_seed() impure asm "SETRAND"; 207 | () randomize(int x) impure asm "ADDRAND"; 208 | () randomize_lt() impure asm "LTIME" "ADDRAND"; 209 | 210 | 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . --------------------------------------------------------------------------------