tokiwa-software/fuzion
GitHub: tokiwa-software/fuzion
Fuzion 是一门通过统一的 feature 机制、契约式设计和代数效应系统来实现简洁、安全、正确编程的现代语言实现。
Stars: 60 | Forks: 13
#
Fuzion
[](https://api.securityscorecards.dev/projects/github.com/tokiwa-software/fuzion)
[](https://github.com/tokiwa-software/fuzion/actions/workflows/linux.yml)
[](https://github.com/tokiwa-software/fuzion/actions/workflows/apple.yml)
[](https://github.com/tokiwa-software/fuzion/actions/workflows/windows.yml)
[](https://github.com/tokiwa-software/fuzion/actions/workflows/interpreter.yml)
## 一门专注于简单性、安全性和正确性的语言。
* [示例](#examples)
* [文档](#documentation)
* [克隆](#clone)
* [依赖包](#required-packages)
* [Linux](#linux)
* [MacOS](#macos)
* [Windows](#windows)
* [构建](#build)
* [运行](#run)
* [测试](#tests)
* [运行所有测试](#running-all-tests)
* [运行单个测试](#run-a-single-test)
* [记录测试](#record-a-test)
* [软依赖](#soft-dependencies)
* [安装预编译版本](#install-prebuilt)
* [语言服务器](#language-server)
* [安装](#install)
* [Vim](#vim)
* [Emacs](#emacs)
* [Eglot](#eglot)
* [LSP-Mode](#lsp-mode)
* [独立运行](#run-standalone)
* [socket](#transport-socket)
* [stdio](#transport-stdio)
## 示例
```
hello_world is
# first we define a custom mutate effect.
# we will need this for buffered reading from stdin
#
lm : mutate is
# calling `lm` creates an instance of our mutate effect,
# `instate_self` is then used to instate this instance and
# run code in the context of the instated effect.
#
lm ! ()->
# read someone's name from standard input
#
get_name =>
(io.stdin.reader lm) ! ()->
(io.buffered lm).read_line ? str String => str | io.end_of_file => ""
# greet someone with the name given
#
greet(name String) is
say "Hello, {name}!"
# greet the user
#
x := greet get_name
# you can access any feature - even argument features of other features
# from outside
#
say "How are you, {x.name}?"
```
这个 `hello_world` 示例很好地展示了 Fuzion 中一个非常重要的概念:一切都是 *feature*。在其他编程语言中,*类*、*方法*、*接口* 以及各种其他概念常常会造成混乱,而 *feature* 正是 Fuzion 解决这种混乱的方案。既然一切都是 feature,程序员就不必再关心这些区别,编译器会完成这项工作。正如你所看到的,甚至可以从外部访问某个 feature 的参数 feature。
```
ex_gcd is
# return common divisors of a and b
#
common_divisors_of(a, b i32) =>
max := max a.abs b.abs
(1..max).flat_map i->
if (a % i = 0) && (b % i = 0)
[-i, i]
else
[]
# find the greatest common divisor of a and b
#
gcd(a, b i32)
pre
safety: (a != 0 || b != 0)
post
safety: a % result = 0
safety: b % result = 0
pedantic: (common_divisors_of a b).reduce bool true (acc,cur -> acc && (result % cur = 0))
=>
if b = 0 then a else gcd b (a % b)
say <| gcd 8 12
say <| gcd -8 12
say <| gcd 28 0
```
这个示例实现了一个简单版本的算法,用于找出两个数的最大公约数。然而,它也演示了 Fuzion 的一个显著特性:契约式设计。通过为 feature 指定前置条件和后置条件,正确性检查成为可能。
```
generator_effect is
# define a generator effect with a yield operation
#
gen(T type,
yield T->unit # yield is called by code to yield values
) : effect is
# traverse a list and yield the elements
#
list.traverse unit =>
match list.this
c Cons => (generator_effect.gen A).env.yield c.head; c.tail.traverse
nil =>
# bind the yield operation dynamically
#
(gen i32 (i -> say "yielded $i")) ! ()->
[0,8,15].as_list.traverse
```
Fuzion 的另一个主要概念是
*[代数效应](https://en.wikipedia.org/wiki/Effect_system)* - 这是一种以安全方式封装带有副作用代码的新方法。
在上面的示例中,使用了一个自定义的 *effect* 来实现一个带有 `yield` 操作的生成器。在其他一些语言中,这需要语言本身提供一个 `yield` 关键字,但在 Fuzion 中,无需语言层面的特殊支持即可实现这一点。
如果你想亲自体验一下 Fuzion,请尝试 [交互式教程](https://fuzion-lang.dev/tutorial/index)。
## 文档
有关语言和实现设计,请访问 [fuzion-lang.dev](https://fuzion-lang.dev)。
## 克隆
```
git clone https://github.com/tokiwa-software/fuzion
```
## 依赖包
### Linux
- OpenJDK 25[^1], (需要包含 jmods)
- clang LLVM C 编译器
- GNU make
- libgc
### MacOS
- OpenJDK 25[^1], (需要包含 jmods)
- clang LLVM C 编译器
- GNU make
- libgc
### Windows
1. 安装 chocolatey: [chocolatey.org](https://chocolatey.org/install)
2. 在 Powershell 中:
1. choco install git openjdk make msys2 diffutils
2. [Environment]::SetEnvironmentVariable("Path","c:\tools\msys64\ucrt64\bin;" + $env:Path , "User")
3. 在文件 C:\tools\msys64\msys2_shell.cmd 中修改这一行:'rem set MSYS2_PATH_TYPE=inherit' 为 'set MSYS2_PATH_TYPE=inherit'
4. 在 msys2 shell 中(执行 C:\tools\msys64\msys2_shell.cmd):
1. pacman -S mingw-w64-x86_64-clang
2. make
5. 执行 ./bin/windows_install_boehm_gc.sh
## 构建
```
cd fuzion
make
```
你现在应该有一个名为 **build** 的文件夹了。
## 运行
```
cd build
export PATH=$PWD/bin:$PATH
cd tests/rosettacode_factors_of_an_integer
fz factors
```
要编译同一个示例(需要 clang C 编译器):
```
fz -c factors
./factors
```
祝你玩得开心!
## 测试
### 运行所有测试
在源代码文件夹中,要运行所有后端的所有测试,请使用以下命令:
```
make run_tests
```
当测试完成后,你将看到一份包含成功、失败和跳过的测试的摘要。
如果只想运行特定后端的测试,请使用:
```
# 针对 jvm backend
make run_tests_jvm
# 针对 c backend
make run_tests_c
# 针对 interpreter backend
make run_tests_int
```
### 运行单个测试
在源代码文件夹中运行:
```
make _BACKEND_ -C _BUILD_DIR_/tests/_TEST_/
```
其中 `_BACKEND_` 可以是以下之一:
- jvm
- c
- int
- effect
- 留空以在所有后端上运行测试
除非你指定了自定义的构建目录,否则你需要将 `_BUILD_DIR_` 替换为 `build`。
最后,将 `_TEST_` 替换为测试的名称。
完整示例:
```
make jvm -C build/tests/hello
```
### 记录测试
这与运行测试的操作相同,只需指定不同的 make target 即可。
- record
- record_jvm
- record_c
- record_int
- record_effect
完整示例:
```
make record_jvm -C ./build/tests/hello
```
这将会记录标准输出和标准错误,并将它们保存在测试目录中的文件里。(HelloWorld.fz.expected_out, HelloWorld.fz.expected_err)
## 软依赖
没有这些依赖也可以编译和使用编译器。
但以下工具/依赖会用于例如生成文档或运行测试套件:
- antlr,用于 ebnf 语法
- asciidoctor,asciidoctor-pdf
- sed,用于规范化测试输出
- wget,用于下载 jar 依赖
- org.eclipse.lsp4j 及其他,用于语言服务器
## 安装预编译版本
[](https://repology.org/project/fuzion/versions)
## 语言服务器
### 安装
|客户端|仓库|
|---|---|
|vscode|https://github.com/tokiwa-software/vscode-fuzion|
|vim|请参阅以下说明|
|emacs|请参阅以下说明|
|eclipse (theia)|https://github.com/tokiwa-software/vscode-fuzion|
#### Vim
0. 注意:fuzion_language_server (来自 ./bin/) 需要包含在 $PATH 中
1. .vimrc 示例:
:filetype on
call plug#begin('~/.vim/plugged')
Plug 'neoclide/coc.nvim', {'branch': 'release'}
call plug#end()
2. 在 vim 中
1. `:PlugInstall`
2. `:CocConfig`
{
"languageserver": {
"fuzion": {
"command": "fuzion_language_server",
"args" : ["-stdio"],
"filetypes": [
"fz",
"fuzion"
]
}
}
}
3. 添加文件类型检测文件 ~/.vim/ftdetect/fz.vim
au BufRead,BufNewFile *.fz set filetype=fz
#### Emacs
对于 emacs,有 eglot 或 lsp-mode 两种选择。
##### Eglot
- M-x package-install RET eglot RET (仅 emacs 版本 <29 时需要)
- 将以下代码添加到 ~/.emacs.d/fuzion-lsp.el 以启用 [eglot](https://github.com/joaotavora/eglot)
```
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives '("elpa" . "https://elpa.gnu.org/packages/"))
;; Comment/uncomment this line to enable MELPA Stable if desired. See `package-archive-priorities`
;; and `package-pinned-packages`. Most users will not need or want to do this.
;;(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(package-initialize)
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(inhibit-startup-screen t)
'(package-selected-packages '(eglot ##)))
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
)
(define-derived-mode fuzion-mode
fundamental-mode "Fuzion"
"Major mode for Fuzion.")
(add-to-list 'auto-mode-alist '("\\.fz\\'" . fuzion-mode))
(require 'eglot)
(add-to-list 'eglot-server-programs
'(fuzion-mode . ("fuzion_language_server" "-stdio")))
(add-hook 'after-init-hook 'global-company-mode)
(add-hook 'fuzion-mode-hook 'eglot-ensure)
(provide 'init)
;;; init.el ends here
```
- 将以下行添加到 ~/.emacs.d/init.el 或 ~/.emacs 中
(load "~/.emacs.d/fuzion-lsp.el")
##### LSP-Mode
- 使用以下命令为 emacs 安装 lsp-mode、flycheck 和 company
- M-x package-install RET lsp-mode
- M-x package-install RET flycheck
- M-x package-install RET company RET
- 将以下代码添加到 ~/.emacs.d/fuzion-lsp.el 以启用 [lsp-mode](https://github.com/emacs-lsp/lsp-mode)
```
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives '("elpa" . "https://elpa.gnu.org/packages/"))
(package-initialize)
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(inhibit-startup-screen t)
'(package-selected-packages '(lsp-ui company flycheck lsp-mode ##)))
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
)
(define-derived-mode fuzion-mode
fundamental-mode "Fuzion"
"Major mode for Fuzion.")
(add-to-list 'auto-mode-alist '("\\.fz\\'" . fuzion-mode))
(require 'lsp-mode)
(global-flycheck-mode)
(add-to-list 'lsp-language-id-configuration '(fuzion-mode . "fuzion"))
(defgroup lsp-fuzionlsp nil
"LSP support for Fuzion, using fuzionlsp."
:group 'lsp-mode
:link '(url-link ""))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection (lambda ()
`(,"fuzion_language_server"
"-stdio")))
:major-modes '(fuzion-mode)
:priority -1
:server-id 'fuzionls))
(lsp-consistency-check lsp-fuzion)
(add-hook 'fuzion-mode-hook #'lsp)
(add-hook 'after-init-hook 'global-company-mode)
(setq lsp-enable-symbol-highlighting t)
;; (setq lsp-enable-semantic-highlighting t
;; lsp-semantic-tokens-enable t
;; lsp-semantic-tokens-warn-on-missing-face t
;; lsp-semantic-tokens-apply-modifiers nil
;; lsp-semantic-tokens-allow-delta-requests nil
;; lsp-semantic-tokens-allow-ranged-requests nil)
;; (setq lsp-modeline-code-actions-mode t)
;; (setq lsp-modeline-code-actions-segments '(name icon))
;; (setq lsp-log-io t)
(provide 'lsp-fuzion)
(provide 'init)
;;; init.el ends here
```
- 将以下行添加到 ~/.emacs.d/init.el 或 ~/.emacs 中
(load "~/.emacs.d/fuzion-lsp.el")
### 独立运行
#### 传输 socket
- 运行 `./bin/fuzion_language_server -socket --port=3000`
- 将客户端连接到服务器打印到标准输出的(随机)端口。
#### 传输 stdio
- 运行 `./bin/fuzion_language_server -stdio`