mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
Finch 1.0.0
A small, fast programming language on LLVM with C-like syntax. - Lexer, recursive-descent parser, single-pass type checker + LLVM IR codegen, O2 pipeline - Types: int/float/bool/char/str, sized i8..u64/f32/f64, pointers ptr[T] with addr()/.value/null - Functions (fn ... -> T), if/else, while, for..in, break/continue, Go-style operator precedence - Implicit conversions only when lossless; literals adapt to their target type - Runtime checks for division by zero, MIN/-1 and null .value - C interop: import "header.h" via libclang, link "lib" with pkg-config, link-error advice - Human-readable errors with source excerpts - Test suite (34 tests), examples, docs for beginners/technicians/engineers in English and Polish Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
0591cd20f3
73 changed files with 8066 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
*.o
|
||||
37
CMakeLists.txt
Normal file
37
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
project(finch VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
message(STATUS "Using LLVM ${LLVM_PACKAGE_VERSION}")
|
||||
|
||||
add_executable(finch
|
||||
src/main.cpp
|
||||
src/lexer.cpp
|
||||
src/parser.cpp
|
||||
src/codegen.cpp
|
||||
src/cimport.cpp
|
||||
)
|
||||
|
||||
# libclang reads C headers for `import "stdio.h"`
|
||||
find_path(LIBCLANG_INCLUDE clang-c/Index.h HINTS ${LLVM_INCLUDE_DIRS})
|
||||
find_library(LIBCLANG NAMES clang libclang HINTS ${LLVM_LIBRARY_DIRS})
|
||||
if(NOT LIBCLANG_INCLUDE OR NOT LIBCLANG)
|
||||
message(FATAL_ERROR "libclang not found (on Nix: run inside nix-shell)")
|
||||
endif()
|
||||
|
||||
target_include_directories(finch SYSTEM PRIVATE ${LLVM_INCLUDE_DIRS} ${LIBCLANG_INCLUDE})
|
||||
target_compile_definitions(finch PRIVATE ${LLVM_DEFINITIONS} FINCH_VERSION="${PROJECT_VERSION}")
|
||||
target_compile_options(finch PRIVATE -Wall -Wextra)
|
||||
|
||||
# Link the one shared libLLVM when available: libclang uses it too, and two copies
|
||||
# of LLVM in one process crash on exit.
|
||||
if(TARGET LLVM)
|
||||
set(LLVM_LIBS LLVM)
|
||||
else()
|
||||
llvm_map_components_to_libnames(LLVM_LIBS core support passes native)
|
||||
endif()
|
||||
target_link_libraries(finch PRIVATE ${LLVM_LIBS} ${LIBCLANG})
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
119
README.md
Normal file
119
README.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Finch 🐦
|
||||
|
||||
**Small, quick, sharp.** A programming language on LLVM with C-like syntax, simple enough
|
||||
to learn in an hour and as fast as C.
|
||||
|
||||
> 🇵🇱 [Polska wersja](README.pl.md)
|
||||
|
||||
```c
|
||||
import "math.h"
|
||||
|
||||
fn hypot2(float a, float b) -> float {
|
||||
return sqrt(a * a + b * b)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
name := "Finch"
|
||||
print("Hello from", name, "!")
|
||||
|
||||
for i in 1..4 {
|
||||
print(i, "→", hypot2(float(i), 1.0))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Why Finch
|
||||
|
||||
- **Simple.** No semicolons, no headers, no preprocessor. Each thing has one way to do it.
|
||||
- **Fast.** It compiles through LLVM's `-O2` pipeline to native code, with no runtime or garbage collector.
|
||||
Recursive `fib(40)` runs in about 0.19 s (`clang -O2` on the same C: about 0.23 s).
|
||||
- **Safer than C, where it is cheap.** No silent narrowing and no signed/unsigned mixing. Division by zero
|
||||
and null `.value` stop with a clear runtime error instead of undefined behavior. Operator precedence
|
||||
that does not bite (`x & 1 == 0` means what it says).
|
||||
- **Errors that talk like a person:**
|
||||
```
|
||||
game.fn:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
|
||||
3 | i32 y = x
|
||||
| ^
|
||||
```
|
||||
- **Any C library, directly.** `import "GLFW/glfw3.h"` + `link "glfw"`, and you can call it.
|
||||
Headers are read by libclang, so there is nothing to declare by hand.
|
||||
|
||||
## Quick start
|
||||
|
||||
Linux x86-64, LLVM 21.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Nexoniarz/Finch.git
|
||||
cd Finch
|
||||
nix-shell # or install LLVM 21 + libclang + cmake + ninja yourself
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build
|
||||
|
||||
./build/finch run examples/hello.fn
|
||||
./build/finch build examples/tour.fn -o tour && ./tour
|
||||
tests/run.sh # 34 passed, 0 failed
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Pick the guide that fits you:
|
||||
|
||||
| Guide | For | English | Polski |
|
||||
|---|---|---|---|
|
||||
| **Beginners** | Never programmed: students, seniors, anyone curious | [for-beginners.md](docs/en/for-beginners.md) | [dla-poczatkujacych.md](docs/pl/dla-poczatkujacych.md) |
|
||||
| **Technicians** | Know computers, new to programming: install, whole language, C libraries, troubleshooting | [for-technicians.md](docs/en/for-technicians.md) | [dla-technikow.md](docs/pl/dla-technikow.md) |
|
||||
| **Engineers** | Everything: lexer, grammar, AST, type rules, IR lowering, libclang import, ABI, internals | [for-engineers.md](docs/en/for-engineers.md) | [dla-inzynierow.md](docs/pl/dla-inzynierow.md) |
|
||||
|
||||
## The language at a glance
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Variables | `x := 5` (inferred) or `int x = 5` |
|
||||
| Types | `int` `float` `bool` `char` `str`, sized `i8`…`i64` `u8`…`u64` `f32` `f64`, pointers `ptr[T]` / `ptr` |
|
||||
| Functions | `fn add(int a, int b) -> int { return a + b }` |
|
||||
| Control | `if` / `else if` / `else`, `while`, `for i in 0..10`, `break`, `continue` |
|
||||
| Pointers | `p := addr(x)`, `p.value = 5`, `null` |
|
||||
| Conversions | `int(3.9)`, `u8(x)`, `float(n)` (implicit only when nothing is lost) |
|
||||
| Operators | `+ - * / %`, `& \| ^ << >> ~`, `== != < <= > >=`, `&& \|\| !` |
|
||||
| C interop | `import "stdio.h"`, `link "glfw"` |
|
||||
| Output | `print(a, b, c)` prints anything, separated by spaces |
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Shows |
|
||||
|---|---|
|
||||
| [`examples/hello.fn`](examples/hello.fn) | the smallest program |
|
||||
| [`examples/tour.fn`](examples/tour.fn) | variables, functions, conditions, loops |
|
||||
| [`examples/types.fn`](examples/types.fn) | sized numbers and pointers |
|
||||
| [`examples/c_import.fn`](examples/c_import.fn) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
|
||||
| [`examples/window.fn`](examples/window.fn) | a GLFW + OpenGL window |
|
||||
| [`examples/llvm.fn`](examples/llvm.fn) | Finch building LLVM IR through the LLVM-C API |
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] Lexer, parser, LLVM codegen, O2 optimization
|
||||
- [x] Types incl. sized integers, pointers, conversions; functions; `if`/`while`/`for`
|
||||
- [x] Readable compile errors; runtime checks (division, null)
|
||||
- [x] Bitwise operators with sane precedence
|
||||
- [x] C headers through libclang; `link` with pkg-config; link-error advice
|
||||
- [ ] `struct` (and C structs by value)
|
||||
- [ ] Arrays/slices with `.len` and bounds checks
|
||||
- [ ] Memory: automatic free at the end of the owning block, `defer`, manual `free` when wanted
|
||||
- [ ] Strings: `+`, `len`, `str(x)`; `input()`
|
||||
- [ ] Finch modules (`import math`)
|
||||
- [ ] Debug info (DWARF)
|
||||
- [ ] Bootstrap: the Finch compiler written in Finch
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
src/ compiler: lexer, parser, AST, codegen, C import, driver (C++17, ~2.4k lines)
|
||||
examples/ example programs
|
||||
tests/ run/ (golden output), fail/ (expected errors), run.sh
|
||||
docs/ en/ and pl/ guides for three audiences
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[Apache License 2.0](LICENSE). Copyright 2026 Nexoniarz.
|
||||
119
README.pl.md
Normal file
119
README.pl.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Finch 🐦
|
||||
|
||||
**Mały, zwinny, skuteczny.** Język programowania na LLVM ze składnią podobną do C,
|
||||
na tyle prosty, że nauczysz się go w godzinę, i tak szybki jak C.
|
||||
|
||||
> 🇬🇧 [English version](README.md)
|
||||
|
||||
```c
|
||||
import "math.h"
|
||||
|
||||
fn przeciwprostokatna(float a, float b) -> float {
|
||||
return sqrt(a * a + b * b)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
imie := "Finch"
|
||||
print("Cześć od", imie, "!")
|
||||
|
||||
for i in 1..4 {
|
||||
print(i, "→", przeciwprostokatna(float(i), 1.0))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dlaczego Finch
|
||||
|
||||
- **Prosty.** Bez średników, bez plików nagłówkowych, bez preprocesora. Każdą rzecz robi się na jeden sposób.
|
||||
- **Szybki.** Kompiluje się przez potok `-O2` z LLVM do kodu natywnego, bez środowiska uruchomieniowego i garbage collectora.
|
||||
Rekurencyjne `fib(40)` trwa ok. 0,19 s (`clang -O2` na tym samym kodzie C: ok. 0,23 s).
|
||||
- **Bezpieczniejszy niż C tam, gdzie to tanie.** Nie ma cichego zwężania typów ani mieszania liczb ze znakiem
|
||||
i bez znaku. Dzielenie przez zero i `.value` na null zatrzymują program czytelnym błędem, zamiast
|
||||
powodować niezdefiniowane zachowanie. Priorytety operatorów nie gryzą (`x & 1 == 0` znaczy to, co widać).
|
||||
- **Błędy mówią po ludzku:**
|
||||
```
|
||||
gra.fn:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
|
||||
3 | i32 y = x
|
||||
| ^
|
||||
```
|
||||
- **Każda biblioteka C, bezpośrednio.** Piszesz `import "GLFW/glfw3.h"` i `link "glfw"`, i możesz z niej korzystać.
|
||||
Nagłówki czyta libclang, więc nie trzeba niczego deklarować ręcznie.
|
||||
|
||||
## Szybki start
|
||||
|
||||
Linux x86-64, LLVM 21.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Nexoniarz/Finch.git
|
||||
cd Finch
|
||||
nix-shell # albo zainstaluj samodzielnie LLVM 21 + libclang + cmake + ninja
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build
|
||||
|
||||
./build/finch run examples/hello.fn
|
||||
./build/finch build examples/tour.fn -o tour && ./tour
|
||||
tests/run.sh # 34 passed, 0 failed
|
||||
```
|
||||
|
||||
## Dokumentacja
|
||||
|
||||
Wybierz przewodnik dla siebie:
|
||||
|
||||
| Przewodnik | Dla kogo | Polski | English |
|
||||
|---|---|---|---|
|
||||
| **Początkujący** | Nigdy nie programowałeś: uczniowie, seniorzy, ciekawscy | [dla-poczatkujacych.md](docs/pl/dla-poczatkujacych.md) | [for-beginners.md](docs/en/for-beginners.md) |
|
||||
| **Technicy** | Znasz komputery, programowanie jest nowe: instalacja, cały język, biblioteki C, problemy | [dla-technikow.md](docs/pl/dla-technikow.md) | [for-technicians.md](docs/en/for-technicians.md) |
|
||||
| **Inżynierowie** | Wszystko: lekser, gramatyka, AST, reguły typów, tłumaczenie na IR, import przez libclang, ABI, wnętrze | [dla-inzynierow.md](docs/pl/dla-inzynierow.md) | [for-engineers.md](docs/en/for-engineers.md) |
|
||||
|
||||
## Język w pigułce
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Zmienne | `x := 5` (typ zgadnięty) albo `int x = 5` |
|
||||
| Typy | `int` `float` `bool` `char` `str`, z rozmiarem `i8`…`i64` `u8`…`u64` `f32` `f64`, wskaźniki `ptr[T]` / `ptr` |
|
||||
| Funkcje | `fn add(int a, int b) -> int { return a + b }` |
|
||||
| Sterowanie | `if` / `else if` / `else`, `while`, `for i in 0..10`, `break`, `continue` |
|
||||
| Wskaźniki | `p := addr(x)`, `p.value = 5`, `null` |
|
||||
| Konwersje | `int(3.9)`, `u8(x)`, `float(n)` (niejawne tylko wtedy, gdy nic nie ginie) |
|
||||
| Operatory | `+ - * / %`, `& \| ^ << >> ~`, `== != < <= > >=`, `&& \|\| !` |
|
||||
| Współpraca z C | `import "stdio.h"`, `link "glfw"` |
|
||||
| Wypisywanie | `print(a, b, c)` wypisuje wszystko, oddzielając spacjami |
|
||||
|
||||
## Przykłady
|
||||
|
||||
| Plik | Pokazuje |
|
||||
|---|---|
|
||||
| [`examples/hello.fn`](examples/hello.fn) | najmniejszy program |
|
||||
| [`examples/tour.fn`](examples/tour.fn) | zmienne, funkcje, warunki, pętle |
|
||||
| [`examples/types.fn`](examples/types.fn) | liczby z rozmiarem i wskaźniki |
|
||||
| [`examples/c_import.fn`](examples/c_import.fn) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
|
||||
| [`examples/window.fn`](examples/window.fn) | okno GLFW + OpenGL |
|
||||
| [`examples/llvm.fn`](examples/llvm.fn) | Finch budujący LLVM IR przez API LLVM-C |
|
||||
|
||||
## Plan rozwoju
|
||||
|
||||
- [x] Lekser, parser, generowanie kodu LLVM, optymalizacja O2
|
||||
- [x] Typy, w tym liczby z rozmiarem, wskaźniki, konwersje; funkcje; `if`/`while`/`for`
|
||||
- [x] Czytelne błędy kompilacji; kontrole w czasie działania (dzielenie, null)
|
||||
- [x] Operatory bitowe z rozsądnymi priorytetami
|
||||
- [x] Nagłówki C przez libclang; `link` z pkg-config; porady przy błędach linkowania
|
||||
- [ ] `struct` (i struktury C przez wartość)
|
||||
- [ ] Tablice/wycinki z `.len` i kontrolą zakresu
|
||||
- [ ] Pamięć: automatyczne zwalnianie na końcu bloku, `defer`, ręczne `free` dla chętnych
|
||||
- [ ] Teksty: `+`, `len`, `str(x)`; `input()`
|
||||
- [ ] Moduły Fincha (`import math`)
|
||||
- [ ] Informacje dla debuggera (DWARF)
|
||||
- [ ] Bootstrap: kompilator Fincha napisany w Finchu
|
||||
|
||||
## Struktura
|
||||
|
||||
```
|
||||
src/ kompilator: lekser, parser, AST, codegen, import C, sterownik (C++17, ok. 2,4 tys. linii)
|
||||
examples/ przykładowe programy
|
||||
tests/ run/ (wzorcowe wyjście), fail/ (oczekiwane błędy), run.sh
|
||||
docs/ przewodniki en/ i pl/ dla trzech grup odbiorców
|
||||
```
|
||||
|
||||
## Licencja
|
||||
|
||||
[Apache License 2.0](LICENSE). Copyright 2026 Nexoniarz.
|
||||
650
docs/en/for-beginners.md
Normal file
650
docs/en/for-beginners.md
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
# Finch for Beginners
|
||||
|
||||
**Never written a program before? This guide is for you.**
|
||||
No experience needed, and no math beyond what you learned in primary school.
|
||||
Take it slowly, one section at a time, and type every example yourself: that is how it sticks.
|
||||
|
||||
> 🇵🇱 Polska wersja: [dla-poczatkujacych.md](../pl/dla-poczatkujacych.md)
|
||||
|
||||
---
|
||||
|
||||
## Contents
|
||||
|
||||
1. [What is a program?](#1-what-is-a-program)
|
||||
2. [Getting Finch ready](#2-getting-finch-ready)
|
||||
3. [Your first program](#3-your-first-program)
|
||||
4. [Showing things on the screen](#4-showing-things-on-the-screen)
|
||||
5. [Boxes with names: variables](#5-boxes-with-names-variables)
|
||||
6. [Kinds of values](#6-kinds-of-values)
|
||||
7. [Doing math](#7-doing-math)
|
||||
8. [Making decisions: if](#8-making-decisions-if)
|
||||
9. [Repeating things: loops](#9-repeating-things-loops)
|
||||
10. [Your own commands: functions](#10-your-own-commands-functions)
|
||||
11. [When something goes wrong](#11-when-something-goes-wrong)
|
||||
12. [Small projects to try](#12-small-projects-to-try)
|
||||
13. [Cheat sheet](#13-cheat-sheet)
|
||||
14. [Words you will hear](#14-words-you-will-hear)
|
||||
|
||||
---
|
||||
|
||||
## 1. What is a program?
|
||||
|
||||
A computer is very fast, but it does not think. It only does exactly what it is told,
|
||||
one step at a time.
|
||||
|
||||
A **program** is a list of those steps, written down. Like a recipe:
|
||||
|
||||
> 1. Take two eggs.
|
||||
> 2. Break them into a bowl.
|
||||
> 3. Stir.
|
||||
|
||||
A **programming language** is the way we write that recipe so the computer understands it.
|
||||
**Finch** is one of those languages. It was made to be small and simple, so you
|
||||
can learn it quickly.
|
||||
|
||||
The computer reads your program from top to bottom, line by line.
|
||||
|
||||
---
|
||||
|
||||
## 2. Getting Finch ready
|
||||
|
||||
Finch runs on **Linux** computers. If a teacher, parent or friend has already set it up
|
||||
for you, skip to the next section.
|
||||
|
||||
Otherwise, open the **Terminal** program (a window where you type commands) and type
|
||||
these lines one by one, pressing **Enter** after each:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Nexoniarz/Finch.git
|
||||
cd Finch
|
||||
nix-shell
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
If the last line finishes without the word `error`, Finch is ready.
|
||||
(If your computer does not have `nix-shell`, ask someone to follow the
|
||||
[guide for technicians](for-technicians.md#2-installing). It explains the other way.)
|
||||
|
||||
To check it works, type:
|
||||
|
||||
```sh
|
||||
./build/finch version
|
||||
```
|
||||
|
||||
You should see something like `finch 1.0.0`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Your first program
|
||||
|
||||
1. Open any text editor (for example *Text Editor*, *Kate*, *gedit* or *VS Code*).
|
||||
2. Type exactly this:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
print("Hello!")
|
||||
}
|
||||
```
|
||||
|
||||
3. Save it in the `Finch` folder as **`hello.fn`**. The `.fn` at the end tells everyone it is a Finch program.
|
||||
4. In the Terminal, type:
|
||||
|
||||
```sh
|
||||
./build/finch run hello.fn
|
||||
```
|
||||
|
||||
You will see:
|
||||
|
||||
```
|
||||
Hello!
|
||||
```
|
||||
|
||||
🎉 **You have just written and run a program.**
|
||||
|
||||
### What did we write?
|
||||
|
||||
```text
|
||||
fn main() { ← "here starts the main part of my program"
|
||||
print("Hello!") ← "show the text Hello! on the screen"
|
||||
} ← "the main part ends here"
|
||||
```
|
||||
|
||||
- `fn main()` is where every Finch program **starts**. Each program has exactly one `main`.
|
||||
- The curly brackets `{` and `}` hold everything that belongs to `main`, like the two covers of a book.
|
||||
- `print(...)` shows something on the screen.
|
||||
- Text always goes inside double quotes: `"Hello!"`.
|
||||
|
||||
The spaces at the start of `print` are not required, but they make it easy to see
|
||||
what is inside the brackets. Always add them. It is a good habit.
|
||||
|
||||
---
|
||||
|
||||
## 4. Showing things on the screen
|
||||
|
||||
`print` can show text and numbers. Put a comma between things, and Finch adds a space for you:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
print("I am", 10, "years old")
|
||||
print("2 + 2 =", 2 + 2)
|
||||
print()
|
||||
print("The line above is empty")
|
||||
}
|
||||
```
|
||||
|
||||
Shows:
|
||||
|
||||
```
|
||||
I am 10 years old
|
||||
2 + 2 = 4
|
||||
|
||||
The line above is empty
|
||||
```
|
||||
|
||||
Every `print` starts a new line. `print()` with nothing inside makes an empty line.
|
||||
|
||||
### Notes for yourself: comments
|
||||
|
||||
Anything after `//` is ignored by the computer. Use it to leave notes:
|
||||
|
||||
```c
|
||||
// This program says hi
|
||||
fn main() {
|
||||
print("Hi") // this shows Hi
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Boxes with names: variables
|
||||
|
||||
Imagine a box with a label on it. You can put something in the box and later look
|
||||
at what is inside by reading the label. In programming that box is called a **variable**.
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
age := 10
|
||||
print(age)
|
||||
}
|
||||
```
|
||||
|
||||
`age := 10` means: *"make a new box, label it `age`, and put 10 in it."*
|
||||
|
||||
You can change what is in the box later, using just `=`:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
score := 0
|
||||
print("Score:", score)
|
||||
|
||||
score = 5
|
||||
print("Score:", score)
|
||||
|
||||
score = score + 1
|
||||
print("Score:", score)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Score: 0
|
||||
Score: 5
|
||||
Score: 6
|
||||
```
|
||||
|
||||
`score = score + 1` reads as: *"take what is in `score`, add 1, and put the result back into `score`."*
|
||||
There is a shorter way to write the same thing: `score += 1`.
|
||||
|
||||
**Remember:**
|
||||
|
||||
- `:=` makes a **new** box. You use it only once per name.
|
||||
- `=` puts a new value into a box **that already exists**.
|
||||
|
||||
### Naming your boxes
|
||||
|
||||
Names can use the English letters `a`–`z` and `A`–`Z`, digits and `_`, but cannot start with a digit:
|
||||
`age`, `total_points`, `player2` are fine. `2player` is not.
|
||||
Letters like `é`, `ü` or `ż` cannot be used in names, only inside text in quotes: `print("Zürich")` is fine.
|
||||
Pick names that say what is inside: `price` is better than `p`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Kinds of values
|
||||
|
||||
Finch keeps track of what *kind* of thing is in each box. The kind is called a **type**.
|
||||
These are the ones you will use most:
|
||||
|
||||
| Type | What it holds | Examples |
|
||||
|---------|------------------------------|--------------------------|
|
||||
| `int` | whole numbers | `0`, `42`, `-7` |
|
||||
| `float` | numbers with a decimal point | `3.14`, `0.5`, `-2.0` |
|
||||
| `str` | text | `"Hello"`, `"Ola"` |
|
||||
| `bool` | yes or no | `true`, `false` |
|
||||
| `char` | a single letter or sign | `'A'`, `'z'`, `'?'` |
|
||||
|
||||
Finch figures out the type by itself when you use `:=`:
|
||||
|
||||
```c
|
||||
name := "Ola" // str
|
||||
age := 10 // int
|
||||
height := 1.42 // float
|
||||
likes_cats := true // bool
|
||||
```
|
||||
|
||||
You can also write the type yourself, in front of the name:
|
||||
|
||||
```c
|
||||
int age = 10
|
||||
str name = "Ola"
|
||||
```
|
||||
|
||||
Both ways do the same thing. Use the one you like.
|
||||
|
||||
**A box keeps its type.** If `age` holds whole numbers, you cannot put text in it later.
|
||||
Finch will stop and tell you, which helps you catch mistakes early.
|
||||
|
||||
---
|
||||
|
||||
## 7. Doing math
|
||||
|
||||
| Sign | Meaning | Example | Result |
|
||||
|------|----------------------------|----------|--------|
|
||||
| `+` | add | `7 + 2` | `9` |
|
||||
| `-` | subtract | `7 - 2` | `5` |
|
||||
| `*` | multiply | `7 * 2` | `14` |
|
||||
| `/` | divide | `7 / 2` | `3` |
|
||||
| `%` | what is left after dividing | `7 % 2` | `1` |
|
||||
|
||||
**Careful with dividing whole numbers:** `7 / 2` gives `3`, not `3.5`, because both are
|
||||
whole numbers, so the answer is a whole number too (the part after the dot is dropped).
|
||||
If you want `3.5`, use a decimal number: `7.0 / 2`.
|
||||
|
||||
Multiplication and division happen before adding and subtracting, just like at school.
|
||||
Use brackets to change the order:
|
||||
|
||||
```c
|
||||
print(2 + 3 * 4) // 14
|
||||
print((2 + 3) * 4) // 20
|
||||
```
|
||||
|
||||
`%` is handy to check if a number is even: an even number divided by 2 leaves nothing,
|
||||
so `n % 2` is `0`.
|
||||
|
||||
You **cannot divide by zero**. If your program tries, it stops and tells you which line did it.
|
||||
|
||||
---
|
||||
|
||||
## 8. Making decisions: if
|
||||
|
||||
Programs can choose what to do. `if` means *"only do this when something is true"*.
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
temperature := 25
|
||||
|
||||
if temperature > 20 {
|
||||
print("It's warm, wear a t-shirt")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add `else` for *"otherwise, do this"*:
|
||||
|
||||
```c
|
||||
if temperature > 20 {
|
||||
print("It's warm")
|
||||
} else {
|
||||
print("Take a jacket")
|
||||
}
|
||||
```
|
||||
|
||||
And `else if` for more choices. Finch checks them from top to bottom and does only
|
||||
the **first** one that is true:
|
||||
|
||||
```c
|
||||
if temperature > 25 {
|
||||
print("Hot!")
|
||||
} else if temperature > 15 {
|
||||
print("Nice")
|
||||
} else {
|
||||
print("Cold")
|
||||
}
|
||||
```
|
||||
|
||||
### Comparing things
|
||||
|
||||
| Sign | Means |
|
||||
|------|-----------------------------|
|
||||
| `==` | is equal to |
|
||||
| `!=` | is not equal to |
|
||||
| `<` | is smaller than |
|
||||
| `>` | is bigger than |
|
||||
| `<=` | is smaller than or equal to |
|
||||
| `>=` | is bigger than or equal to |
|
||||
|
||||
⚠️ **One `=` puts a value in a box. Two `==` ask "are these equal?"**
|
||||
|
||||
You can compare text too: `if name == "Ola" { ... }`.
|
||||
|
||||
### Joining questions
|
||||
|
||||
- `&&` means **and**: both must be true. `if age > 6 && age < 12 { ... }`
|
||||
- `||` means **or**: at least one must be true. `if day == "Sat" || day == "Sun" { ... }`
|
||||
- `!` means **not**: it flips true and false. `if !raining { ... }`
|
||||
|
||||
---
|
||||
|
||||
## 9. Repeating things: loops
|
||||
|
||||
Computers never get bored. A **loop** repeats the same steps many times.
|
||||
|
||||
### Counting with `for`
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for i in 1..6 {
|
||||
print("Number", i)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Number 1
|
||||
Number 2
|
||||
Number 3
|
||||
Number 4
|
||||
Number 5
|
||||
```
|
||||
|
||||
`for i in 1..6` means: *"count with `i` from 1, up to but **not including** 6"*.
|
||||
So `0..10` gives you ten numbers: 0, 1, 2 … 9.
|
||||
|
||||
### Repeating while something is true: `while`
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
count := 3
|
||||
while count > 0 {
|
||||
print(count)
|
||||
count -= 1
|
||||
}
|
||||
print("Go!")
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
3
|
||||
2
|
||||
1
|
||||
Go!
|
||||
```
|
||||
|
||||
⚠️ Make sure the loop can end. If `count` never went down, the loop would run forever.
|
||||
If that happens, press **Ctrl + C** in the Terminal to stop it.
|
||||
|
||||
### Stopping early and skipping
|
||||
|
||||
- `break` leaves the loop right away.
|
||||
- `continue` skips the rest of this round and goes to the next one.
|
||||
|
||||
```c
|
||||
for i in 1..100 {
|
||||
if i == 5 {
|
||||
break // stop when we reach 5
|
||||
}
|
||||
print(i) // prints 1 2 3 4
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Your own commands: functions
|
||||
|
||||
When you do the same thing in many places, give it a name. That named block is a **function**.
|
||||
You already know one: `main`.
|
||||
|
||||
```c
|
||||
fn say_hello(str name) {
|
||||
print("Hello,", name)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
say_hello("Ola")
|
||||
say_hello("Tom")
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Hello, Ola
|
||||
Hello, Tom
|
||||
```
|
||||
|
||||
`str name` in brackets means: *"this function needs one piece of text, and inside it
|
||||
we call that text `name`."* The thing you put in brackets when using it is passed in.
|
||||
|
||||
### Functions that give back an answer
|
||||
|
||||
Write `->` and the type of the answer, and use `return` to give it back:
|
||||
|
||||
```c
|
||||
fn add(int a, int b) -> int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
result := add(2, 3)
|
||||
print(result) // 5
|
||||
}
|
||||
```
|
||||
|
||||
It does not matter where in the file you put your functions, above or below `main`.
|
||||
|
||||
---
|
||||
|
||||
## 11. When something goes wrong
|
||||
|
||||
Everyone makes mistakes when programming, even experts, every day.
|
||||
Finch tries hard to explain what went wrong in plain words. Example:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
age := 10
|
||||
print(agee)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
hello.fn:3:11: error: there is no variable named 'agee'
|
||||
3 | print(agee)
|
||||
| ^
|
||||
```
|
||||
|
||||
How to read it:
|
||||
|
||||
- `hello.fn` is the file,
|
||||
- `3` is the **line** number,
|
||||
- `11` is how far into the line (the **column**),
|
||||
- then the explanation, and the line itself with a `^` pointing at the problem.
|
||||
|
||||
Here we simply misspelled `age`.
|
||||
|
||||
### Common mistakes
|
||||
|
||||
| Finch says | What it means | Fix |
|
||||
|---|---|---|
|
||||
| `there is no variable named 'x'` | The name is misspelled, or you never made that box | Check spelling. Create it with `x := ...` |
|
||||
| `a variable named 'x' already exists here` | You used `:=` twice for the same name | Use `=` the second time |
|
||||
| `'x' must be int, but this is str` | Wrong kind of value for that box | Put the right kind of value in |
|
||||
| `put each statement on its own line` | Two commands on one line | Press Enter between them |
|
||||
| `this '{' is never closed` | A `}` is missing | Add the `}` |
|
||||
| `a condition must be bool` | `if` needs a yes/no question | Compare something: `if x > 0` |
|
||||
| `runtime error: division by zero` | You divided by 0 while the program ran | Check the number before dividing |
|
||||
|
||||
---
|
||||
|
||||
## 12. Small projects to try
|
||||
|
||||
Try writing each one yourself first. Look at the solution only if you get stuck.
|
||||
|
||||
### Project 1: Times table
|
||||
|
||||
Show the 7 times table, from 7 × 1 to 7 × 10.
|
||||
|
||||
<details><summary>Solution</summary>
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for i in 1..11 {
|
||||
print("7 x", i, "=", 7 * i)
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Project 2: Even or odd
|
||||
|
||||
For every number from 1 to 10, show whether it is even or odd.
|
||||
|
||||
<details><summary>Solution</summary>
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for n in 1..11 {
|
||||
if n % 2 == 0 {
|
||||
print(n, "is even")
|
||||
} else {
|
||||
print(n, "is odd")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Project 3: Celsius to Fahrenheit
|
||||
|
||||
Show a table from 0 °C to 100 °C, every 10 degrees. Formula: `F = C * 9 / 5 + 32`.
|
||||
|
||||
<details><summary>Solution</summary>
|
||||
|
||||
```c
|
||||
fn to_fahrenheit(float c) -> float {
|
||||
return c * 9 / 5 + 32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for c in 0..11 {
|
||||
celsius := float(c * 10)
|
||||
print(celsius, "C =", to_fahrenheit(celsius), "F")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`float(...)` turns a whole number into a decimal number.
|
||||
</details>
|
||||
|
||||
### Project 4: Fizz Buzz
|
||||
|
||||
Count from 1 to 20. For numbers divisible by 3 say `Fizz`, by 5 say `Buzz`,
|
||||
by both say `FizzBuzz`, otherwise say the number.
|
||||
|
||||
<details><summary>Solution</summary>
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for n in 1..21 {
|
||||
if n % 15 == 0 {
|
||||
print("FizzBuzz")
|
||||
} else if n % 3 == 0 {
|
||||
print("Fizz")
|
||||
} else if n % 5 == 0 {
|
||||
print("Buzz")
|
||||
} else {
|
||||
print(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Project 5: Biggest of three
|
||||
|
||||
Write a function `biggest(int a, int b, int c) -> int` that gives back the largest number.
|
||||
|
||||
<details><summary>Solution</summary>
|
||||
|
||||
```c
|
||||
fn biggest(int a, int b, int c) -> int {
|
||||
if a >= b && a >= c {
|
||||
return a
|
||||
}
|
||||
if b >= c {
|
||||
return b
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print(biggest(4, 9, 2)) // 9
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 13. Cheat sheet
|
||||
|
||||
```c
|
||||
// a comment
|
||||
|
||||
fn main() { // the program starts here
|
||||
print("text", 42) // show things
|
||||
|
||||
x := 5 // new box (type guessed)
|
||||
int y = 10 // new box (type written)
|
||||
x = 7 // change a box
|
||||
x += 1 // add 1 to x
|
||||
|
||||
if x > 5 { // decide
|
||||
print("big")
|
||||
} else if x > 0 {
|
||||
print("small")
|
||||
} else {
|
||||
print("zero or less")
|
||||
}
|
||||
|
||||
for i in 0..10 { // count 0 to 9
|
||||
print(i)
|
||||
}
|
||||
|
||||
while x > 0 { // repeat while true
|
||||
x -= 1
|
||||
}
|
||||
}
|
||||
|
||||
fn add(int a, int b) -> int { // your own command
|
||||
return a + b
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Words you will hear
|
||||
|
||||
| Word | Meaning |
|
||||
|---|---|
|
||||
| **program** | A list of steps for the computer |
|
||||
| **code** | The text of a program |
|
||||
| **run** | Make the computer do the steps |
|
||||
| **compile** | Translate your code into something the computer can run. `finch run` does this for you |
|
||||
| **variable** | A named box holding a value |
|
||||
| **type** | The kind of value: number, text, yes/no… |
|
||||
| **function** | A named group of steps you can use again |
|
||||
| **loop** | Steps that repeat |
|
||||
| **condition** | A yes/no question, like `x > 5` |
|
||||
| **error** | A message saying something is wrong, and where |
|
||||
| **bug** | A mistake in a program |
|
||||
|
||||
---
|
||||
|
||||
**What next?** When this all feels easy, move on to the
|
||||
[guide for technicians](for-technicians.md). It covers every part of Finch.
|
||||
1131
docs/en/for-engineers.md
Normal file
1131
docs/en/for-engineers.md
Normal file
File diff suppressed because it is too large
Load diff
541
docs/en/for-technicians.md
Normal file
541
docs/en/for-technicians.md
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
# Finch for Technicians
|
||||
|
||||
**For people who know their way around a computer, but little or no programming.**
|
||||
This guide covers installing Finch, using the `finch` tool, the whole language, using
|
||||
C libraries, and fixing common problems. It does not cover how the compiler works inside.
|
||||
For that, see the [guide for engineers](for-engineers.md).
|
||||
|
||||
> 🇵🇱 Polska wersja: [dla-technikow.md](../pl/dla-technikow.md)
|
||||
|
||||
---
|
||||
|
||||
## Contents
|
||||
|
||||
1. [What Finch is](#1-what-finch-is)
|
||||
2. [Installing](#2-installing)
|
||||
3. [The finch command](#3-the-finch-command)
|
||||
4. [How a Finch file looks](#4-how-a-finch-file-looks)
|
||||
5. [Variables](#5-variables)
|
||||
6. [Types](#6-types)
|
||||
7. [Operators](#7-operators)
|
||||
8. [Control flow](#8-control-flow)
|
||||
9. [Functions](#9-functions)
|
||||
10. [Pointers](#10-pointers)
|
||||
11. [Using C libraries](#11-using-c-libraries)
|
||||
12. [Errors](#12-errors)
|
||||
13. [Troubleshooting](#13-troubleshooting)
|
||||
14. [Project layout and tests](#14-project-layout-and-tests)
|
||||
15. [Current limits](#15-current-limits)
|
||||
|
||||
---
|
||||
|
||||
## 1. What Finch is
|
||||
|
||||
Finch is a **compiled** programming language. The `finch` tool translates your `.fn`
|
||||
file into a real program (a native executable, like the ones made from C), using the
|
||||
**LLVM** compiler toolkit, the same backend that powers clang, Rust and Swift.
|
||||
|
||||
What that gives you:
|
||||
|
||||
- **Speed close to C.** There is no interpreter, virtual machine or garbage collector.
|
||||
- **Small, standalone programs** that you can copy and run.
|
||||
- **Direct access to C libraries.** `import "stdio.h"` and call anything in it.
|
||||
|
||||
The design rules: one way to do each thing, C-like syntax without C's traps, and error
|
||||
messages that tell you how to fix the problem.
|
||||
|
||||
---
|
||||
|
||||
## 2. Installing
|
||||
|
||||
Finch currently runs on **Linux, x86-64**. It has been tested with **LLVM 21**.
|
||||
Other LLVM versions will likely fail to compile, because LLVM's C++ API changes between releases.
|
||||
|
||||
### Option A: Nix (recommended, nothing to install by hand)
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Nexoniarz/Finch.git
|
||||
cd Finch
|
||||
nix-shell # downloads LLVM, libclang, cmake, ninja, pkg-config
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build
|
||||
./build/finch version # finch 1.0.0 (LLVM 21.x)
|
||||
```
|
||||
|
||||
### Option B: your distribution's packages
|
||||
|
||||
You need: a C/C++ compiler, CMake ≥ 3.20, Ninja (or Make), LLVM 21 development files,
|
||||
libclang 21 development files, and optionally `pkg-config`.
|
||||
|
||||
| Distribution | Packages (names can differ slightly by version) |
|
||||
|---|---|
|
||||
| Debian / Ubuntu | `build-essential cmake ninja-build pkg-config llvm-21-dev libclang-21-dev` |
|
||||
| Fedora | `gcc-c++ cmake ninja-build pkgconf llvm-devel clang-devel` |
|
||||
| Arch | `base-devel cmake ninja pkgconf llvm clang` |
|
||||
|
||||
If CMake cannot find LLVM, point it there:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
### Putting `finch` on your PATH (optional)
|
||||
|
||||
```sh
|
||||
sudo cp build/finch /usr/local/bin/
|
||||
finch version
|
||||
```
|
||||
|
||||
Finch needs a working C compiler (`cc`) at run time. It uses it as the **linker** that
|
||||
turns compiled code into a program. Set the `CC` environment variable to use a different one.
|
||||
|
||||
---
|
||||
|
||||
## 3. The finch command
|
||||
|
||||
```
|
||||
finch run <file.fn> compile and run right away
|
||||
finch build <file.fn> [-o name] compile into a program (default name: the file's name)
|
||||
finch ir <file.fn> print the LLVM IR (for the curious)
|
||||
finch version show the version
|
||||
|
||||
options:
|
||||
-l <lib> link a C library, same as link "lib" in the file
|
||||
-O0 skip optimizations
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
finch run hello.fn # prints the output
|
||||
finch build game.fn -o mygame # creates ./mygame
|
||||
./mygame
|
||||
finch run window.fn -l glfw # with a C library
|
||||
```
|
||||
|
||||
`finch run` passes on your program's exit code. If the program crashes, it tells you
|
||||
how (for example `the program crashed: Segmentation fault`).
|
||||
|
||||
---
|
||||
|
||||
## 4. How a Finch file looks
|
||||
|
||||
```c
|
||||
// comments start with two slashes
|
||||
/* or span
|
||||
several lines */
|
||||
|
||||
import "math.h" // C headers (optional), always at the top
|
||||
link "m" // C libraries to link (optional)
|
||||
|
||||
fn main() { // the program starts here
|
||||
print("Hello")
|
||||
}
|
||||
```
|
||||
|
||||
Rules worth knowing:
|
||||
|
||||
- **One statement per line.** There are no semicolons. To split a long expression,
|
||||
break the line **after** an operator (`a +⏎ b`) or anywhere inside brackets `( )`.
|
||||
- **Curly braces are always required** after `if`, `else`, `for`, `while`, and the opening
|
||||
`{` goes on the same line.
|
||||
- **No brackets around conditions:** `if x > 5 {`, not `if (x > 5) {`.
|
||||
- Functions can be in any order in the file.
|
||||
- `main` is either `fn main()` or `fn main() -> int` (returns the exit code).
|
||||
|
||||
---
|
||||
|
||||
## 5. Variables
|
||||
|
||||
```c
|
||||
x := 5 // new variable, type inferred (int)
|
||||
int y = 10 // new variable, type written explicitly
|
||||
int z // no value: starts at zero ("" for str, null for pointers)
|
||||
|
||||
x = 7 // assign
|
||||
x += 1 // also -= *= /= %=
|
||||
```
|
||||
|
||||
- A name can be declared **once** in a function. Finch does not allow reusing a name in an
|
||||
inner block (no "shadowing"), because that is a common source of confusion.
|
||||
- Variables exist from their declaration to the end of the `{ }` block they are in.
|
||||
- Names: ASCII letters, digits and `_`, not starting with a digit. Non-ASCII letters are allowed only inside strings. Type names (`int`, `u8`, …) and
|
||||
keywords are reserved.
|
||||
|
||||
**Keywords:** `fn return if else while for in break continue true false null import link`
|
||||
|
||||
---
|
||||
|
||||
## 6. Types
|
||||
|
||||
### Everyday types
|
||||
|
||||
| Type | Holds | Example |
|
||||
|---------|-------|---------|
|
||||
| `int` | whole number, 64-bit (same as `i64`) | `42`, `-7`, `0xFF`, `1_000_000` |
|
||||
| `float` | decimal number, 64-bit (same as `f64`) | `3.14` |
|
||||
| `bool` | `true` or `false` | |
|
||||
| `char` | one byte-sized character | `'A'`, `'\n'` |
|
||||
| `str` | text (a C string) | `"hello\tworld"` |
|
||||
|
||||
String and char escapes: `\n` new line, `\t` tab, `\r`, `\0`, `\\`, `\"`, `\'`.
|
||||
|
||||
### Sized types (for files, hardware, C libraries)
|
||||
|
||||
| Signed | Range | Unsigned | Range |
|
||||
|---|---|---|---|
|
||||
| `i8` | −128 … 127 | `u8` | 0 … 255 |
|
||||
| `i16` | −32 768 … 32 767 | `u16` | 0 … 65 535 |
|
||||
| `i32` | about ±2.1 billion | `u32` | 0 … about 4.29 billion |
|
||||
| `i64` | about ±9.2 × 10¹⁸ | `u64` | 0 … about 1.8 × 10¹⁹ |
|
||||
|
||||
`f32` is a 32-bit decimal number (about 7 significant digits), `f64` a 64-bit one (about 15–16).
|
||||
|
||||
### Pointers
|
||||
|
||||
`ptr[T]` points to a value of type `T`. Plain `ptr` points to "something" with unknown
|
||||
type, like `void*` in C. See [Pointers](#10-pointers).
|
||||
|
||||
### Conversions
|
||||
|
||||
Finch converts automatically **only when nothing can be lost**:
|
||||
|
||||
| From | To | Automatic? |
|
||||
|---|---|---|
|
||||
| a smaller whole number | a bigger one of the same signedness (`i8`→`i32`, `u8`→`u64`) | ✅ |
|
||||
| unsigned | a strictly bigger signed (`u8`→`i16`, `u32`→`i64`) | ✅ |
|
||||
| any whole number | `float` / `f32` | ✅ |
|
||||
| `f32` | `f64` | ✅ |
|
||||
| `null` | any pointer | ✅ |
|
||||
| any pointer, `str` | `ptr` | ✅ |
|
||||
| `ptr` | any `ptr[T]` | ✅ |
|
||||
| anything else | | ❌ write it: `type(value)` |
|
||||
|
||||
**Number literals adapt.** A number written in the code fits into any type that can hold it:
|
||||
`u8 b = 200` works, `u8 b = 300` is an error (`the number 300 doesn't fit in u8`).
|
||||
The same applies to constants imported from C headers.
|
||||
|
||||
**Explicit conversions** use the type name like a function:
|
||||
|
||||
```c
|
||||
int(3.99) // 3 (cuts off the fraction)
|
||||
int(-3.99) // -3
|
||||
float(7) / 2 // 3.5
|
||||
u8(300) // 44 (keeps the lowest 8 bits)
|
||||
i8(200) // -56
|
||||
int('A') // 65
|
||||
char(66) // 'B'
|
||||
bool(0) // false (any non-zero number is true)
|
||||
ptr(text) // a str as an untyped pointer
|
||||
str(p) // a pointer as a str
|
||||
```
|
||||
|
||||
**Fixed-size numbers wrap around**, like real hardware: a `u8` holding 255 plus 1 becomes 0.
|
||||
Calculations happen in the type of the values involved, so `u8(250) + 10` is `4`, not `260`.
|
||||
Convert first if you need more room: `int(x) + 10`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Operators
|
||||
|
||||
### By priority (higher binds tighter)
|
||||
|
||||
| Priority | Operators | Meaning |
|
||||
|---|---|---|
|
||||
| 5 | `*` `/` `%` `<<` `>>` `&` | multiply, divide, remainder, shift left/right, bitwise AND |
|
||||
| 4 | `+` `-` `\|` `^` | add, subtract, bitwise OR, bitwise XOR |
|
||||
| 3 | `==` `!=` `<` `<=` `>` `>=` | comparisons |
|
||||
| 2 | `&&` | logical AND |
|
||||
| 1 | `\|\|` | logical OR |
|
||||
|
||||
Unary (in front of a value): `-x` negate, `!x` logical NOT, `~x` flip all bits.
|
||||
|
||||
> **Different from C, on purpose:** bit operators bind tighter than comparisons, so
|
||||
> `flags & 4 != 0` means `(flags & 4) != 0`. In C it silently means something else.
|
||||
|
||||
Notes:
|
||||
|
||||
- `/` on whole numbers rounds toward zero: `7 / 2` is `3`, `-7 / 2` is `-3`. `%` takes the sign of the left side.
|
||||
- Dividing by zero **stops the program** with `runtime error: division by zero` and the line number.
|
||||
If the zero is written in the code, it is a compile error instead.
|
||||
- `&&` and `||` only evaluate the right side if needed.
|
||||
- Text (`str`) can be compared with `==` and `!=` (it compares the contents). `<` and `>` do not work on text.
|
||||
- Pointers can be compared with `==` and `!=`, including against `null`.
|
||||
- Bit operators need whole numbers. For `bool` use `&&` and `||`.
|
||||
- `>>` on signed numbers keeps the sign (`-16 >> 2` is `-4`). On unsigned numbers it fills with zeros.
|
||||
|
||||
---
|
||||
|
||||
## 8. Control flow
|
||||
|
||||
```c
|
||||
if x > 10 {
|
||||
...
|
||||
} else if x > 0 {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
|
||||
while count > 0 {
|
||||
count -= 1
|
||||
}
|
||||
|
||||
for i in 0..10 { // i = 0, 1, …, 9 (the end is not included)
|
||||
if i == 3 { continue }
|
||||
if i == 8 { break }
|
||||
}
|
||||
```
|
||||
|
||||
- A condition must be a `bool`. `if x {` with a number is an error; write `if x != 0 {`.
|
||||
- In `for i in a..b`, both ends are whole numbers evaluated **once** before the loop starts.
|
||||
`i` is an `int` and cannot be changed inside the loop.
|
||||
- `while true { ... }` loops until a `break` or `return`.
|
||||
- Code after `return`, `break` or `continue` in the same block is an error, because it can never run.
|
||||
|
||||
---
|
||||
|
||||
## 9. Functions
|
||||
|
||||
```c
|
||||
fn name(type1 param1, type2 param2) -> return_type {
|
||||
...
|
||||
return value
|
||||
}
|
||||
|
||||
fn no_result(str message) { // no "->": returns nothing
|
||||
print(message)
|
||||
}
|
||||
```
|
||||
|
||||
- Parameters are copies. Changing a parameter does not change the caller's variable.
|
||||
Use a pointer for that (see below).
|
||||
- A function with `-> type` must return a value on every path. Finch checks this.
|
||||
- A function cannot be defined inside another function.
|
||||
- Built-in names you cannot reuse: `print`, `addr`, and all type names.
|
||||
|
||||
### Built-in functions
|
||||
|
||||
| Function | What it does |
|
||||
|---|---|
|
||||
| `print(a, b, ...)` | Prints all values separated by spaces, then a new line. Works with every type. |
|
||||
| `addr(x)` | Gives a pointer to variable `x`. |
|
||||
| `int(x)`, `u8(x)`, `float(x)`, … | Converts types (see [Conversions](#conversions)). |
|
||||
|
||||
---
|
||||
|
||||
## 10. Pointers
|
||||
|
||||
A pointer holds the **address** of a value: where it lives in memory. Finch uses words
|
||||
instead of C's `*` and `&`:
|
||||
|
||||
| C | Finch | Meaning |
|
||||
|---|---|---|
|
||||
| `int *p` | `ptr[int] p` | p points to an int |
|
||||
| `&x` | `addr(x)` | the address of x |
|
||||
| `*p` | `p.value` | the value p points to |
|
||||
| `NULL` | `null` | points nowhere |
|
||||
| `void *` | `ptr` | points to something of unknown type |
|
||||
|
||||
```c
|
||||
fn double(ptr[int] p) {
|
||||
p.value = p.value * 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := 21
|
||||
double(addr(x))
|
||||
print(x) // 42
|
||||
|
||||
ptr[int] nothing = null
|
||||
if nothing == null {
|
||||
print("empty")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pointers can point to pointers: `ptr[ptr[int]]`, then `pp.value.value`.
|
||||
|
||||
Using `.value` on a `null` pointer **stops the program** with
|
||||
`runtime error: used .value on a null pointer` instead of crashing randomly.
|
||||
|
||||
⚠️ A pointer to a variable must not be used after the function that owns the variable
|
||||
has returned. The variable no longer exists then. Finch does not check this yet.
|
||||
|
||||
---
|
||||
|
||||
## 11. Using C libraries
|
||||
|
||||
Almost every library on Linux has a C interface: graphics, sound, networking, databases.
|
||||
Finch can use them directly.
|
||||
|
||||
### Step 1: import the header
|
||||
|
||||
```c
|
||||
import "stdio.h"
|
||||
import "math.h"
|
||||
import "GLFW/glfw3.h" // a path inside the system include folders
|
||||
import "mylib.h" // a header next to your .fn file
|
||||
```
|
||||
|
||||
Finch reads the header with **libclang** (clang's C parser) and makes these usable:
|
||||
|
||||
- **functions**, including ones with a variable number of arguments, like `printf`,
|
||||
- **`enum` values**,
|
||||
- **simple `#define` constants** that are a single number, like `M_PI`, `EOF`, `GL_COLOR_BUFFER_BIT`,
|
||||
- **global variables**, like `stdout` and `stderr`.
|
||||
|
||||
### Step 2: link the library
|
||||
|
||||
A header only *describes* functions. Their actual code is in a library file (`libNAME.so`).
|
||||
Tell Finch which library to use, at the top of the file:
|
||||
|
||||
```c
|
||||
link "glfw" // uses libglfw.so
|
||||
link "GL" // uses libGL.so
|
||||
```
|
||||
|
||||
Write just the name: for `libglfw.so` that is `glfw`. If `pkg-config` knows the name, Finch uses
|
||||
its settings (paths and extra libraries). Otherwise it passes `-lNAME` to the linker.
|
||||
The C standard library and the math library (`libm`) are always linked.
|
||||
|
||||
On the command line, `-l NAME` does the same as `link "NAME"`.
|
||||
|
||||
If you forget the `link` line, Finch tells you exactly what is missing:
|
||||
|
||||
```
|
||||
error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "GLFW/glfw3.h", but its library isn't linked
|
||||
the header only says the functions exist; their code lives in a library.
|
||||
add this at the top of your file (with the library's real name):
|
||||
|
||||
link "glfw"
|
||||
```
|
||||
|
||||
### How C types appear in Finch
|
||||
|
||||
| C | Finch |
|
||||
|---|---|
|
||||
| `char`, `signed char`, `unsigned char` | `char`, `i8`, `u8` |
|
||||
| `short`, `int`, `long`, `long long` | `i16`, `i32`, `i64`, `i64` (by actual size) |
|
||||
| `unsigned …` | `u16`, `u32`, `u64` |
|
||||
| `float`, `double` | `f32`, `f64` |
|
||||
| `_Bool` / `bool` | `bool` |
|
||||
| `enum` | the matching signed integer |
|
||||
| `char *`, `const char *` | `str` |
|
||||
| `int *`, `double *`, … | `ptr[i32]`, `ptr[f64]`, … |
|
||||
| `void *`, `struct X *`, `FILE *`, function pointers | `ptr` |
|
||||
|
||||
So C's `int` is Finch's `i32`. Literals adapt automatically (`abs(-5)` works),
|
||||
but a Finch `int` variable must be converted: `abs(i32(x))`.
|
||||
|
||||
### Example
|
||||
|
||||
```c
|
||||
import "stdio.h"
|
||||
import "math.h"
|
||||
import "stdlib.h"
|
||||
|
||||
fn main() {
|
||||
printf("%d + %d = %d\n", 2, 3, 5)
|
||||
print(sqrt(2), M_PI, RAND_MAX)
|
||||
|
||||
ptr[i32] n = malloc(4) // ask C for 4 bytes of memory
|
||||
n.value = 7
|
||||
print(n.value)
|
||||
free(n) // and give them back
|
||||
|
||||
fprintf(stderr, "this goes to the error output\n")
|
||||
}
|
||||
```
|
||||
|
||||
A complete graphics example is in `examples/window.fn`: a GLFW + OpenGL window that changes color.
|
||||
`examples/llvm.fn` shows that Finch can even drive LLVM itself through its C API.
|
||||
|
||||
### What does not work yet
|
||||
|
||||
- C functions that take or return a **`struct` by value**, such as `div()`. Finch says so
|
||||
clearly if you try. Functions taking a *pointer* to a struct are fine.
|
||||
- **Function-like macros** (`#define MAX(a,b) ...`) and macros that are not a single number.
|
||||
- `long double`, 128-bit integers.
|
||||
- Reading the **fields** of a C struct.
|
||||
|
||||
---
|
||||
|
||||
## 12. Errors
|
||||
|
||||
### Compile errors
|
||||
|
||||
Finch stops at the first problem and shows where it is:
|
||||
|
||||
```
|
||||
game.fn:12:9: error: 'score' must be int, but this is str
|
||||
12 | score = "high"
|
||||
| ^
|
||||
```
|
||||
|
||||
Format: `file:line:column: error: explanation`, then the line with a `^` under the spot.
|
||||
|
||||
### Runtime errors
|
||||
|
||||
Some problems can only be seen while the program runs. Instead of undefined behavior
|
||||
(as in C), Finch stops with a clear message on the error output and exit code 1:
|
||||
|
||||
| Message | Cause |
|
||||
|---|---|
|
||||
| `runtime error: division by zero` | `/` or `%` by a variable that was 0 |
|
||||
| `runtime error: division overflows …` | the smallest signed number divided by −1 |
|
||||
| `runtime error: used .value on a null pointer` | reading or writing `p.value` while `p` is `null` |
|
||||
|
||||
---
|
||||
|
||||
## 13. Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---|---|
|
||||
| `can't find the C header 'x.h'` | The library's development files are missing. Install them (on Debian: `libx-dev`; on Nix: add to `shell.nix`). For your own header, put it next to the `.fn` file. |
|
||||
| `… come from "x.h", but its library isn't linked` | Add `link "name"` at the top of the file. |
|
||||
| `the library 'x' wasn't found` | The library is not installed, or the name is wrong. On Nix, add it to `shell.nix` and run inside `nix-shell`. |
|
||||
| `the C function 'f' can't be used from Finch yet` | It uses a struct by value, or another unsupported type. Look for a variant that takes pointers. |
|
||||
| `no C compiler found to link with` | Install gcc or clang, or set `CC`. |
|
||||
| The program runs, but a C library is not found at start | It was linked from a path the system does not search. Run it inside the same `nix-shell`, or install the library system-wide. |
|
||||
| CMake: `libclang not found` | Install the libclang dev package, or run `cmake` inside `nix-shell`. |
|
||||
| Build fails with LLVM API errors | You have a different LLVM version than 21. |
|
||||
| A program never ends | A loop condition never becomes false. Press Ctrl+C. |
|
||||
|
||||
---
|
||||
|
||||
## 14. Project layout and tests
|
||||
|
||||
```
|
||||
Finch/
|
||||
├── src/ the compiler (C++)
|
||||
├── examples/ example programs (hello, tour, types, c_import, window, llvm)
|
||||
├── tests/
|
||||
│ ├── run/ programs + the exact output they must print (.out)
|
||||
│ ├── fail/ programs that must fail, with the expected error in line 1
|
||||
│ └── run.sh the test runner
|
||||
├── docs/ this documentation (en, pl)
|
||||
├── shell.nix the Nix development environment
|
||||
└── CMakeLists.txt
|
||||
```
|
||||
|
||||
Run the tests after building:
|
||||
|
||||
```sh
|
||||
tests/run.sh # → 34 passed, 0 failed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. Current limits
|
||||
|
||||
Finch 1.0 is a solid core, not a finished language. Not there yet:
|
||||
|
||||
- `struct`, arrays, reading keyboard input (`input()`), string building (`"a" + "b"`).
|
||||
For now, use C functions for these (`scanf`, `snprintf`, `malloc`…).
|
||||
- Automatic memory management (planned: memory freed at the end of the block, plus `defer`).
|
||||
- Finch modules (one program = one `.fn` file for now).
|
||||
- Platforms other than Linux x86-64.
|
||||
|
||||
See the [roadmap in the README](../../README.md#roadmap).
|
||||
1136
docs/pl/dla-inzynierow.md
Normal file
1136
docs/pl/dla-inzynierow.md
Normal file
File diff suppressed because it is too large
Load diff
670
docs/pl/dla-poczatkujacych.md
Normal file
670
docs/pl/dla-poczatkujacych.md
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
# Finch dla początkujących
|
||||
|
||||
**Ten przewodnik jest dla ciebie, jeśli jeszcze nigdy nie pisałeś programu.**
|
||||
Nie potrzebujesz żadnego doświadczenia ani matematyki ponad to, czego uczy się w podstawówce.
|
||||
Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. Tak zostaje w głowie najlepiej.
|
||||
|
||||
> 🇬🇧 English version: [for-beginners.md](../en/for-beginners.md)
|
||||
|
||||
---
|
||||
|
||||
## Spis treści
|
||||
|
||||
1. [Co to jest program?](#1-co-to-jest-program)
|
||||
2. [Przygotowanie Fincha](#2-przygotowanie-fincha)
|
||||
3. [Twój pierwszy program](#3-twój-pierwszy-program)
|
||||
4. [Wypisywanie na ekran](#4-wypisywanie-na-ekran)
|
||||
5. [Pudełka z nazwami: zmienne](#5-pudełka-z-nazwami-zmienne)
|
||||
6. [Rodzaje wartości](#6-rodzaje-wartości)
|
||||
7. [Liczenie](#7-liczenie)
|
||||
8. [Podejmowanie decyzji: if](#8-podejmowanie-decyzji-if)
|
||||
9. [Powtarzanie: pętle](#9-powtarzanie-pętle)
|
||||
10. [Własne polecenia: funkcje](#10-własne-polecenia-funkcje)
|
||||
11. [Kiedy coś pójdzie nie tak](#11-kiedy-coś-pójdzie-nie-tak)
|
||||
12. [Małe projekty do zrobienia](#12-małe-projekty-do-zrobienia)
|
||||
13. [Ściąga](#13-ściąga)
|
||||
14. [Słowniczek](#14-słowniczek)
|
||||
|
||||
---
|
||||
|
||||
## 1. Co to jest program?
|
||||
|
||||
Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu się każe,
|
||||
krok po kroku.
|
||||
|
||||
**Program** to spisana lista takich kroków. Trochę jak przepis kuchenny:
|
||||
|
||||
> 1. Weź dwa jajka.
|
||||
> 2. Wbij je do miski.
|
||||
> 3. Wymieszaj.
|
||||
|
||||
**Język programowania** to sposób zapisania takiego przepisu, który komputer rozumie.
|
||||
**Finch** (po polsku: zięba) jest jednym z takich języków. Zrobiono go tak, żeby był
|
||||
mały i prosty, więc nauczysz się go szybko.
|
||||
|
||||
Komputer czyta program od góry do dołu, linijka po linijce.
|
||||
|
||||
---
|
||||
|
||||
## 2. Przygotowanie Fincha
|
||||
|
||||
Finch działa na komputerach z **Linuksem**. Jeśli nauczyciel, rodzic albo znajomy już go
|
||||
dla ciebie zainstalował, przejdź od razu do następnego rozdziału.
|
||||
|
||||
W przeciwnym razie otwórz program **Terminal** (okienko, w którym wpisuje się polecenia)
|
||||
i wpisz te linijki po kolei, naciskając **Enter** po każdej:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Nexoniarz/Finch.git
|
||||
cd Finch
|
||||
nix-shell
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy.
|
||||
(Jeśli komputer nie zna polecenia `nix-shell`, poproś kogoś o skorzystanie z
|
||||
[przewodnika dla techników](dla-technikow.md#2-instalacja), gdzie opisano inny sposób.)
|
||||
|
||||
Żeby sprawdzić, czy działa, wpisz:
|
||||
|
||||
```sh
|
||||
./build/finch version
|
||||
```
|
||||
|
||||
Powinieneś zobaczyć coś w stylu `finch 1.0.0`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Twój pierwszy program
|
||||
|
||||
1. Otwórz dowolny edytor tekstu (na przykład *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*).
|
||||
2. Wpisz dokładnie to:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
print("Cześć!")
|
||||
}
|
||||
```
|
||||
|
||||
3. Zapisz plik w folderze `Finch` pod nazwą **`czesc.fn`**. Końcówka `.fn` mówi, że to program w Finchu.
|
||||
4. W Terminalu wpisz:
|
||||
|
||||
```sh
|
||||
./build/finch run czesc.fn
|
||||
```
|
||||
|
||||
Zobaczysz:
|
||||
|
||||
```
|
||||
Cześć!
|
||||
```
|
||||
|
||||
🎉 **Właśnie napisałeś i uruchomiłeś program.**
|
||||
|
||||
### Co właściwie napisaliśmy?
|
||||
|
||||
```text
|
||||
fn main() { ← „tu zaczyna się główna część mojego programu”
|
||||
print("Cześć!") ← „pokaż na ekranie tekst Cześć!”
|
||||
} ← „tu główna część się kończy”
|
||||
```
|
||||
|
||||
- `fn main()` to miejsce, od którego **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`.
|
||||
- Nawiasy klamrowe `{` i `}` obejmują wszystko, co należy do `main`, jak dwie okładki książki.
|
||||
- `print(...)` pokazuje coś na ekranie.
|
||||
- Tekst zawsze piszemy w cudzysłowie: `"Cześć!"`.
|
||||
|
||||
Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku
|
||||
klamer. Zawsze je dodawaj, to dobry nawyk.
|
||||
|
||||
---
|
||||
|
||||
## 4. Wypisywanie na ekran
|
||||
|
||||
`print` umie pokazać tekst i liczby. Między kolejne rzeczy wstaw przecinek, a Finch sam doda spację:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
print("Mam", 10, "lat")
|
||||
print("2 + 2 =", 2 + 2)
|
||||
print()
|
||||
print("Linijka wyżej jest pusta")
|
||||
}
|
||||
```
|
||||
|
||||
Pokaże:
|
||||
|
||||
```
|
||||
Mam 10 lat
|
||||
2 + 2 = 4
|
||||
|
||||
Linijka wyżej jest pusta
|
||||
```
|
||||
|
||||
Każdy `print` zaczyna nową linijkę. `print()` bez niczego w środku robi pustą linijkę.
|
||||
|
||||
### Notatki dla siebie: komentarze
|
||||
|
||||
Wszystko po `//` komputer pomija. Możesz tam zostawiać sobie notatki:
|
||||
|
||||
```c
|
||||
// Ten program się wita
|
||||
fn main() {
|
||||
print("Cześć") // to pokazuje Cześć
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Pudełka z nazwami: zmienne
|
||||
|
||||
Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a potem sprawdzić,
|
||||
co jest w środku, czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**.
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
wiek := 10
|
||||
print(wiek)
|
||||
}
|
||||
```
|
||||
|
||||
`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”*.
|
||||
|
||||
Później możesz zmienić zawartość pudełka, używając samego `=`:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
punkty := 0
|
||||
print("Punkty:", punkty)
|
||||
|
||||
punkty = 5
|
||||
print("Punkty:", punkty)
|
||||
|
||||
punkty = punkty + 1
|
||||
print("Punkty:", punkty)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Punkty: 0
|
||||
Punkty: 5
|
||||
Punkty: 6
|
||||
```
|
||||
|
||||
`punkty = punkty + 1` czytamy tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”*.
|
||||
To samo można zapisać krócej: `punkty += 1`.
|
||||
|
||||
**Zapamiętaj:**
|
||||
|
||||
- `:=` robi **nowe** pudełko. Używasz tego tylko raz dla danej nazwy.
|
||||
- `=` wkłada nową wartość do pudełka, **które już istnieje**.
|
||||
|
||||
### Nazywanie pudełek
|
||||
|
||||
Nazwy mogą zawierać angielskie litery `a`–`z` i `A`–`Z`, cyfry i znak `_`, ale nie mogą zaczynać się od cyfry:
|
||||
`wiek`, `suma_punktow`, `gracz2` są w porządku. `2gracz` już nie.
|
||||
|
||||
⚠️ **Bez polskich liter w nazwach!** `ą`, `ę`, `ł`, `ż` i inne mogą być tylko w tekście w cudzysłowie.
|
||||
Pisz `zolw`, a nie `żółw`. Natomiast `print("Żółw")` działa bez problemu.
|
||||
|
||||
Wybieraj nazwy, które mówią, co jest w środku: `cena` jest lepsza niż `c`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Rodzaje wartości
|
||||
|
||||
Finch pilnuje, *jakiego rodzaju* rzecz jest w każdym pudełku. Ten rodzaj nazywa się **typem**.
|
||||
Najczęściej będziesz używać tych:
|
||||
|
||||
| Typ | Co przechowuje | Przykłady |
|
||||
|---------|---------------------------|--------------------------|
|
||||
| `int` | liczby całkowite | `0`, `42`, `-7` |
|
||||
| `float` | liczby z przecinkiem | `3.14`, `0.5`, `-2.0` |
|
||||
| `str` | tekst | `"Cześć"`, `"Ola"` |
|
||||
| `bool` | tak albo nie | `true` (prawda), `false` (fałsz) |
|
||||
| `char` | jeden znak | `'A'`, `'z'`, `'?'` |
|
||||
|
||||
⚠️ W liczbach zamiast przecinka piszemy **kropkę**: `3.14`, a nie `3,14`. Tak jest w prawie wszystkich językach programowania.
|
||||
|
||||
Kiedy używasz `:=`, Finch sam rozpoznaje typ:
|
||||
|
||||
```c
|
||||
imie := "Ola" // str
|
||||
wiek := 10 // int
|
||||
wzrost := 1.42 // float
|
||||
lubi_koty := true // bool
|
||||
```
|
||||
|
||||
Typ możesz też napisać sam, przed nazwą:
|
||||
|
||||
```c
|
||||
int wiek = 10
|
||||
str imie = "Ola"
|
||||
```
|
||||
|
||||
Oba sposoby robią to samo. Wybierz ten, który ci bardziej pasuje.
|
||||
|
||||
**Pudełko nie zmienia typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu.
|
||||
Finch zatrzyma się i powie o tym, co pomaga wcześnie wyłapywać pomyłki.
|
||||
|
||||
---
|
||||
|
||||
## 7. Liczenie
|
||||
|
||||
| Znak | Co robi | Przykład | Wynik |
|
||||
|------|-----------------------------|----------|-------|
|
||||
| `+` | dodawanie | `7 + 2` | `9` |
|
||||
| `-` | odejmowanie | `7 - 2` | `5` |
|
||||
| `*` | mnożenie | `7 * 2` | `14` |
|
||||
| `/` | dzielenie | `7 / 2` | `3` |
|
||||
| `%` | reszta z dzielenia | `7 % 2` | `1` |
|
||||
|
||||
**Uwaga na dzielenie liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`. Obie liczby są
|
||||
całkowite, więc wynik też jest całkowity (część po kropce jest odrzucana).
|
||||
Jeśli chcesz dostać `3.5`, użyj liczby z kropką: `7.0 / 2`.
|
||||
|
||||
Mnożenie i dzielenie wykonują się przed dodawaniem i odejmowaniem, tak jak w szkole.
|
||||
Nawiasy zmieniają kolejność:
|
||||
|
||||
```c
|
||||
print(2 + 3 * 4) // 14
|
||||
print((2 + 3) * 4) // 20
|
||||
```
|
||||
|
||||
`%` przydaje się do sprawdzania, czy liczba jest parzysta. Liczba parzysta dzielona przez 2
|
||||
nie daje reszty, więc `n % 2` wynosi `0`.
|
||||
|
||||
**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linijce to się stało.
|
||||
|
||||
---
|
||||
|
||||
## 8. Podejmowanie decyzji: if
|
||||
|
||||
Program może wybierać, co zrobić. `if` (czyt. „if”, po polsku „jeżeli”) znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*.
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
temperatura := 25
|
||||
|
||||
if temperatura > 20 {
|
||||
print("Jest ciepło, załóż koszulkę")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dodaj `else` („w przeciwnym razie”):
|
||||
|
||||
```c
|
||||
if temperatura > 20 {
|
||||
print("Jest ciepło")
|
||||
} else {
|
||||
print("Weź kurtkę")
|
||||
}
|
||||
```
|
||||
|
||||
A `else if` dodaje kolejne możliwości. Finch sprawdza je od góry i wykonuje tylko
|
||||
**pierwszą** prawdziwą:
|
||||
|
||||
```c
|
||||
if temperatura > 25 {
|
||||
print("Gorąco!")
|
||||
} else if temperatura > 15 {
|
||||
print("Przyjemnie")
|
||||
} else {
|
||||
print("Zimno")
|
||||
}
|
||||
```
|
||||
|
||||
### Porównywanie
|
||||
|
||||
| Znak | Znaczy |
|
||||
|------|------------------------------|
|
||||
| `==` | jest równe |
|
||||
| `!=` | nie jest równe |
|
||||
| `<` | jest mniejsze niż |
|
||||
| `>` | jest większe niż |
|
||||
| `<=` | jest mniejsze lub równe |
|
||||
| `>=` | jest większe lub równe |
|
||||
|
||||
⚠️ **Jedno `=` wkłada wartość do pudełka. Dwa `==` pytają „czy to jest równe?”.**
|
||||
|
||||
Tekst też można porównywać: `if imie == "Ola" { ... }`.
|
||||
|
||||
### Łączenie pytań
|
||||
|
||||
- `&&` znaczy **i**: oba warunki muszą być prawdziwe. `if wiek > 6 && wiek < 12 { ... }`
|
||||
- `||` znaczy **lub**: wystarczy, że jeden jest prawdziwy. `if dzien == "sob" || dzien == "nd" { ... }`
|
||||
- `!` znaczy **nie**: odwraca prawdę i fałsz. `if !pada { ... }`
|
||||
|
||||
---
|
||||
|
||||
## 9. Powtarzanie: pętle
|
||||
|
||||
Komputer nigdy się nie nudzi. **Pętla** powtarza te same kroki wiele razy.
|
||||
|
||||
### Liczenie z `for`
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for i in 1..6 {
|
||||
print("Liczba", i)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Liczba 1
|
||||
Liczba 2
|
||||
Liczba 3
|
||||
Liczba 4
|
||||
Liczba 5
|
||||
```
|
||||
|
||||
`for i in 1..6` znaczy: *„licz za pomocą `i` od 1 do 6, ale **bez** 6”*.
|
||||
Czyli `0..10` daje dziesięć liczb: 0, 1, 2 … 9.
|
||||
|
||||
### Powtarzanie, dopóki coś jest prawdą: `while`
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
licznik := 3
|
||||
while licznik > 0 {
|
||||
print(licznik)
|
||||
licznik -= 1
|
||||
}
|
||||
print("Start!")
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
3
|
||||
2
|
||||
1
|
||||
Start!
|
||||
```
|
||||
|
||||
⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się
|
||||
w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać.
|
||||
|
||||
### Wcześniejsze wyjście i pomijanie
|
||||
|
||||
- `break` od razu wychodzi z pętli.
|
||||
- `continue` pomija resztę bieżącego okrążenia i przechodzi do następnego.
|
||||
|
||||
```c
|
||||
for i in 1..100 {
|
||||
if i == 5 {
|
||||
break // stop, gdy dojdziemy do 5
|
||||
}
|
||||
print(i) // pokaże 1 2 3 4
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Własne polecenia: funkcje
|
||||
|
||||
Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany kawałek kodu to **funkcja**.
|
||||
Jedną już znasz: `main`.
|
||||
|
||||
```c
|
||||
fn przywitaj(str imie) {
|
||||
print("Cześć,", imie)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
przywitaj("Ola")
|
||||
przywitaj("Tomek")
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Cześć, Ola
|
||||
Cześć, Tomek
|
||||
```
|
||||
|
||||
`str imie` w nawiasie znaczy: *„ta funkcja potrzebuje jednego tekstu, a w środku nazywamy go `imie`”*.
|
||||
To, co wpiszesz w nawias przy wywołaniu, zostanie przekazane do środka.
|
||||
|
||||
### Funkcje, które oddają odpowiedź
|
||||
|
||||
Napisz `->` i typ odpowiedzi, a odpowiedź oddaj słowem `return`:
|
||||
|
||||
```c
|
||||
fn dodaj(int a, int b) -> int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
wynik := dodaj(2, 3)
|
||||
print(wynik) // 5
|
||||
}
|
||||
```
|
||||
|
||||
Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad czy pod `main`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Kiedy coś pójdzie nie tak
|
||||
|
||||
Każdy się myli przy programowaniu, nawet eksperci, codziennie.
|
||||
Finch stara się tłumaczyć, co poszło nie tak, prostymi słowami (na razie po angielsku). Przykład:
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
wiek := 10
|
||||
print(wiekk)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
czesc.fn:3:11: error: there is no variable named 'wiekk'
|
||||
3 | print(wiekk)
|
||||
| ^
|
||||
```
|
||||
|
||||
Jak to czytać:
|
||||
|
||||
- `czesc.fn` to plik,
|
||||
- `3` to numer **linijki**,
|
||||
- `11` to który znak w linijce (**kolumna**),
|
||||
- potem wyjaśnienie, a pod nim ta linijka ze strzałką `^` wskazującą problem.
|
||||
|
||||
Tutaj po prostu źle napisaliśmy `wiek`. Komunikat znaczy: „nie ma zmiennej o nazwie 'wiekk'”.
|
||||
|
||||
### Częste błędy
|
||||
|
||||
| Finch mówi | Co to znaczy | Jak naprawić |
|
||||
|---|---|---|
|
||||
| `there is no variable named 'x'` | Nie ma zmiennej `x`: literówka albo zapomniałeś ją zrobić | Sprawdź pisownię. Utwórz ją przez `x := ...` |
|
||||
| `a variable named 'x' already exists here` | Użyłeś `:=` dwa razy dla tej samej nazwy | Za drugim razem użyj `=` |
|
||||
| `'x' must be int, but this is str` | Zły rodzaj wartości dla tego pudełka | Włóż wartość właściwego rodzaju |
|
||||
| `put each statement on its own line` | Dwa polecenia w jednej linijce | Wciśnij Enter między nimi |
|
||||
| `this '{' is never closed` | Brakuje `}` | Dopisz `}` |
|
||||
| `a condition must be bool` | `if` potrzebuje pytania tak/nie | Porównaj coś: `if x > 0` |
|
||||
| `unexpected character 'ż'` | Polska litera w nazwie | Zmień na zwykłą literę: `zolw` |
|
||||
| `runtime error: division by zero` | Program podzielił przez 0 w trakcie działania | Sprawdź liczbę przed dzieleniem |
|
||||
|
||||
---
|
||||
|
||||
## 12. Małe projekty do zrobienia
|
||||
|
||||
Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero, gdy utkniesz.
|
||||
|
||||
### Projekt 1: Tabliczka mnożenia
|
||||
|
||||
Pokaż tabliczkę mnożenia przez 7, od 7 × 1 do 7 × 10.
|
||||
|
||||
<details><summary>Rozwiązanie</summary>
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for i in 1..11 {
|
||||
print("7 x", i, "=", 7 * i)
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Projekt 2: Parzysta czy nieparzysta
|
||||
|
||||
Dla każdej liczby od 1 do 10 napisz, czy jest parzysta, czy nieparzysta.
|
||||
|
||||
<details><summary>Rozwiązanie</summary>
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for n in 1..11 {
|
||||
if n % 2 == 0 {
|
||||
print(n, "jest parzysta")
|
||||
} else {
|
||||
print(n, "jest nieparzysta")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Projekt 3: Stopnie Celsjusza na Fahrenheita
|
||||
|
||||
Pokaż tabelkę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`.
|
||||
|
||||
<details><summary>Rozwiązanie</summary>
|
||||
|
||||
```c
|
||||
fn na_fahrenheita(float c) -> float {
|
||||
return c * 9 / 5 + 32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for c in 0..11 {
|
||||
celsjusz := float(c * 10)
|
||||
print(celsjusz, "C =", na_fahrenheita(celsjusz), "F")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`float(...)` zamienia liczbę całkowitą na liczbę z przecinkiem.
|
||||
</details>
|
||||
|
||||
### Projekt 4: Fizz Buzz
|
||||
|
||||
Policz od 1 do 20. Dla liczb podzielnych przez 3 napisz `Fizz`, przez 5 napisz `Buzz`,
|
||||
przez obie naraz napisz `FizzBuzz`, a w pozostałych przypadkach samą liczbę.
|
||||
|
||||
<details><summary>Rozwiązanie</summary>
|
||||
|
||||
```c
|
||||
fn main() {
|
||||
for n in 1..21 {
|
||||
if n % 15 == 0 {
|
||||
print("FizzBuzz")
|
||||
} else if n % 3 == 0 {
|
||||
print("Fizz")
|
||||
} else if n % 5 == 0 {
|
||||
print("Buzz")
|
||||
} else {
|
||||
print(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Projekt 5: Największa z trzech
|
||||
|
||||
Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która oddaje największą z trzech liczb.
|
||||
|
||||
<details><summary>Rozwiązanie</summary>
|
||||
|
||||
```c
|
||||
fn najwieksza(int a, int b, int c) -> int {
|
||||
if a >= b && a >= c {
|
||||
return a
|
||||
}
|
||||
if b >= c {
|
||||
return b
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print(najwieksza(4, 9, 2)) // 9
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 13. Ściąga
|
||||
|
||||
```c
|
||||
// komentarz
|
||||
|
||||
fn main() { // tu zaczyna się program
|
||||
print("tekst", 42) // pokaż coś
|
||||
|
||||
x := 5 // nowe pudełko (typ zgadnięty)
|
||||
int y = 10 // nowe pudełko (typ podany)
|
||||
x = 7 // zmień zawartość
|
||||
x += 1 // dodaj 1 do x
|
||||
|
||||
if x > 5 { // decyzja
|
||||
print("duże")
|
||||
} else if x > 0 {
|
||||
print("małe")
|
||||
} else {
|
||||
print("zero albo mniej")
|
||||
}
|
||||
|
||||
for i in 0..10 { // licz od 0 do 9
|
||||
print(i)
|
||||
}
|
||||
|
||||
while x > 0 { // powtarzaj, dopóki prawda
|
||||
x -= 1
|
||||
}
|
||||
}
|
||||
|
||||
fn dodaj(int a, int b) -> int { // własne polecenie
|
||||
return a + b
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Słowniczek
|
||||
|
||||
| Słowo | Znaczenie |
|
||||
|---|---|
|
||||
| **program** | Lista kroków dla komputera |
|
||||
| **kod** | Tekst programu |
|
||||
| **uruchomić** | Kazać komputerowi wykonać kroki |
|
||||
| **kompilować** | Przetłumaczyć kod na coś, co komputer umie uruchomić. `finch run` robi to za ciebie |
|
||||
| **zmienna** | Pudełko z nazwą, w którym jest wartość |
|
||||
| **typ** | Rodzaj wartości: liczba, tekst, tak/nie… |
|
||||
| **funkcja** | Nazwana grupa kroków, której można używać wiele razy |
|
||||
| **pętla** | Kroki, które się powtarzają |
|
||||
| **warunek** | Pytanie tak/nie, np. `x > 5` |
|
||||
| **błąd** (error) | Komunikat, że coś jest nie tak, i gdzie |
|
||||
| **bug** | Pomyłka w programie |
|
||||
|
||||
### Słówka po angielsku, które spotkasz w Finchu
|
||||
|
||||
| Słowo | Po polsku |
|
||||
|---|---|
|
||||
| `print` | wypisz |
|
||||
| `if` / `else` | jeżeli / w przeciwnym razie |
|
||||
| `for` … `in` | dla … w |
|
||||
| `while` | dopóki |
|
||||
| `break` / `continue` | przerwij / kontynuuj |
|
||||
| `return` | zwróć (oddaj) |
|
||||
| `true` / `false` | prawda / fałsz |
|
||||
| `fn` (od *function*) | funkcja |
|
||||
| `error` | błąd |
|
||||
|
||||
---
|
||||
|
||||
**Co dalej?** Kiedy to wszystko wyda ci się łatwe, przejdź do
|
||||
[przewodnika dla techników](dla-technikow.md). Opisuje każdą część Fincha.
|
||||
542
docs/pl/dla-technikow.md
Normal file
542
docs/pl/dla-technikow.md
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
# Finch dla techników
|
||||
|
||||
**Dla osób, które dobrze znają komputery, ale programować nie umieją albo umieją niewiele.**
|
||||
Ten przewodnik opisuje instalację Fincha, obsługę polecenia `finch`, cały język,
|
||||
korzystanie z bibliotek C i rozwiązywanie typowych problemów. Nie opisuje, jak kompilator
|
||||
działa w środku. Do tego służy [przewodnik dla inżynierów](dla-inzynierow.md).
|
||||
|
||||
> 🇬🇧 English version: [for-technicians.md](../en/for-technicians.md)
|
||||
|
||||
---
|
||||
|
||||
## Spis treści
|
||||
|
||||
1. [Czym jest Finch](#1-czym-jest-finch)
|
||||
2. [Instalacja](#2-instalacja)
|
||||
3. [Polecenie finch](#3-polecenie-finch)
|
||||
4. [Jak wygląda plik Fincha](#4-jak-wygląda-plik-fincha)
|
||||
5. [Zmienne](#5-zmienne)
|
||||
6. [Typy](#6-typy)
|
||||
7. [Operatory](#7-operatory)
|
||||
8. [Sterowanie przebiegiem](#8-sterowanie-przebiegiem)
|
||||
9. [Funkcje](#9-funkcje)
|
||||
10. [Wskaźniki](#10-wskaźniki)
|
||||
11. [Biblioteki z C](#11-biblioteki-z-c)
|
||||
12. [Błędy](#12-błędy)
|
||||
13. [Rozwiązywanie problemów](#13-rozwiązywanie-problemów)
|
||||
14. [Struktura projektu i testy](#14-struktura-projektu-i-testy)
|
||||
15. [Obecne ograniczenia](#15-obecne-ograniczenia)
|
||||
|
||||
---
|
||||
|
||||
## 1. Czym jest Finch
|
||||
|
||||
Finch to język **kompilowany**. Narzędzie `finch` tłumaczy plik `.fn` na prawdziwy program
|
||||
(natywny plik wykonywalny, taki sam jak te z C). Korzysta przy tym z **LLVM**, tego samego
|
||||
zaplecza kompilatorów, na którym działają clang, Rust i Swift.
|
||||
|
||||
Co to daje:
|
||||
|
||||
- **Szybkość zbliżoną do C.** Nie ma interpretera, maszyny wirtualnej ani garbage collectora.
|
||||
- **Małe, samodzielne programy**, które można skopiować i uruchomić.
|
||||
- **Bezpośredni dostęp do bibliotek C.** Piszesz `import "stdio.h"` i wołasz, co chcesz.
|
||||
|
||||
Zasady projektu: jeden sposób na każdą rzecz, składnia podobna do C bez jej pułapek
|
||||
i komunikaty błędów, które mówią, jak problem naprawić.
|
||||
|
||||
---
|
||||
|
||||
## 2. Instalacja
|
||||
|
||||
Finch działa obecnie na **Linuksie x86-64**. Testowany był z **LLVM 21**.
|
||||
Z innymi wersjami LLVM prawdopodobnie się nie skompiluje, bo API C++ LLVM zmienia się między wydaniami.
|
||||
|
||||
### Opcja A: Nix (zalecana, nic nie instalujesz ręcznie)
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Nexoniarz/Finch.git
|
||||
cd Finch
|
||||
nix-shell # pobiera LLVM, libclang, cmake, ninja, pkg-config
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build
|
||||
./build/finch version # finch 1.0.0 (LLVM 21.x)
|
||||
```
|
||||
|
||||
### Opcja B: pakiety z twojej dystrybucji
|
||||
|
||||
Potrzebujesz: kompilatora C/C++, CMake ≥ 3.20, Ninja (albo Make), plików deweloperskich LLVM 21
|
||||
i libclang 21, a opcjonalnie `pkg-config`.
|
||||
|
||||
| Dystrybucja | Pakiety (nazwy mogą się nieco różnić zależnie od wersji) |
|
||||
|---|---|
|
||||
| Debian / Ubuntu | `build-essential cmake ninja-build pkg-config llvm-21-dev libclang-21-dev` |
|
||||
| Fedora | `gcc-c++ cmake ninja-build pkgconf llvm-devel clang-devel` |
|
||||
| Arch | `base-devel cmake ninja pkgconf llvm clang` |
|
||||
|
||||
Jeśli CMake nie znajduje LLVM, wskaż mu katalog:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
### Dodanie `finch` do PATH (opcjonalnie)
|
||||
|
||||
```sh
|
||||
sudo cp build/finch /usr/local/bin/
|
||||
finch version
|
||||
```
|
||||
|
||||
Podczas pracy Finch potrzebuje działającego kompilatora C (`cc`). Używa go jako **linkera**,
|
||||
który skleja skompilowany kod w gotowy program. Inny kompilator wskażesz zmienną środowiskową `CC`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Polecenie finch
|
||||
|
||||
```
|
||||
finch run <plik.fn> skompiluj i od razu uruchom
|
||||
finch build <plik.fn> [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku)
|
||||
finch ir <plik.fn> pokaż LLVM IR (dla ciekawskich)
|
||||
finch version pokaż wersję
|
||||
|
||||
opcje:
|
||||
-l <lib> dolinkuj bibliotekę C, to samo co link "lib" w pliku
|
||||
-O0 bez optymalizacji
|
||||
```
|
||||
|
||||
Przykłady:
|
||||
|
||||
```sh
|
||||
finch run hello.fn # wypisuje wynik
|
||||
finch build gra.fn -o mojagra # tworzy ./mojagra
|
||||
./mojagra
|
||||
finch run okno.fn -l glfw # z biblioteką C
|
||||
```
|
||||
|
||||
`finch run` przekazuje dalej kod wyjścia twojego programu. Jeśli program się wysypie,
|
||||
powie jak (np. `the program crashed: Segmentation fault`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Jak wygląda plik Fincha
|
||||
|
||||
```c
|
||||
// komentarz zaczyna się od dwóch ukośników
|
||||
/* albo zajmuje
|
||||
kilka linijek */
|
||||
|
||||
import "math.h" // nagłówki C (opcjonalne), zawsze na górze
|
||||
link "m" // biblioteki C do dolinkowania (opcjonalne)
|
||||
|
||||
fn main() { // tu zaczyna się program
|
||||
print("Cześć")
|
||||
}
|
||||
```
|
||||
|
||||
Zasady, które warto znać:
|
||||
|
||||
- **Jedna instrukcja na linijkę.** Średników nie ma. Długie wyrażenie możesz złamać
|
||||
**po** operatorze (`a +⏎ b`) albo w dowolnym miejscu wewnątrz nawiasów `( )`.
|
||||
- **Klamry są zawsze obowiązkowe** po `if`, `else`, `for`, `while`, a otwierająca `{` stoi w tej samej linijce.
|
||||
- **Bez nawiasów wokół warunków:** `if x > 5 {`, a nie `if (x > 5) {`.
|
||||
- Funkcje mogą być w pliku w dowolnej kolejności.
|
||||
- `main` to `fn main()` albo `fn main() -> int` (zwraca kod wyjścia).
|
||||
|
||||
---
|
||||
|
||||
## 5. Zmienne
|
||||
|
||||
```c
|
||||
x := 5 // nowa zmienna, typ zgadnięty (int)
|
||||
int y = 10 // nowa zmienna, typ podany jawnie
|
||||
int z // bez wartości: startuje od zera ("" dla str, null dla wskaźników)
|
||||
|
||||
x = 7 // przypisanie
|
||||
x += 1 // także -= *= /= %=
|
||||
```
|
||||
|
||||
- Nazwę można zadeklarować **raz** w funkcji. Finch nie pozwala użyć tej samej nazwy ponownie
|
||||
w bloku wewnętrznym (nie ma „przesłaniania”), bo to częste źródło pomyłek.
|
||||
- Zmienna istnieje od deklaracji do końca bloku `{ }`, w którym powstała.
|
||||
- Nazwy: litery ASCII, cyfry i `_`, bez cyfry na początku. Polskie litery tylko w tekstach w cudzysłowie.
|
||||
Nazwy typów (`int`, `u8`, …) i słowa kluczowe są zarezerwowane.
|
||||
|
||||
**Słowa kluczowe:** `fn return if else while for in break continue true false null import link`
|
||||
|
||||
---
|
||||
|
||||
## 6. Typy
|
||||
|
||||
### Typy na co dzień
|
||||
|
||||
| Typ | Przechowuje | Przykład |
|
||||
|---------|-------------|----------|
|
||||
| `int` | liczba całkowita, 64-bitowa (to samo co `i64`) | `42`, `-7`, `0xFF`, `1_000_000` |
|
||||
| `float` | liczba z przecinkiem, 64-bitowa (to samo co `f64`) | `3.14` |
|
||||
| `bool` | `true` albo `false` | |
|
||||
| `char` | jeden znak wielkości bajtu | `'A'`, `'\n'` |
|
||||
| `str` | tekst (napis w stylu C) | `"cześć\tświecie"` |
|
||||
|
||||
Znaki specjalne w tekstach: `\n` nowa linia, `\t` tabulator, `\r`, `\0`, `\\`, `\"`, `\'`.
|
||||
|
||||
### Typy z rozmiarem (do plików, sprzętu, bibliotek C)
|
||||
|
||||
| Ze znakiem | Zakres | Bez znaku | Zakres |
|
||||
|---|---|---|---|
|
||||
| `i8` | −128 … 127 | `u8` | 0 … 255 |
|
||||
| `i16` | −32 768 … 32 767 | `u16` | 0 … 65 535 |
|
||||
| `i32` | ok. ±2,1 mld | `u32` | 0 … ok. 4,29 mld |
|
||||
| `i64` | ok. ±9,2 × 10¹⁸ | `u64` | 0 … ok. 1,8 × 10¹⁹ |
|
||||
|
||||
`f32` to 32-bitowa liczba z przecinkiem (ok. 7 cyfr znaczących), `f64` to 64-bitowa (ok. 15–16).
|
||||
|
||||
### Wskaźniki
|
||||
|
||||
`ptr[T]` wskazuje na wartość typu `T`. Samo `ptr` wskazuje na „coś” nieznanego typu,
|
||||
jak `void*` w C. Szczegóły w rozdziale [Wskaźniki](#10-wskaźniki).
|
||||
|
||||
### Zamiana typów
|
||||
|
||||
Finch zamienia typy sam **tylko wtedy, gdy nic nie może zginąć**:
|
||||
|
||||
| Z | Na | Automatycznie? |
|
||||
|---|---|---|
|
||||
| mniejsza liczba całkowita | większa o tej samej „znakowości” (`i8`→`i32`, `u8`→`u64`) | ✅ |
|
||||
| bez znaku | ściśle większa ze znakiem (`u8`→`i16`, `u32`→`i64`) | ✅ |
|
||||
| dowolna liczba całkowita | `float` / `f32` | ✅ |
|
||||
| `f32` | `f64` | ✅ |
|
||||
| `null` | dowolny wskaźnik | ✅ |
|
||||
| dowolny wskaźnik, `str` | `ptr` | ✅ |
|
||||
| `ptr` | dowolny `ptr[T]` | ✅ |
|
||||
| wszystko inne | | ❌ napisz to jawnie: `typ(wartość)` |
|
||||
|
||||
**Liczby wpisane w kod się dopasowują.** Liczba napisana wprost w kodzie pasuje do każdego typu,
|
||||
który ją pomieści: `u8 b = 200` działa, a `u8 b = 300` to błąd (`the number 300 doesn't fit in u8`).
|
||||
To samo dotyczy stałych z nagłówków C.
|
||||
|
||||
**Jawna zamiana** wygląda jak wywołanie funkcji o nazwie typu:
|
||||
|
||||
```c
|
||||
int(3.99) // 3 (obcina część ułamkową)
|
||||
int(-3.99) // -3
|
||||
float(7) / 2 // 3.5
|
||||
u8(300) // 44 (zostawia najniższe 8 bitów)
|
||||
i8(200) // -56
|
||||
int('A') // 65
|
||||
char(66) // 'B'
|
||||
bool(0) // false (każda liczba różna od zera to true)
|
||||
ptr(tekst) // str jako wskaźnik bez typu
|
||||
str(p) // wskaźnik jako str
|
||||
```
|
||||
|
||||
**Liczby o stałym rozmiarze się „zawijają”**, jak w prawdziwym sprzęcie: `u8` równe 255 plus 1 daje 0.
|
||||
Działania liczą się w typie użytych wartości, więc `u8(250) + 10` daje `4`, a nie `260`.
|
||||
Jeśli potrzebujesz więcej miejsca, najpierw zamień typ: `int(x) + 10`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Operatory
|
||||
|
||||
### Według priorytetu (wyższy wiąże mocniej)
|
||||
|
||||
| Priorytet | Operatory | Znaczenie |
|
||||
|---|---|---|
|
||||
| 5 | `*` `/` `%` `<<` `>>` `&` | mnożenie, dzielenie, reszta, przesunięcie bitów w lewo/prawo, bitowe AND |
|
||||
| 4 | `+` `-` `\|` `^` | dodawanie, odejmowanie, bitowe OR, bitowe XOR |
|
||||
| 3 | `==` `!=` `<` `<=` `>` `>=` | porównania |
|
||||
| 2 | `&&` | logiczne I |
|
||||
| 1 | `\|\|` | logiczne LUB |
|
||||
|
||||
Jednoargumentowe (przed wartością): `-x` zmiana znaku, `!x` logiczne NIE, `~x` odwrócenie wszystkich bitów.
|
||||
|
||||
> **Celowo inaczej niż w C:** operatory bitowe wiążą mocniej niż porównania, więc
|
||||
> `flagi & 4 != 0` znaczy `(flagi & 4) != 0`. W C to samo po cichu znaczy co innego.
|
||||
|
||||
Uwagi:
|
||||
|
||||
- `/` na liczbach całkowitych zaokrągla w stronę zera: `7 / 2` to `3`, `-7 / 2` to `-3`. `%` ma znak lewej strony.
|
||||
- Dzielenie przez zero **zatrzymuje program** komunikatem `runtime error: division by zero` z numerem linijki.
|
||||
Jeśli zero jest wpisane wprost w kod, jest to błąd już przy kompilacji.
|
||||
- `&&` i `||` liczą prawą stronę tylko wtedy, gdy to potrzebne.
|
||||
- Tekst (`str`) można porównywać przez `==` i `!=` (porównuje zawartość). `<` i `>` na tekstach nie działają.
|
||||
- Wskaźniki porównuje się przez `==` i `!=`, także z `null`.
|
||||
- Operatory bitowe wymagają liczb całkowitych. Dla `bool` używaj `&&` i `||`.
|
||||
- `>>` na liczbach ze znakiem zachowuje znak (`-16 >> 2` to `-4`). Na liczbach bez znaku wypełnia zerami.
|
||||
|
||||
---
|
||||
|
||||
## 8. Sterowanie przebiegiem
|
||||
|
||||
```c
|
||||
if x > 10 {
|
||||
...
|
||||
} else if x > 0 {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
|
||||
while licznik > 0 {
|
||||
licznik -= 1
|
||||
}
|
||||
|
||||
for i in 0..10 { // i = 0, 1, …, 9 (koniec nie jest wliczany)
|
||||
if i == 3 { continue }
|
||||
if i == 8 { break }
|
||||
}
|
||||
```
|
||||
|
||||
- Warunek musi być typu `bool`. `if x {` z liczbą to błąd; napisz `if x != 0 {`.
|
||||
- W `for i in a..b` oba końce są liczbami całkowitymi obliczanymi **raz**, przed startem pętli.
|
||||
`i` jest typu `int` i nie można go zmieniać wewnątrz pętli.
|
||||
- `while true { ... }` kręci się do `break` albo `return`.
|
||||
- Kod po `return`, `break` lub `continue` w tym samym bloku to błąd, bo nigdy się nie wykona.
|
||||
|
||||
---
|
||||
|
||||
## 9. Funkcje
|
||||
|
||||
```c
|
||||
fn nazwa(typ1 param1, typ2 param2) -> typ_wyniku {
|
||||
...
|
||||
return wartosc
|
||||
}
|
||||
|
||||
fn bez_wyniku(str wiadomosc) { // bez "->": nic nie zwraca
|
||||
print(wiadomosc)
|
||||
}
|
||||
```
|
||||
|
||||
- Parametry są kopiami. Zmiana parametru nie zmienia zmiennej u wywołującego.
|
||||
Do tego służą wskaźniki (niżej).
|
||||
- Funkcja z `-> typ` musi zwrócić wartość na każdej ścieżce. Finch to sprawdza.
|
||||
- Funkcji nie można definiować wewnątrz innej funkcji.
|
||||
- Wbudowane nazwy, których nie możesz użyć: `print`, `addr` i wszystkie nazwy typów.
|
||||
|
||||
### Funkcje wbudowane
|
||||
|
||||
| Funkcja | Co robi |
|
||||
|---|---|
|
||||
| `print(a, b, ...)` | Wypisuje wszystkie wartości oddzielone spacjami i przechodzi do nowej linii. Działa z każdym typem. |
|
||||
| `addr(x)` | Daje wskaźnik na zmienną `x`. |
|
||||
| `int(x)`, `u8(x)`, `float(x)`, … | Zamienia typy (zobacz [Zamiana typów](#zamiana-typów)). |
|
||||
|
||||
---
|
||||
|
||||
## 10. Wskaźniki
|
||||
|
||||
Wskaźnik przechowuje **adres** wartości, czyli miejsce w pamięci, gdzie ona leży. Finch zamiast
|
||||
`*` i `&` z C używa słów:
|
||||
|
||||
| C | Finch | Znaczenie |
|
||||
|---|---|---|
|
||||
| `int *p` | `ptr[int] p` | p wskazuje na int |
|
||||
| `&x` | `addr(x)` | adres x |
|
||||
| `*p` | `p.value` | wartość, na którą wskazuje p |
|
||||
| `NULL` | `null` | wskazuje donikąd |
|
||||
| `void *` | `ptr` | wskazuje na coś nieznanego typu |
|
||||
|
||||
```c
|
||||
fn podwoj(ptr[int] p) {
|
||||
p.value = p.value * 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := 21
|
||||
podwoj(addr(x))
|
||||
print(x) // 42
|
||||
|
||||
ptr[int] nic = null
|
||||
if nic == null {
|
||||
print("pusty")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Wskaźnik może wskazywać na wskaźnik: `ptr[ptr[int]]`, a potem `pp.value.value`.
|
||||
|
||||
Użycie `.value` na wskaźniku `null` **zatrzymuje program** komunikatem
|
||||
`runtime error: used .value on a null pointer`, zamiast losowo go wysypać.
|
||||
|
||||
⚠️ Wskaźnika na zmienną nie wolno używać po zakończeniu funkcji, do której ta zmienna należała,
|
||||
bo zmienna wtedy już nie istnieje. Finch jeszcze tego nie sprawdza.
|
||||
|
||||
---
|
||||
|
||||
## 11. Biblioteki z C
|
||||
|
||||
Prawie każda biblioteka na Linuksie ma interfejs w C: grafika, dźwięk, sieć, bazy danych.
|
||||
Finch może z nich korzystać bezpośrednio.
|
||||
|
||||
### Krok 1: zaimportuj nagłówek
|
||||
|
||||
```c
|
||||
import "stdio.h"
|
||||
import "math.h"
|
||||
import "GLFW/glfw3.h" // ścieżka w systemowych katalogach nagłówków
|
||||
import "mojalib.h" // nagłówek leżący obok twojego pliku .fn
|
||||
```
|
||||
|
||||
Finch czyta nagłówek przez **libclang** (parser C z clanga) i udostępnia:
|
||||
|
||||
- **funkcje**, także te ze zmienną liczbą argumentów, jak `printf`,
|
||||
- **wartości `enum`**,
|
||||
- **proste stałe `#define`**, które są pojedynczą liczbą, np. `M_PI`, `EOF`, `GL_COLOR_BUFFER_BIT`,
|
||||
- **zmienne globalne**, np. `stdout` i `stderr`.
|
||||
|
||||
### Krok 2: dolinkuj bibliotekę
|
||||
|
||||
Nagłówek tylko *opisuje* funkcje. Ich właściwy kod jest w pliku biblioteki (`libNAZWA.so`).
|
||||
Powiedz Finchowi, której biblioteki użyć, na górze pliku:
|
||||
|
||||
```c
|
||||
link "glfw" // używa libglfw.so
|
||||
link "GL" // używa libGL.so
|
||||
```
|
||||
|
||||
Wpisz samą nazwę: dla `libglfw.so` to `glfw`. Jeśli `pkg-config` zna tę nazwę, Finch użyje jego
|
||||
ustawień (ścieżek i dodatkowych bibliotek). W przeciwnym razie przekaże linkerowi `-lNAZWA`.
|
||||
Biblioteka standardowa C i matematyczna (`libm`) są dolinkowane zawsze.
|
||||
|
||||
Na linii poleceń `-l NAZWA` robi to samo co `link "NAZWA"`.
|
||||
|
||||
Jeśli zapomnisz linijki `link`, Finch powie dokładnie, czego brakuje:
|
||||
|
||||
```
|
||||
error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "GLFW/glfw3.h", but its library isn't linked
|
||||
the header only says the functions exist; their code lives in a library.
|
||||
add this at the top of your file (with the library's real name):
|
||||
|
||||
link "glfw"
|
||||
```
|
||||
|
||||
### Jak typy C wyglądają w Finchu
|
||||
|
||||
| C | Finch |
|
||||
|---|---|
|
||||
| `char`, `signed char`, `unsigned char` | `char`, `i8`, `u8` |
|
||||
| `short`, `int`, `long`, `long long` | `i16`, `i32`, `i64`, `i64` (według faktycznego rozmiaru) |
|
||||
| `unsigned …` | `u16`, `u32`, `u64` |
|
||||
| `float`, `double` | `f32`, `f64` |
|
||||
| `_Bool` / `bool` | `bool` |
|
||||
| `enum` | odpowiadająca liczba całkowita ze znakiem |
|
||||
| `char *`, `const char *` | `str` |
|
||||
| `int *`, `double *`, … | `ptr[i32]`, `ptr[f64]`, … |
|
||||
| `void *`, `struct X *`, `FILE *`, wskaźniki na funkcje | `ptr` |
|
||||
|
||||
Czyli `int` z C to `i32` w Finchu. Liczby wpisane w kod dopasowują się same (`abs(-5)` działa),
|
||||
ale zmienną typu `int` z Fincha trzeba zamienić: `abs(i32(x))`.
|
||||
|
||||
### Przykład
|
||||
|
||||
```c
|
||||
import "stdio.h"
|
||||
import "math.h"
|
||||
import "stdlib.h"
|
||||
|
||||
fn main() {
|
||||
printf("%d + %d = %d\n", 2, 3, 5)
|
||||
print(sqrt(2), M_PI, RAND_MAX)
|
||||
|
||||
ptr[i32] n = malloc(4) // poproś C o 4 bajty pamięci
|
||||
n.value = 7
|
||||
print(n.value)
|
||||
free(n) // i oddaj je
|
||||
|
||||
fprintf(stderr, "to idzie na wyjście błędów\n")
|
||||
}
|
||||
```
|
||||
|
||||
Pełny przykład z grafiką jest w `examples/window.fn`: okno GLFW + OpenGL, które zmienia kolor.
|
||||
`examples/llvm.fn` pokazuje, że Finch potrafi nawet sterować samym LLVM przez jego API w C.
|
||||
|
||||
### Co jeszcze nie działa
|
||||
|
||||
- Funkcje C, które przyjmują lub zwracają **`struct` przez wartość**, np. `div()`. Finch mówi o tym
|
||||
wprost, jeśli spróbujesz. Funkcje przyjmujące *wskaźnik* na strukturę działają.
|
||||
- **Makra-funkcje** (`#define MAX(a,b) ...`) i makra, które nie są pojedynczą liczbą.
|
||||
- `long double`, liczby 128-bitowe.
|
||||
- Odczytywanie **pól** struktury C.
|
||||
|
||||
---
|
||||
|
||||
## 12. Błędy
|
||||
|
||||
### Błędy kompilacji
|
||||
|
||||
Finch zatrzymuje się na pierwszym problemie i pokazuje, gdzie jest:
|
||||
|
||||
```
|
||||
gra.fn:12:9: error: 'wynik' must be int, but this is str
|
||||
12 | wynik = "wysoki"
|
||||
| ^
|
||||
```
|
||||
|
||||
Format: `plik:linijka:kolumna: error: wyjaśnienie`, a pod spodem linijka ze znakiem `^` pod miejscem błędu.
|
||||
Komunikaty są po angielsku.
|
||||
|
||||
### Błędy w trakcie działania
|
||||
|
||||
Niektóre problemy widać dopiero, gdy program działa. Zamiast niezdefiniowanego zachowania
|
||||
(jak w C) Finch zatrzymuje się z czytelnym komunikatem na wyjściu błędów i kodem wyjścia 1:
|
||||
|
||||
| Komunikat | Przyczyna |
|
||||
|---|---|
|
||||
| `runtime error: division by zero` | `/` albo `%` przez zmienną równą 0 |
|
||||
| `runtime error: division overflows …` | najmniejsza liczba ze znakiem podzielona przez −1 |
|
||||
| `runtime error: used .value on a null pointer` | odczyt lub zapis `p.value`, gdy `p` jest `null` |
|
||||
|
||||
---
|
||||
|
||||
## 13. Rozwiązywanie problemów
|
||||
|
||||
| Problem | Rozwiązanie |
|
||||
|---|---|
|
||||
| `can't find the C header 'x.h'` | Brakuje plików deweloperskich biblioteki. Zainstaluj je (Debian: `libx-dev`; Nix: dopisz do `shell.nix`). Własny nagłówek połóż obok pliku `.fn`. |
|
||||
| `… come from "x.h", but its library isn't linked` | Dopisz `link "nazwa"` na górze pliku. |
|
||||
| `the library 'x' wasn't found` | Biblioteka nie jest zainstalowana albo nazwa jest zła. Na Niksie dopisz ją do `shell.nix` i pracuj wewnątrz `nix-shell`. |
|
||||
| `the C function 'f' can't be used from Finch yet` | Używa struktury przez wartość albo innego nieobsługiwanego typu. Poszukaj wariantu, który przyjmuje wskaźniki. |
|
||||
| `no C compiler found to link with` | Zainstaluj gcc lub clang albo ustaw `CC`. |
|
||||
| Program się kompiluje, ale przy starcie nie znajduje biblioteki C | Został zlinkowany ze ścieżki, której system nie przeszukuje. Uruchamiaj go w tym samym `nix-shell` albo zainstaluj bibliotekę systemowo. |
|
||||
| `unexpected character 'ż'` | Polska litera w nazwie. W nazwach używaj tylko a–z. |
|
||||
| CMake: `libclang not found` | Zainstaluj pakiet deweloperski libclang albo uruchom `cmake` w `nix-shell`. |
|
||||
| Budowanie kończy się błędami API LLVM | Masz inną wersję LLVM niż 21. |
|
||||
| Program nigdy się nie kończy | Warunek pętli nigdy nie staje się fałszywy. Naciśnij Ctrl+C. |
|
||||
|
||||
---
|
||||
|
||||
## 14. Struktura projektu i testy
|
||||
|
||||
```
|
||||
Finch/
|
||||
├── src/ kompilator (C++)
|
||||
├── examples/ przykładowe programy (hello, tour, types, c_import, window, llvm)
|
||||
├── tests/
|
||||
│ ├── run/ programy + dokładny wynik, który muszą wypisać (.out)
|
||||
│ ├── fail/ programy, które muszą się nie udać, z oczekiwanym błędem w 1. linijce
|
||||
│ └── run.sh skrypt uruchamiający testy
|
||||
├── docs/ ta dokumentacja (en, pl)
|
||||
├── shell.nix środowisko deweloperskie Nix
|
||||
└── CMakeLists.txt
|
||||
```
|
||||
|
||||
Testy uruchomisz po zbudowaniu:
|
||||
|
||||
```sh
|
||||
tests/run.sh # → 34 passed, 0 failed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. Obecne ograniczenia
|
||||
|
||||
Finch 1.0 to solidny rdzeń, a nie skończony język. Jeszcze nie ma:
|
||||
|
||||
- `struct`, tablic, czytania z klawiatury (`input()`), sklejania tekstów (`"a" + "b"`).
|
||||
Na razie użyj do tego funkcji z C (`scanf`, `snprintf`, `malloc`…).
|
||||
- Automatycznego zarządzania pamięcią (w planach: zwalnianie na końcu bloku i `defer`).
|
||||
- Modułów Fincha (na razie jeden program = jeden plik `.fn`).
|
||||
- Platform innych niż Linux x86-64.
|
||||
|
||||
Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju).
|
||||
23
examples/c_import.fn
Normal file
23
examples/c_import.fn
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Use any C library: import its header and call it.
|
||||
import "stdio.h"
|
||||
import "math.h"
|
||||
import "stdlib.h"
|
||||
import "string.h"
|
||||
|
||||
fn main() {
|
||||
printf("printf works: %d + %d = %d\n", 2, 3, 2 + 3)
|
||||
puts("so does puts")
|
||||
|
||||
print("sqrt(2) =", sqrt(2))
|
||||
print("pi from math.h:", M_PI)
|
||||
print("strlen(\"finch\") =", strlen("finch"))
|
||||
print("RAND_MAX =", RAND_MAX, "EOF =", EOF)
|
||||
|
||||
// memory from C: malloc gives a ptr, free gives it back
|
||||
ptr[i32] n = malloc(4)
|
||||
n.value = 7
|
||||
print("from the heap:", n.value)
|
||||
free(n)
|
||||
|
||||
fprintf(stderr, "this goes to stderr\n")
|
||||
}
|
||||
3
examples/hello.fn
Normal file
3
examples/hello.fn
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
print("Hello, Finch!")
|
||||
}
|
||||
18
examples/llvm.fn
Normal file
18
examples/llvm.fn
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Finch driving LLVM itself through the LLVM-C API: the first step toward
|
||||
// a compiler written in Finch. Builds a module with `answer() -> 42` and prints its IR.
|
||||
import "llvm-c/Core.h"
|
||||
link "LLVM"
|
||||
|
||||
fn main() {
|
||||
mod := LLVMModuleCreateWithName("from_finch")
|
||||
i64t := LLVMInt64Type()
|
||||
fnType := LLVMFunctionType(i64t, null, 0, 0)
|
||||
f := LLVMAddFunction(mod, "answer", fnType)
|
||||
entry := LLVMAppendBasicBlock(f, "entry")
|
||||
b := LLVMCreateBuilder()
|
||||
LLVMPositionBuilderAtEnd(b, entry)
|
||||
LLVMBuildRet(b, LLVMConstInt(i64t, 42, 0))
|
||||
LLVMDumpModule(mod)
|
||||
LLVMDisposeBuilder(b)
|
||||
LLVMDisposeModule(mod)
|
||||
}
|
||||
62
examples/tour.fn
Normal file
62
examples/tour.fn
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// A quick tour of Finch.
|
||||
|
||||
fn add(int a, int b) -> int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn fib(int n) -> int {
|
||||
if n < 2 {
|
||||
return n
|
||||
}
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
fn isEven(int n) -> bool {
|
||||
return n % 2 == 0
|
||||
}
|
||||
|
||||
fn greet(str name) {
|
||||
if name == "Finch" {
|
||||
print("Hi, it's me!")
|
||||
} else {
|
||||
print("Hi,", name)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// two ways to make a variable
|
||||
int age = 10
|
||||
name := "Ola" // type is guessed: str
|
||||
pi := 3.14 // float
|
||||
letter := 'F' // char
|
||||
|
||||
print(name, "is", age, "years old")
|
||||
print("2 + 3 =", add(2, 3))
|
||||
print("pi * 2 =", pi * 2)
|
||||
print("first letter:", letter)
|
||||
print("10 is even?", isEven(10))
|
||||
|
||||
greet("Finch")
|
||||
greet(name)
|
||||
|
||||
// loops
|
||||
for i in 0..10 {
|
||||
if i == 3 {
|
||||
continue
|
||||
}
|
||||
if i == 7 {
|
||||
break
|
||||
}
|
||||
print("i =", i)
|
||||
}
|
||||
|
||||
count := 3
|
||||
while count > 0 {
|
||||
print("countdown", count)
|
||||
count -= 1
|
||||
}
|
||||
|
||||
print("fib(30) =", fib(30))
|
||||
print("7 / 2 =", 7 / 2, "and as float:", float(7) / 2)
|
||||
print("int(9.99) =", int(9.99))
|
||||
}
|
||||
31
examples/types.fn
Normal file
31
examples/types.fn
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Sized numbers and pointers.
|
||||
|
||||
fn double(ptr[int] p) {
|
||||
p.value = p.value * 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// everyday: int and float. For bytes and hardware: exact sizes.
|
||||
u8 small = 255
|
||||
i32 points = -1000
|
||||
big := u64(0xFFFF_FFFF)
|
||||
f32 half = 0.5
|
||||
|
||||
small += 1 // u8 wraps around, like real hardware
|
||||
print("u8 255 + 1 =", small)
|
||||
print("points:", points, "big:", big, "half:", half)
|
||||
|
||||
i64 wide = points // a smaller type fits into a bigger one by itself
|
||||
print("wide:", wide)
|
||||
|
||||
// pointers: addr(x) gives you where x lives, p.value is what is there
|
||||
int x = 21
|
||||
p := addr(x)
|
||||
double(p)
|
||||
print("x is now", x)
|
||||
|
||||
ptr[int] nothing = null
|
||||
if nothing == null {
|
||||
print("nothing points nowhere")
|
||||
}
|
||||
}
|
||||
34
examples/window.fn
Normal file
34
examples/window.fn
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// A window that slowly changes color. Needs GLFW and OpenGL
|
||||
// (on Nix they are in shell.nix: run this inside nix-shell).
|
||||
import "GLFW/glfw3.h"
|
||||
import "GL/gl.h"
|
||||
import "math.h"
|
||||
link "glfw"
|
||||
link "GL"
|
||||
|
||||
fn main() {
|
||||
if glfwInit() == 0 {
|
||||
print("could not start GLFW")
|
||||
return
|
||||
}
|
||||
|
||||
window := glfwCreateWindow(800, 600, "Finch", null, null)
|
||||
if window == null {
|
||||
print("could not open a window")
|
||||
glfwTerminate()
|
||||
return
|
||||
}
|
||||
glfwMakeContextCurrent(window)
|
||||
|
||||
while glfwWindowShouldClose(window) == 0 {
|
||||
t := glfwGetTime()
|
||||
glClearColor(f32(sin(t) * 0.5 + 0.5), 0.3, 0.6, 1.0)
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
glfwSwapBuffers(window)
|
||||
glfwPollEvents()
|
||||
}
|
||||
|
||||
glfwDestroyWindow(window)
|
||||
glfwTerminate()
|
||||
}
|
||||
17
shell.nix
Normal file
17
shell.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
# building Finch
|
||||
llvmPackages.llvm
|
||||
llvmPackages.libclang # reads C headers for `import "stdio.h"`
|
||||
llvmPackages.clang
|
||||
cmake
|
||||
ninja
|
||||
pkg-config # finds the right flags for `link "..."`
|
||||
|
||||
# for examples/window.fn
|
||||
glfw
|
||||
libGL
|
||||
];
|
||||
}
|
||||
235
src/ast.h
Normal file
235
src/ast.h
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Type {
|
||||
enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, Str, Ptr, Null };
|
||||
Kind kind = Void;
|
||||
std::shared_ptr<Type> elem; // what a Ptr points to; null for the untyped `ptr`
|
||||
|
||||
Type() = default;
|
||||
Type(Kind k) : kind(k) {}
|
||||
static Type ptrTo(const Type &t) {
|
||||
Type p(Ptr);
|
||||
p.elem = std::make_shared<Type>(t);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool isInt() const { return kind >= I8 && kind <= U64; }
|
||||
bool isSigned() const { return kind >= I8 && kind <= I64; }
|
||||
bool isUnsigned() const { return kind >= U8 && kind <= U64; }
|
||||
bool isFloat() const { return kind == F32 || kind == F64; }
|
||||
bool isNumber() const { return isInt() || isFloat(); }
|
||||
bool isPtr() const { return kind == Ptr; }
|
||||
int bits() const {
|
||||
switch (kind) {
|
||||
case Bool: return 1;
|
||||
case Char: case I8: case U8: return 8;
|
||||
case I16: case U16: return 16;
|
||||
case I32: case U32: case F32: return 32;
|
||||
default: return 64;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const Type &o) const {
|
||||
if (kind != o.kind) return false;
|
||||
if (kind != Ptr) return true;
|
||||
if (!elem || !o.elem) return !elem && !o.elem;
|
||||
return *elem == *o.elem;
|
||||
}
|
||||
bool operator!=(const Type &o) const { return !(*this == o); }
|
||||
|
||||
std::string name() const {
|
||||
switch (kind) {
|
||||
case Void: return "nothing";
|
||||
case Bool: return "bool";
|
||||
case Char: return "char";
|
||||
case I8: return "i8";
|
||||
case I16: return "i16";
|
||||
case I32: return "i32";
|
||||
case I64: return "int";
|
||||
case U8: return "u8";
|
||||
case U16: return "u16";
|
||||
case U32: return "u32";
|
||||
case U64: return "u64";
|
||||
case F32: return "f32";
|
||||
case F64: return "float";
|
||||
case Str: return "str";
|
||||
case Ptr: return elem ? "ptr[" + elem->name() + "]" : "ptr";
|
||||
case Null: return "null";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
};
|
||||
|
||||
// Built-in type names. `int` is i64 and `float` is f64: the everyday defaults.
|
||||
bool typeFromName(const std::string &name, Type &out);
|
||||
|
||||
struct Pos {
|
||||
int line = 0, col = 0;
|
||||
};
|
||||
|
||||
// ---------- expressions ----------
|
||||
|
||||
enum class ExprKind { Int, Float, Bool, Char, Str, Null, Var, Unary, Binary, Call, Member };
|
||||
|
||||
struct Expr {
|
||||
ExprKind kind;
|
||||
Pos pos;
|
||||
Expr(ExprKind k, Pos p) : kind(k), pos(p) {}
|
||||
virtual ~Expr() = default;
|
||||
};
|
||||
using ExprPtr = std::unique_ptr<Expr>;
|
||||
|
||||
struct IntExpr : Expr {
|
||||
long long value;
|
||||
IntExpr(Pos p, long long v) : Expr(ExprKind::Int, p), value(v) {}
|
||||
};
|
||||
struct FloatExpr : Expr {
|
||||
double value;
|
||||
FloatExpr(Pos p, double v) : Expr(ExprKind::Float, p), value(v) {}
|
||||
};
|
||||
struct BoolExpr : Expr {
|
||||
bool value;
|
||||
BoolExpr(Pos p, bool v) : Expr(ExprKind::Bool, p), value(v) {}
|
||||
};
|
||||
struct CharExpr : Expr {
|
||||
char value;
|
||||
CharExpr(Pos p, char v) : Expr(ExprKind::Char, p), value(v) {}
|
||||
};
|
||||
struct StrExpr : Expr {
|
||||
std::string value;
|
||||
StrExpr(Pos p, std::string v) : Expr(ExprKind::Str, p), value(std::move(v)) {}
|
||||
};
|
||||
struct NullExpr : Expr {
|
||||
explicit NullExpr(Pos p) : Expr(ExprKind::Null, p) {}
|
||||
};
|
||||
struct VarExpr : Expr {
|
||||
std::string name;
|
||||
VarExpr(Pos p, std::string n) : Expr(ExprKind::Var, p), name(std::move(n)) {}
|
||||
};
|
||||
struct UnaryExpr : Expr {
|
||||
char op; // '-', '!' or '~'
|
||||
ExprPtr operand;
|
||||
UnaryExpr(Pos p, char o, ExprPtr e) : Expr(ExprKind::Unary, p), op(o), operand(std::move(e)) {}
|
||||
};
|
||||
enum class BinOp {
|
||||
Add, Sub, Mul, Div, Mod,
|
||||
BitAnd, BitOr, BitXor, Shl, Shr,
|
||||
Eq, NotEq, Less, LessEq, Greater, GreaterEq,
|
||||
And, Or,
|
||||
};
|
||||
struct BinaryExpr : Expr {
|
||||
BinOp op;
|
||||
ExprPtr lhs, rhs;
|
||||
BinaryExpr(Pos p, BinOp o, ExprPtr l, ExprPtr r)
|
||||
: Expr(ExprKind::Binary, p), op(o), lhs(std::move(l)), rhs(std::move(r)) {}
|
||||
};
|
||||
struct CallExpr : Expr {
|
||||
std::string callee;
|
||||
std::vector<ExprPtr> args;
|
||||
CallExpr(Pos p, std::string c) : Expr(ExprKind::Call, p), callee(std::move(c)) {}
|
||||
};
|
||||
struct MemberExpr : Expr { // obj.field (for now only p.value)
|
||||
ExprPtr obj;
|
||||
std::string field;
|
||||
MemberExpr(Pos p, ExprPtr o, std::string f)
|
||||
: Expr(ExprKind::Member, p), obj(std::move(o)), field(std::move(f)) {}
|
||||
};
|
||||
|
||||
// ---------- statements ----------
|
||||
|
||||
enum class StmtKind { VarDecl, Assign, Expr, If, While, For, Return, Break, Continue, Block };
|
||||
|
||||
struct Stmt {
|
||||
StmtKind kind;
|
||||
Pos pos;
|
||||
Stmt(StmtKind k, Pos p) : kind(k), pos(p) {}
|
||||
virtual ~Stmt() = default;
|
||||
};
|
||||
using StmtPtr = std::unique_ptr<Stmt>;
|
||||
|
||||
struct BlockStmt : Stmt {
|
||||
std::vector<StmtPtr> body;
|
||||
explicit BlockStmt(Pos p) : Stmt(StmtKind::Block, p) {}
|
||||
};
|
||||
using BlockPtr = std::unique_ptr<BlockStmt>;
|
||||
|
||||
struct VarDeclStmt : Stmt {
|
||||
bool hasType; // `int x = 5` vs `x := 5`
|
||||
Type type;
|
||||
std::string name;
|
||||
ExprPtr init; // may be null only when hasType
|
||||
VarDeclStmt(Pos p) : Stmt(StmtKind::VarDecl, p) {}
|
||||
};
|
||||
struct AssignStmt : Stmt {
|
||||
ExprPtr target; // a variable or p.value
|
||||
char op; // '=', '+', '-', '*', '/', '%'
|
||||
ExprPtr value;
|
||||
AssignStmt(Pos p) : Stmt(StmtKind::Assign, p) {}
|
||||
};
|
||||
struct ExprStmt : Stmt {
|
||||
ExprPtr expr;
|
||||
ExprStmt(Pos p, ExprPtr e) : Stmt(StmtKind::Expr, p), expr(std::move(e)) {}
|
||||
};
|
||||
struct IfStmt : Stmt {
|
||||
ExprPtr cond;
|
||||
BlockPtr then;
|
||||
StmtPtr otherwise; // null, BlockStmt, or IfStmt (else if)
|
||||
IfStmt(Pos p) : Stmt(StmtKind::If, p) {}
|
||||
};
|
||||
struct WhileStmt : Stmt {
|
||||
ExprPtr cond;
|
||||
BlockPtr body;
|
||||
WhileStmt(Pos p) : Stmt(StmtKind::While, p) {}
|
||||
};
|
||||
struct ForStmt : Stmt {
|
||||
std::string var;
|
||||
ExprPtr from, to; // for var in from..to (to is exclusive)
|
||||
BlockPtr body;
|
||||
ForStmt(Pos p) : Stmt(StmtKind::For, p) {}
|
||||
};
|
||||
struct ReturnStmt : Stmt {
|
||||
ExprPtr value; // may be null
|
||||
ReturnStmt(Pos p) : Stmt(StmtKind::Return, p) {}
|
||||
};
|
||||
struct BreakStmt : Stmt {
|
||||
explicit BreakStmt(Pos p) : Stmt(StmtKind::Break, p) {}
|
||||
};
|
||||
struct ContinueStmt : Stmt {
|
||||
explicit ContinueStmt(Pos p) : Stmt(StmtKind::Continue, p) {}
|
||||
};
|
||||
|
||||
// ---------- top level ----------
|
||||
|
||||
struct Param {
|
||||
Type type;
|
||||
std::string name;
|
||||
Pos pos;
|
||||
};
|
||||
|
||||
struct FnDecl {
|
||||
Pos pos;
|
||||
std::string name;
|
||||
Type ret; // Void when there is no `-> type`
|
||||
std::vector<Param> params;
|
||||
BlockPtr body;
|
||||
};
|
||||
|
||||
struct Import {
|
||||
Pos pos;
|
||||
std::string path; // "stdio.h" for C headers
|
||||
};
|
||||
|
||||
struct Link {
|
||||
Pos pos;
|
||||
std::string lib; // "glfw" -> libglfw.so
|
||||
};
|
||||
|
||||
struct Program {
|
||||
std::vector<Import> imports;
|
||||
std::vector<Link> links;
|
||||
std::vector<FnDecl> fns;
|
||||
};
|
||||
246
src/cimport.cpp
Normal file
246
src/cimport.cpp
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
#include "cimport.h"
|
||||
#include "error.h"
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
|
||||
std::vector<std::string> systemIncludeDirs() {
|
||||
const char *cc = std::getenv("CC");
|
||||
std::string cmd = std::string(cc ? cc : "cc") + " -E -v -x c /dev/null 2>&1";
|
||||
std::vector<std::string> dirs;
|
||||
FILE *p = popen(cmd.c_str(), "r");
|
||||
if (!p) return dirs;
|
||||
char line[4096];
|
||||
bool inList = false;
|
||||
while (std::fgets(line, sizeof line, p)) {
|
||||
std::string l = line;
|
||||
if (l.rfind("#include <...> search starts here:", 0) == 0) { inList = true; continue; }
|
||||
if (l.rfind("End of search list.", 0) == 0) break;
|
||||
if (!inList) continue;
|
||||
size_t a = l.find_first_not_of(' ');
|
||||
size_t z = l.find_last_not_of(" \n");
|
||||
if (a == std::string::npos) continue;
|
||||
std::string d = l.substr(a, z - a + 1);
|
||||
if (d.size() > 20 && d.compare(d.size() - 20, 20, " (framework directory)") == 0) continue;
|
||||
dirs.push_back(d);
|
||||
}
|
||||
pclose(p);
|
||||
return dirs;
|
||||
}
|
||||
|
||||
std::string str(CXString s) {
|
||||
std::string out = clang_getCString(s) ? clang_getCString(s) : "";
|
||||
clang_disposeString(s);
|
||||
return out;
|
||||
}
|
||||
|
||||
// C type -> Finch type. Returns false (with a reason) if Finch can't express it yet.
|
||||
bool mapType(CXType t, Type &out, std::string &why) {
|
||||
t = clang_getCanonicalType(t);
|
||||
long long size = clang_Type_getSizeOf(t);
|
||||
auto sized = [&](bool isSigned) {
|
||||
switch (size) {
|
||||
case 1: out = isSigned ? Type::I8 : Type::U8; return true;
|
||||
case 2: out = isSigned ? Type::I16 : Type::U16; return true;
|
||||
case 4: out = isSigned ? Type::I32 : Type::U32; return true;
|
||||
case 8: out = isSigned ? Type::I64 : Type::U64; return true;
|
||||
}
|
||||
why = "uses a " + std::to_string(size * 8) + "-bit number";
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (t.kind) {
|
||||
case CXType_Void: out = Type::Void; return true;
|
||||
case CXType_Bool: out = Type::Bool; return true;
|
||||
case CXType_Char_S:
|
||||
case CXType_Char_U: out = Type::Char; return true;
|
||||
case CXType_SChar:
|
||||
case CXType_Short:
|
||||
case CXType_Int:
|
||||
case CXType_Long:
|
||||
case CXType_LongLong: return sized(true);
|
||||
case CXType_UChar:
|
||||
case CXType_UShort:
|
||||
case CXType_UInt:
|
||||
case CXType_ULong:
|
||||
case CXType_ULongLong: return sized(false);
|
||||
case CXType_Enum: return sized(true);
|
||||
case CXType_Float: out = Type::F32; return true;
|
||||
case CXType_Double: out = Type::F64; return true;
|
||||
case CXType_Pointer: {
|
||||
CXType pointee = clang_getCanonicalType(clang_getPointeeType(t));
|
||||
if (pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) {
|
||||
out = Type::Str; // char* is a C string
|
||||
return true;
|
||||
}
|
||||
Type inner;
|
||||
std::string ignored;
|
||||
// pointers to structs, functions, void...: an opaque `ptr` you can pass around
|
||||
if (pointee.kind == CXType_Void || !mapType(pointee, inner, ignored) || inner.kind == Type::Void)
|
||||
out = Type::Ptr;
|
||||
else
|
||||
out = Type::ptrTo(inner);
|
||||
return true;
|
||||
}
|
||||
case CXType_Record: why = "passes a C struct by value (structs are coming)"; return false;
|
||||
case CXType_LongDouble: why = "uses long double"; return false;
|
||||
default: why = "uses the C type '" + str(clang_getTypeSpelling(t)) + "'"; return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple #defines: a single number, maybe negative, maybe in parentheses.
|
||||
bool macroValue(CXTranslationUnit tu, CXCursor c, CConst &out) {
|
||||
CXToken *toks;
|
||||
unsigned n;
|
||||
clang_tokenize(tu, clang_getCursorExtent(c), &toks, &n);
|
||||
std::vector<std::string> parts;
|
||||
for (unsigned k = 1; k < n; k++) { // token 0 is the macro's name
|
||||
if (clang_getTokenKind(toks[k]) == CXToken_Comment) continue;
|
||||
parts.push_back(str(clang_getTokenSpelling(tu, toks[k])));
|
||||
}
|
||||
clang_disposeTokens(tu, toks, n);
|
||||
|
||||
while (parts.size() >= 2 && parts.front() == "(" && parts.back() == ")") {
|
||||
parts.erase(parts.begin());
|
||||
parts.pop_back();
|
||||
}
|
||||
bool neg = false;
|
||||
if (parts.size() == 2 && parts[0] == "-") {
|
||||
neg = true;
|
||||
parts.erase(parts.begin());
|
||||
}
|
||||
if (parts.size() != 1) return false;
|
||||
const std::string &lit = parts[0];
|
||||
if (lit.empty() || !(std::isdigit((unsigned char)lit[0]) || lit[0] == '.')) return false;
|
||||
|
||||
bool isFloat = lit.find_first_of(".eE") != std::string::npos && lit.rfind("0x", 0) != 0;
|
||||
char *end;
|
||||
if (isFloat) {
|
||||
out.type = Type::F64;
|
||||
out.f = std::strtod(lit.c_str(), &end);
|
||||
if (neg) out.f = -out.f;
|
||||
} else {
|
||||
out.type = Type::I64;
|
||||
out.i = (long long)std::strtoull(lit.c_str(), &end, 0); // C rules: 0x.., 0755
|
||||
if (neg) out.i = -out.i;
|
||||
}
|
||||
for (; *end; end++) // allow suffixes: 10UL, 1.0f
|
||||
if (!std::strchr("uUlLfF", *end)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct Visit {
|
||||
CImports *out;
|
||||
CXTranslationUnit tu;
|
||||
std::string header;
|
||||
};
|
||||
|
||||
CXChildVisitResult visit(CXCursor c, CXCursor, CXClientData data) {
|
||||
Visit &v = *static_cast<Visit *>(data);
|
||||
CImports &out = *v.out;
|
||||
std::string name = str(clang_getCursorSpelling(c));
|
||||
|
||||
switch (clang_getCursorKind(c)) {
|
||||
case CXCursor_FunctionDecl: {
|
||||
if (out.fns.count(name)) break;
|
||||
CFunc f;
|
||||
f.name = name;
|
||||
f.header = v.header;
|
||||
if (clang_Cursor_getStorageClass(c) == CX_SC_Static) {
|
||||
f.unsupported = "is a static inline C function (it has no code to link against)";
|
||||
out.fns[name] = f;
|
||||
break;
|
||||
}
|
||||
CXType ft = clang_getCanonicalType(clang_getCursorType(c));
|
||||
if (ft.kind == CXType_FunctionNoProto) {
|
||||
f.unsupported = "has no parameter list in its C declaration";
|
||||
out.fns[name] = f;
|
||||
break;
|
||||
}
|
||||
std::string why;
|
||||
if (!mapType(clang_getResultType(ft), f.ret, why)) f.unsupported = why;
|
||||
int n = clang_getNumArgTypes(ft);
|
||||
for (int k = 0; k < n && f.unsupported.empty(); k++) {
|
||||
Type t;
|
||||
if (!mapType(clang_getArgType(ft, k), t, why)) f.unsupported = why;
|
||||
f.params.push_back(t);
|
||||
}
|
||||
f.variadic = clang_isFunctionTypeVariadic(ft);
|
||||
out.fns[name] = f;
|
||||
break;
|
||||
}
|
||||
case CXCursor_VarDecl: {
|
||||
if (clang_Cursor_getStorageClass(c) != CX_SC_Extern || out.globals.count(name)) break;
|
||||
CGlobal g;
|
||||
std::string why;
|
||||
if (mapType(clang_getCursorType(c), g.type, why)) out.globals[name] = g;
|
||||
break;
|
||||
}
|
||||
case CXCursor_EnumDecl:
|
||||
clang_visitChildren(c, visit, data);
|
||||
break;
|
||||
case CXCursor_EnumConstantDecl: {
|
||||
CConst k;
|
||||
k.type = Type::I64;
|
||||
k.i = clang_getEnumConstantDeclValue(c);
|
||||
out.consts.emplace(name, k);
|
||||
break;
|
||||
}
|
||||
case CXCursor_MacroDefinition: {
|
||||
if (clang_Cursor_isMacroBuiltin(c) || clang_Cursor_isMacroFunctionLike(c)) break;
|
||||
CConst k;
|
||||
if (macroValue(v.tu, c, k)) out.consts.emplace(name, k);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return CXChildVisit_Continue;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CImports importHeaders(const std::vector<Import> &imports, const std::string &dir) {
|
||||
CImports out;
|
||||
if (imports.empty()) return out;
|
||||
|
||||
std::vector<std::string> args = {"-x", "c", "-std=gnu11", "-I" + dir};
|
||||
for (const std::string &d : systemIncludeDirs()) args.push_back("-isystem" + d);
|
||||
std::vector<const char *> argv;
|
||||
for (const std::string &a : args) argv.push_back(a.c_str());
|
||||
|
||||
CXIndex index = clang_createIndex(0, 0);
|
||||
for (const Import &im : imports) {
|
||||
std::string src = "#include \"" + im.path + "\"\n";
|
||||
CXUnsavedFile file = {"finch_import.c", src.c_str(), (unsigned long)src.size()};
|
||||
CXTranslationUnit tu = nullptr;
|
||||
CXErrorCode err = clang_parseTranslationUnit2(
|
||||
index, "finch_import.c", argv.data(), (int)argv.size(), &file, 1,
|
||||
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies, &tu);
|
||||
if (err != CXError_Success || !tu) fail(im.pos.line, im.pos.col, "couldn't read C header '" + im.path + "'");
|
||||
|
||||
for (unsigned k = 0; k < clang_getNumDiagnostics(tu); k++) {
|
||||
CXDiagnostic d = clang_getDiagnostic(tu, k);
|
||||
if (clang_getDiagnosticSeverity(d) >= CXDiagnostic_Error) {
|
||||
std::string msg = str(clang_getDiagnosticSpelling(d));
|
||||
clang_disposeDiagnostic(d);
|
||||
if (msg.find("file not found") != std::string::npos)
|
||||
fail(im.pos.line, im.pos.col, "can't find the C header '" + im.path + "'");
|
||||
fail(im.pos.line, im.pos.col, "C header '" + im.path + "' has an error: " + msg);
|
||||
}
|
||||
clang_disposeDiagnostic(d);
|
||||
}
|
||||
|
||||
Visit v{&out, tu, im.path};
|
||||
clang_visitChildren(clang_getTranslationUnitCursor(tu), visit, &v);
|
||||
clang_disposeTranslationUnit(tu);
|
||||
}
|
||||
clang_disposeIndex(index);
|
||||
return out;
|
||||
}
|
||||
37
src/cimport.h
Normal file
37
src/cimport.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "ast.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// What Finch learned from `import "something.h"`.
|
||||
|
||||
struct CFunc {
|
||||
std::string name;
|
||||
std::string header; // the import it came from
|
||||
Type ret;
|
||||
std::vector<Type> params;
|
||||
bool variadic = false;
|
||||
std::string unsupported; // non-empty: why Finch can't call it (yet)
|
||||
};
|
||||
|
||||
struct CConst { // enum values and simple #define numbers
|
||||
Type type;
|
||||
long long i = 0;
|
||||
double f = 0;
|
||||
};
|
||||
|
||||
struct CGlobal { // extern variables like stdout
|
||||
Type type;
|
||||
};
|
||||
|
||||
struct CImports {
|
||||
std::unordered_map<std::string, CFunc> fns;
|
||||
std::unordered_map<std::string, CConst> consts;
|
||||
std::unordered_map<std::string, CGlobal> globals;
|
||||
};
|
||||
|
||||
// `dir` is the folder of the .fn file, searched first for "local.h" headers.
|
||||
CImports importHeaders(const std::vector<Import> &imports, const std::string &dir);
|
||||
887
src/codegen.cpp
Normal file
887
src/codegen.cpp
Normal file
|
|
@ -0,0 +1,887 @@
|
|||
#include "codegen.h"
|
||||
#include "error.h"
|
||||
|
||||
#include <llvm/IR/Constants.h>
|
||||
#include <llvm/IR/IRBuilder.h>
|
||||
#include <llvm/IR/Verifier.h>
|
||||
#include <llvm/Support/raw_ostream.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace llvm;
|
||||
using FType = ::Type; // Finch types (llvm::Type is the LLVM one)
|
||||
|
||||
namespace {
|
||||
|
||||
struct Value_ {
|
||||
Value *v;
|
||||
FType type;
|
||||
bool literal = false; // a number written in the code (or a C constant): adapts to the type around it
|
||||
};
|
||||
|
||||
struct Var {
|
||||
AllocaInst *slot;
|
||||
FType type;
|
||||
bool readonly; // loop variables
|
||||
};
|
||||
|
||||
struct Fn {
|
||||
Function *llvm;
|
||||
const FnDecl *decl;
|
||||
};
|
||||
|
||||
struct Loop {
|
||||
BasicBlock *continueTo, *breakTo;
|
||||
};
|
||||
|
||||
struct Place { // something that can be assigned to: a variable or p.value
|
||||
Value *addr;
|
||||
FType type;
|
||||
};
|
||||
|
||||
class Codegen {
|
||||
public:
|
||||
Codegen(LLVMContext &c, const CImports &ci, TargetMachine &tm)
|
||||
: ctx(c), b(c), mod(std::make_unique<Module>("finch", c)), cimports(ci) {
|
||||
mod->setTargetTriple(tm.getTargetTriple());
|
||||
mod->setDataLayout(tm.createDataLayout());
|
||||
}
|
||||
|
||||
std::unique_ptr<Module> run(const Program &prog) {
|
||||
// Declare every function first, so order in the file doesn't matter.
|
||||
for (const FnDecl &fn : prog.fns) declare(fn);
|
||||
if (!fns.count("main")) fail(1, 1, "there is no main function (add 'fn main() { ... }')");
|
||||
for (const FnDecl &fn : prog.fns) define(fn);
|
||||
|
||||
std::string err;
|
||||
raw_string_ostream os(err);
|
||||
if (verifyModule(*mod, &os)) {
|
||||
std::fprintf(stderr, "internal compiler error (please report):\n%s\n", err.c_str());
|
||||
std::exit(2);
|
||||
}
|
||||
return std::move(mod);
|
||||
}
|
||||
|
||||
private:
|
||||
LLVMContext &ctx;
|
||||
IRBuilder<> b;
|
||||
std::unique_ptr<Module> mod;
|
||||
const CImports &cimports;
|
||||
|
||||
std::unordered_map<std::string, Fn> fns;
|
||||
std::vector<std::unordered_map<std::string, Var>> scopes;
|
||||
std::vector<Loop> loops;
|
||||
const FnDecl *curFn = nullptr;
|
||||
|
||||
// ---------- types ----------
|
||||
|
||||
llvm::Type *ty(const FType &t) {
|
||||
switch (t.kind) {
|
||||
case FType::Void: return b.getVoidTy();
|
||||
case FType::Bool: return b.getInt1Ty();
|
||||
case FType::Char:
|
||||
case FType::I8:
|
||||
case FType::U8: return b.getInt8Ty();
|
||||
case FType::I16:
|
||||
case FType::U16: return b.getInt16Ty();
|
||||
case FType::I32:
|
||||
case FType::U32: return b.getInt32Ty();
|
||||
case FType::I64:
|
||||
case FType::U64: return b.getInt64Ty();
|
||||
case FType::F32: return b.getFloatTy();
|
||||
case FType::F64: return b.getDoubleTy();
|
||||
case FType::Str:
|
||||
case FType::Ptr:
|
||||
case FType::Null: return b.getPtrTy();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Constant *zero(const FType &t) {
|
||||
if (t.kind == FType::Str) return cast<Constant>(b.CreateGlobalString("", "empty"));
|
||||
return Constant::getNullValue(ty(t));
|
||||
}
|
||||
|
||||
// Can every value of `from` be stored in `to`? (i8 -> i32 yes, i32 -> u32 no)
|
||||
static bool widens(const FType &from, const FType &to) {
|
||||
if (!from.isInt() || !to.isInt()) return false;
|
||||
if (from.isSigned() && to.isUnsigned()) return false;
|
||||
if (from.isSigned() == to.isSigned()) return to.bits() > from.bits();
|
||||
return to.bits() > from.bits(); // unsigned -> bigger signed
|
||||
}
|
||||
|
||||
// Does the constant number `c` (of type `from`) fit into `to`?
|
||||
static bool fits(ConstantInt *c, const FType &from, const FType &to) {
|
||||
if (!to.isInt()) return false;
|
||||
bool negative = from.isSigned() && c->isNegative();
|
||||
if (negative && to.isUnsigned()) return false;
|
||||
if (negative) return c->getSExtValue() >= -(1LL << (to.bits() - 1));
|
||||
unsigned long long v = c->getZExtValue();
|
||||
if (to.bits() == 64) return to.isUnsigned() || v <= (unsigned long long)INT64_MAX;
|
||||
unsigned long long max = to.isUnsigned() ? (1ULL << to.bits()) - 1 : (1ULL << (to.bits() - 1)) - 1;
|
||||
return v <= max;
|
||||
}
|
||||
|
||||
Value *intCast(Value *v, const FType &from, const FType &to) {
|
||||
return b.CreateIntCast(v, ty(to), from.isSigned());
|
||||
}
|
||||
|
||||
Value *intToFloat(Value *v, const FType &from, const FType &to) {
|
||||
return from.isSigned() ? b.CreateSIToFP(v, ty(to)) : b.CreateUIToFP(v, ty(to));
|
||||
}
|
||||
|
||||
// Implicit conversions: only the ones that can't surprise you.
|
||||
Value *coerce(Value_ v, const FType &want, Pos p, const std::string &what) {
|
||||
const FType &from = v.type;
|
||||
if (from == want) return v.v;
|
||||
if (from.kind == FType::Null && (want.isPtr() || want.kind == FType::Str))
|
||||
return ConstantPointerNull::get(b.getPtrTy());
|
||||
// any pointer (or str) fits an untyped `ptr`; an untyped ptr fits any pointer
|
||||
if (want.isPtr() && !want.elem && (from.isPtr() || from.kind == FType::Str)) return v.v;
|
||||
if (from.isPtr() && !from.elem && want.isPtr()) return v.v;
|
||||
|
||||
if (from.isInt() && want.isInt()) {
|
||||
auto *c = v.literal ? dyn_cast<ConstantInt>(v.v) : nullptr;
|
||||
if ((c && fits(c, from, want)) || widens(from, want)) return intCast(v.v, from, want);
|
||||
if (c) fail(p.line, p.col, "the number " + std::to_string(from.isSigned() ? c->getSExtValue() : (long long)c->getZExtValue()) +
|
||||
" doesn't fit in " + want.name());
|
||||
}
|
||||
if (from.isInt() && want.isFloat()) return intToFloat(v.v, from, want);
|
||||
if (from.kind == FType::F32 && want.kind == FType::F64) return b.CreateFPExt(v.v, ty(want));
|
||||
if (from.kind == FType::F64 && want.kind == FType::F32 && v.literal)
|
||||
return b.CreateFPTrunc(v.v, ty(want));
|
||||
|
||||
std::string msg = what + " must be " + want.name() + ", but this is " + from.name();
|
||||
if (from.isNumber() && want.isNumber())
|
||||
msg += " (use " + want.name() + "(...) to convert" + (from.isFloat() && want.isInt() ? ", it cuts off the fraction)" : ")");
|
||||
if (from.kind == FType::Null) msg = what + " can't be null (only pointers can)";
|
||||
fail(p.line, p.col, msg);
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
bool terminated() { return b.GetInsertBlock()->getTerminator() != nullptr; }
|
||||
|
||||
BasicBlock *newBlock(const char *name) {
|
||||
return BasicBlock::Create(ctx, name, b.GetInsertBlock()->getParent());
|
||||
}
|
||||
|
||||
// Variables live in stack slots created at the top of the function; LLVM turns them into registers.
|
||||
AllocaInst *slot(const FType &t, const std::string &name) {
|
||||
Function *f = b.GetInsertBlock()->getParent();
|
||||
IRBuilder<> entry(&f->getEntryBlock(), f->getEntryBlock().begin());
|
||||
return entry.CreateAlloca(ty(t), nullptr, name);
|
||||
}
|
||||
|
||||
Var *lookup(const std::string &name) {
|
||||
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) {
|
||||
auto found = it->find(name);
|
||||
if (found != it->end()) return &found->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void addVar(Pos p, const std::string &name, const FType &t, Value *init, bool readonly = false) {
|
||||
if (lookup(name)) fail(p.line, p.col, "a variable named '" + name + "' already exists here");
|
||||
if (fns.count(name)) fail(p.line, p.col, "'" + name + "' is already the name of a function");
|
||||
if (isBuiltin(name)) fail(p.line, p.col, "'" + name + "' is a built-in name, pick another one");
|
||||
AllocaInst *s = slot(t, name);
|
||||
b.CreateStore(init, s);
|
||||
scopes.back()[name] = {s, t, readonly};
|
||||
}
|
||||
|
||||
Function *libc(const char *name, llvm::Type *ret, std::vector<llvm::Type *> params, bool vararg = false) {
|
||||
return cast<Function>(mod->getOrInsertFunction(name, FunctionType::get(ret, params, vararg)).getCallee());
|
||||
}
|
||||
|
||||
// ---------- functions ----------
|
||||
|
||||
void declare(const FnDecl &fn) {
|
||||
if (fns.count(fn.name)) fail(fn.pos.line, fn.pos.col, "function '" + fn.name + "' is defined twice");
|
||||
if (isBuiltin(fn.name)) fail(fn.pos.line, fn.pos.col, "'" + fn.name + "' is a built-in function, pick another name");
|
||||
|
||||
bool isMain = fn.name == "main";
|
||||
if (isMain && !fn.params.empty()) fail(fn.pos.line, fn.pos.col, "main takes no parameters");
|
||||
if (isMain && fn.ret.kind != FType::Void && fn.ret.kind != FType::I64 && fn.ret.kind != FType::I32)
|
||||
fail(fn.pos.line, fn.pos.col, "main returns nothing (fn main()) or an exit code (fn main() -> int)");
|
||||
|
||||
std::vector<llvm::Type *> params;
|
||||
for (const Param &p : fn.params) params.push_back(ty(p.type));
|
||||
llvm::Type *ret = isMain ? b.getInt32Ty() : ty(fn.ret);
|
||||
|
||||
// User functions get a prefix so they never clash with C library names.
|
||||
Function *f = Function::Create(FunctionType::get(ret, params, false),
|
||||
isMain ? Function::ExternalLinkage : Function::InternalLinkage,
|
||||
isMain ? "main" : "finch." + fn.name, mod.get());
|
||||
fns[fn.name] = {f, &fn};
|
||||
}
|
||||
|
||||
void define(const FnDecl &fn) {
|
||||
Function *f = fns[fn.name].llvm;
|
||||
curFn = &fn;
|
||||
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", f));
|
||||
|
||||
scopes.push_back({});
|
||||
unsigned i = 0;
|
||||
for (Argument &arg : f->args()) {
|
||||
const Param &p = fn.params[i++];
|
||||
arg.setName(p.name);
|
||||
addVar(p.pos, p.name, p.type, &arg);
|
||||
}
|
||||
blockBody(*fn.body);
|
||||
scopes.pop_back();
|
||||
|
||||
if (!terminated()) {
|
||||
if (fn.name == "main") b.CreateRet(b.getInt32(0));
|
||||
else if (fn.ret.kind == FType::Void) b.CreateRetVoid();
|
||||
else fail(fn.pos.line, fn.pos.col, "function '" + fn.name + "' can reach its end without returning " + fn.ret.name());
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- statements ----------
|
||||
|
||||
void block(const BlockStmt &blk) {
|
||||
scopes.push_back({});
|
||||
blockBody(blk);
|
||||
scopes.pop_back();
|
||||
}
|
||||
|
||||
void blockBody(const BlockStmt &blk) {
|
||||
for (const StmtPtr &s : blk.body) {
|
||||
if (terminated()) fail(s->pos.line, s->pos.col, "this code can never run (it comes after return, break or continue)");
|
||||
stmt(*s);
|
||||
}
|
||||
}
|
||||
|
||||
void stmt(const Stmt &s) {
|
||||
switch (s.kind) {
|
||||
case StmtKind::Block: block(static_cast<const BlockStmt &>(s)); break;
|
||||
case StmtKind::VarDecl: varDecl(static_cast<const VarDeclStmt &>(s)); break;
|
||||
case StmtKind::Assign: assign(static_cast<const AssignStmt &>(s)); break;
|
||||
case StmtKind::Expr: expr(*static_cast<const ExprStmt &>(s).expr); break;
|
||||
case StmtKind::If: ifStmt(static_cast<const IfStmt &>(s)); break;
|
||||
case StmtKind::While: whileStmt(static_cast<const WhileStmt &>(s)); break;
|
||||
case StmtKind::For: forStmt(static_cast<const ForStmt &>(s)); break;
|
||||
case StmtKind::Return: returnStmt(static_cast<const ReturnStmt &>(s)); break;
|
||||
case StmtKind::Break:
|
||||
case StmtKind::Continue: {
|
||||
bool isBreak = s.kind == StmtKind::Break;
|
||||
if (loops.empty()) fail(s.pos.line, s.pos.col, std::string(isBreak ? "break" : "continue") + " can only be used inside a loop");
|
||||
b.CreateBr(isBreak ? loops.back().breakTo : loops.back().continueTo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void varDecl(const VarDeclStmt &s) {
|
||||
if (!s.hasType) {
|
||||
Value_ v = expr(*s.init);
|
||||
if (v.type.kind == FType::Void) fail(s.init->pos.line, s.init->pos.col, "this gives back nothing, so it can't be stored");
|
||||
if (v.type.kind == FType::Null) fail(s.init->pos.line, s.init->pos.col, "null needs a type, like: ptr[int] " + s.name + " = null");
|
||||
addVar(s.pos, s.name, v.type, v.v);
|
||||
return;
|
||||
}
|
||||
Value *init = s.init ? coerce(expr(*s.init), s.type, s.init->pos, "'" + s.name + "'") : zero(s.type);
|
||||
addVar(s.pos, s.name, s.type, init);
|
||||
}
|
||||
|
||||
// Where does `x` or `p.value` live in memory?
|
||||
Place place(const Expr &e) {
|
||||
if (e.kind == ExprKind::Var) {
|
||||
auto &v = static_cast<const VarExpr &>(e);
|
||||
Var *var = lookup(v.name);
|
||||
if (!var) {
|
||||
if (cimports.globals.count(v.name)) return {cGlobal(v.name), cimports.globals.at(v.name).type};
|
||||
fail(e.pos.line, e.pos.col, "there is no variable named '" + v.name + "'");
|
||||
}
|
||||
return {var->slot, var->type};
|
||||
}
|
||||
if (e.kind == ExprKind::Member) {
|
||||
auto &m = static_cast<const MemberExpr &>(e);
|
||||
Value_ obj = expr(*m.obj);
|
||||
if (!obj.type.isPtr())
|
||||
fail(m.pos.line, m.pos.col, obj.type.name() + " has no '." + m.field + "'");
|
||||
if (m.field != "value")
|
||||
fail(m.pos.line, m.pos.col, "a pointer only has '.value' (the thing it points to)");
|
||||
if (!obj.type.elem)
|
||||
fail(m.pos.line, m.pos.col, "this ptr has no type, so Finch doesn't know what's inside (use ptr[int] etc.)");
|
||||
// reading or writing through null is undefined in C; in Finch it stops the program
|
||||
if (!isa<AllocaInst>(obj.v) && !isa<GlobalValue>(obj.v))
|
||||
panicIf(b.CreateIsNull(obj.v), "used .value on a null pointer", m.pos);
|
||||
return {obj.v, *obj.type.elem};
|
||||
}
|
||||
fail(e.pos.line, e.pos.col, "this can't be assigned to");
|
||||
}
|
||||
|
||||
void assign(const AssignStmt &s) {
|
||||
if (s.target->kind == ExprKind::Var) {
|
||||
auto &name = static_cast<const VarExpr &>(*s.target).name;
|
||||
Var *var = lookup(name);
|
||||
if (!var && !cimports.globals.count(name))
|
||||
fail(s.pos.line, s.pos.col, "there is no variable named '" + name + "' (create it with " + name + " := ...)");
|
||||
if (var && var->readonly) fail(s.pos.line, s.pos.col, "the loop variable '" + name + "' can't be changed");
|
||||
}
|
||||
Place dst = place(*s.target);
|
||||
std::string what = s.target->kind == ExprKind::Var ? "'" + static_cast<const VarExpr &>(*s.target).name + "'" : "the value";
|
||||
|
||||
Value *v;
|
||||
if (s.op == '=') {
|
||||
v = coerce(expr(*s.value), dst.type, s.value->pos, what);
|
||||
} else {
|
||||
BinOp op = s.op == '+' ? BinOp::Add : s.op == '-' ? BinOp::Sub : s.op == '*' ? BinOp::Mul
|
||||
: s.op == '/' ? BinOp::Div : BinOp::Mod;
|
||||
Value_ cur{b.CreateLoad(ty(dst.type), dst.addr), dst.type};
|
||||
v = coerce(arith(op, cur, expr(*s.value), s.pos), dst.type, s.pos, what);
|
||||
}
|
||||
b.CreateStore(v, dst.addr);
|
||||
}
|
||||
|
||||
Value *condition(const Expr &e) { return coerce(expr(e), FType::Bool, e.pos, "a condition"); }
|
||||
|
||||
void ifStmt(const IfStmt &s) {
|
||||
Value *cond = condition(*s.cond);
|
||||
BasicBlock *thenBB = newBlock("then");
|
||||
BasicBlock *elseBB = s.otherwise ? newBlock("else") : nullptr;
|
||||
BasicBlock *end = newBlock("endif");
|
||||
b.CreateCondBr(cond, thenBB, elseBB ? elseBB : end);
|
||||
|
||||
b.SetInsertPoint(thenBB);
|
||||
block(*s.then);
|
||||
BasicBlock *last = b.GetInsertBlock();
|
||||
if (!terminated()) b.CreateBr(end);
|
||||
|
||||
if (elseBB) {
|
||||
b.SetInsertPoint(elseBB);
|
||||
stmt(*s.otherwise);
|
||||
last = b.GetInsertBlock();
|
||||
if (!terminated()) b.CreateBr(end);
|
||||
}
|
||||
continueAt(end, last);
|
||||
}
|
||||
|
||||
// Continue emitting into `bb`; if nothing jumps there, drop it and stay "after a return".
|
||||
void continueAt(BasicBlock *bb, BasicBlock *deadEnd) {
|
||||
if (pred_empty(bb)) {
|
||||
bb->eraseFromParent();
|
||||
b.SetInsertPoint(deadEnd);
|
||||
} else {
|
||||
b.SetInsertPoint(bb);
|
||||
}
|
||||
}
|
||||
|
||||
void whileStmt(const WhileStmt &s) {
|
||||
BasicBlock *condBB = newBlock("while.cond");
|
||||
BasicBlock *body = newBlock("while.body");
|
||||
BasicBlock *end = newBlock("while.end");
|
||||
b.CreateBr(condBB);
|
||||
|
||||
b.SetInsertPoint(condBB);
|
||||
Value *cond = condition(*s.cond);
|
||||
if (auto *c = dyn_cast<ConstantInt>(cond); c && c->isOne()) b.CreateBr(body); // while true
|
||||
else b.CreateCondBr(cond, body, end);
|
||||
|
||||
b.SetInsertPoint(body);
|
||||
loops.push_back({condBB, end});
|
||||
block(*s.body);
|
||||
loops.pop_back();
|
||||
if (!terminated()) b.CreateBr(condBB);
|
||||
continueAt(end, b.GetInsertBlock());
|
||||
}
|
||||
|
||||
void forStmt(const ForStmt &s) {
|
||||
Value *from = coerce(expr(*s.from), FType::I64, s.from->pos, "the start of a range");
|
||||
Value *to = coerce(expr(*s.to), FType::I64, s.to->pos, "the end of a range");
|
||||
|
||||
scopes.push_back({});
|
||||
addVar(s.pos, s.var, FType::I64, from, true);
|
||||
AllocaInst *i = scopes.back()[s.var].slot;
|
||||
|
||||
BasicBlock *condBB = newBlock("for.cond");
|
||||
BasicBlock *body = newBlock("for.body");
|
||||
BasicBlock *step = newBlock("for.step");
|
||||
BasicBlock *end = newBlock("for.end");
|
||||
b.CreateBr(condBB);
|
||||
|
||||
b.SetInsertPoint(condBB);
|
||||
b.CreateCondBr(b.CreateICmpSLT(b.CreateLoad(b.getInt64Ty(), i), to), body, end);
|
||||
|
||||
b.SetInsertPoint(body);
|
||||
loops.push_back({step, end});
|
||||
block(*s.body);
|
||||
loops.pop_back();
|
||||
if (!terminated()) b.CreateBr(step);
|
||||
|
||||
b.SetInsertPoint(step);
|
||||
b.CreateStore(b.CreateAdd(b.CreateLoad(b.getInt64Ty(), i), b.getInt64(1)), i);
|
||||
b.CreateBr(condBB);
|
||||
|
||||
scopes.pop_back();
|
||||
b.SetInsertPoint(end);
|
||||
}
|
||||
|
||||
void returnStmt(const ReturnStmt &s) {
|
||||
bool isMain = curFn->name == "main";
|
||||
if (curFn->ret.kind == FType::Void) {
|
||||
if (s.value) fail(s.value->pos.line, s.value->pos.col, "'" + curFn->name + "' doesn't return a value (it has no '-> type')");
|
||||
if (isMain) b.CreateRet(b.getInt32(0));
|
||||
else b.CreateRetVoid();
|
||||
return;
|
||||
}
|
||||
if (!s.value) fail(s.pos.line, s.pos.col, "'" + curFn->name + "' must return " + curFn->ret.name());
|
||||
Value *v = coerce(expr(*s.value), curFn->ret, s.value->pos, "the returned value");
|
||||
b.CreateRet(isMain ? b.CreateIntCast(v, b.getInt32Ty(), true) : v);
|
||||
}
|
||||
|
||||
// ---------- expressions ----------
|
||||
|
||||
Value_ expr(const Expr &e) {
|
||||
switch (e.kind) {
|
||||
case ExprKind::Int: return {b.getInt64(static_cast<const IntExpr &>(e).value), FType::I64, true};
|
||||
case ExprKind::Float: return {ConstantFP::get(b.getDoubleTy(), static_cast<const FloatExpr &>(e).value), FType::F64, true};
|
||||
case ExprKind::Bool: return {b.getInt1(static_cast<const BoolExpr &>(e).value), FType::Bool};
|
||||
case ExprKind::Char: return {b.getInt8(static_cast<const CharExpr &>(e).value), FType::Char};
|
||||
case ExprKind::Str: return {b.CreateGlobalString(static_cast<const StrExpr &>(e).value, "str"), FType::Str};
|
||||
case ExprKind::Null: return {ConstantPointerNull::get(b.getPtrTy()), FType::Null};
|
||||
case ExprKind::Var: {
|
||||
auto &v = static_cast<const VarExpr &>(e);
|
||||
if (!lookup(v.name)) {
|
||||
auto c = cimports.consts.find(v.name);
|
||||
if (c != cimports.consts.end()) {
|
||||
if (c->second.type.isFloat()) return {ConstantFP::get(b.getDoubleTy(), c->second.f), FType::F64, true};
|
||||
return {b.getInt64(c->second.i), FType::I64, true};
|
||||
}
|
||||
}
|
||||
Place pl = place(e);
|
||||
return {b.CreateLoad(ty(pl.type), pl.addr, v.name), pl.type};
|
||||
}
|
||||
case ExprKind::Member: {
|
||||
Place pl = place(e);
|
||||
return {b.CreateLoad(ty(pl.type), pl.addr), pl.type};
|
||||
}
|
||||
case ExprKind::Unary: {
|
||||
auto &u = static_cast<const UnaryExpr &>(e);
|
||||
Value_ v = expr(*u.operand);
|
||||
if (u.op == '!') return {b.CreateNot(coerce(v, FType::Bool, u.operand->pos, "the value after '!'")), FType::Bool};
|
||||
if (u.op == '~') {
|
||||
if (!v.type.isInt()) fail(e.pos.line, e.pos.col, "'~' flips the bits of a whole number, not of " + v.type.name());
|
||||
return {b.CreateNot(v.v), v.type};
|
||||
}
|
||||
if (v.type.isSigned()) return {b.CreateNeg(v.v), v.type, v.literal};
|
||||
if (v.type.isFloat()) return {b.CreateFNeg(v.v), v.type, v.literal};
|
||||
if (v.type.isUnsigned()) fail(e.pos.line, e.pos.col, v.type.name() + " can't be negative (it's unsigned)");
|
||||
fail(e.pos.line, e.pos.col, "can't put '-' in front of " + v.type.name());
|
||||
}
|
||||
case ExprKind::Binary: return binary(static_cast<const BinaryExpr &>(e));
|
||||
case ExprKind::Call: return call(static_cast<const CallExpr &>(e));
|
||||
}
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
|
||||
static const char *opName(BinOp op) {
|
||||
switch (op) {
|
||||
case BinOp::Add: return "+";
|
||||
case BinOp::Sub: return "-";
|
||||
case BinOp::Mul: return "*";
|
||||
case BinOp::Div: return "/";
|
||||
case BinOp::Mod: return "%";
|
||||
case BinOp::BitAnd: return "&";
|
||||
case BinOp::BitOr: return "|";
|
||||
case BinOp::BitXor: return "^";
|
||||
case BinOp::Shl: return "<<";
|
||||
case BinOp::Shr: return ">>";
|
||||
case BinOp::Eq: return "==";
|
||||
case BinOp::NotEq: return "!=";
|
||||
case BinOp::Less: return "<";
|
||||
case BinOp::LessEq: return "<=";
|
||||
case BinOp::Greater: return ">";
|
||||
case BinOp::GreaterEq: return ">=";
|
||||
case BinOp::And: return "&&";
|
||||
case BinOp::Or: return "||";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
[[noreturn]] void badOperands(BinOp op, const Value_ &l, const Value_ &r, Pos p) {
|
||||
std::string msg = std::string("can't use '") + opName(op) + "' on " + l.type.name() +
|
||||
(l.type == r.type ? "" : " and " + r.type.name());
|
||||
if (l.type.isInt() && r.type.isInt() && l.type != r.type) msg += " (convert one side, like " + l.type.name() + "(...))";
|
||||
if (l.type.kind == FType::Bool && (op == BinOp::BitAnd || op == BinOp::BitOr))
|
||||
msg += std::string(" (for true/false use '") + (op == BinOp::BitAnd ? "&&" : "||") + "')";
|
||||
else if ((l.type.isFloat() || r.type.isFloat()) && op >= BinOp::BitAnd && op <= BinOp::Shr)
|
||||
msg += " (bit operators work on whole numbers only)";
|
||||
fail(p.line, p.col, msg);
|
||||
}
|
||||
|
||||
// Bring both sides to one type: a number literal adapts to the other side,
|
||||
// otherwise the smaller type grows into the bigger one.
|
||||
bool unify(Value_ &l, Value_ &r) {
|
||||
if (l.type == r.type) return true;
|
||||
if (l.type.isInt() && r.type.isInt()) {
|
||||
auto *lc = l.literal ? dyn_cast<ConstantInt>(l.v) : nullptr;
|
||||
auto *rc = r.literal ? dyn_cast<ConstantInt>(r.v) : nullptr;
|
||||
if (lc && !rc && fits(lc, l.type, r.type)) { l = {intCast(l.v, l.type, r.type), r.type, true}; return true; }
|
||||
if (rc && !lc && fits(rc, r.type, l.type)) { r = {intCast(r.v, r.type, l.type), l.type, true}; return true; }
|
||||
if (widens(l.type, r.type)) { l = {intCast(l.v, l.type, r.type), r.type}; return true; }
|
||||
if (widens(r.type, l.type)) { r = {intCast(r.v, r.type, l.type), l.type}; return true; }
|
||||
return false;
|
||||
}
|
||||
if (l.type.isInt() && r.type.isFloat()) { l = {intToFloat(l.v, l.type, r.type), r.type, l.literal}; return true; }
|
||||
if (l.type.isFloat() && r.type.isInt()) { r = {intToFloat(r.v, r.type, l.type), l.type, r.literal}; return true; }
|
||||
if (l.type.isFloat() && r.type.isFloat()) {
|
||||
if (l.type.kind == FType::F32) l = {b.CreateFPExt(l.v, b.getDoubleTy()), FType::F64};
|
||||
if (r.type.kind == FType::F32) r = {b.CreateFPExt(r.v, b.getDoubleTy()), FType::F64};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Value_ arith(BinOp op, Value_ l, Value_ r, Pos p) {
|
||||
Value_ v = arithOp(op, l, r, p);
|
||||
v.literal = l.literal && r.literal && isa<Constant>(v.v);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value_ arithOp(BinOp op, Value_ l, Value_ r, Pos p) {
|
||||
if (!unify(l, r)) badOperands(op, l, r, p);
|
||||
if (l.type.isInt()) {
|
||||
bool s = l.type.isSigned();
|
||||
switch (op) {
|
||||
case BinOp::Add: return {b.CreateAdd(l.v, r.v), l.type};
|
||||
case BinOp::Sub: return {b.CreateSub(l.v, r.v), l.type};
|
||||
case BinOp::Mul: return {b.CreateMul(l.v, r.v), l.type};
|
||||
case BinOp::Div:
|
||||
checkDivisor(l, r, p);
|
||||
return {s ? b.CreateSDiv(l.v, r.v) : b.CreateUDiv(l.v, r.v), l.type};
|
||||
case BinOp::Mod:
|
||||
checkDivisor(l, r, p);
|
||||
return {s ? b.CreateSRem(l.v, r.v) : b.CreateURem(l.v, r.v), l.type};
|
||||
case BinOp::BitAnd: return {b.CreateAnd(l.v, r.v), l.type};
|
||||
case BinOp::BitOr: return {b.CreateOr(l.v, r.v), l.type};
|
||||
case BinOp::BitXor: return {b.CreateXor(l.v, r.v), l.type};
|
||||
case BinOp::Shl: return {b.CreateShl(l.v, r.v), l.type};
|
||||
case BinOp::Shr: return {s ? b.CreateAShr(l.v, r.v) : b.CreateLShr(l.v, r.v), l.type};
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (l.type.isFloat()) {
|
||||
switch (op) {
|
||||
case BinOp::Add: return {b.CreateFAdd(l.v, r.v), l.type};
|
||||
case BinOp::Sub: return {b.CreateFSub(l.v, r.v), l.type};
|
||||
case BinOp::Mul: return {b.CreateFMul(l.v, r.v), l.type};
|
||||
case BinOp::Div: return {b.CreateFDiv(l.v, r.v), l.type};
|
||||
case BinOp::Mod: return {b.CreateFRem(l.v, r.v), l.type};
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
badOperands(op, l, r, p);
|
||||
}
|
||||
|
||||
// ---------- runtime errors ----------
|
||||
|
||||
// finch.panic(msg, line): print "file:line: runtime error: msg" and stop the program.
|
||||
Function *panicFn() {
|
||||
if (Function *f = mod->getFunction("finch.panic")) return f;
|
||||
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy(), b.getInt64Ty()}, false),
|
||||
Function::InternalLinkage, "finch.panic", mod.get());
|
||||
f->addFnAttr(Attribute::NoReturn);
|
||||
f->addFnAttr(Attribute::Cold);
|
||||
f->addFnAttr(Attribute::NoInline);
|
||||
IRBuilder<> pb(BasicBlock::Create(ctx, "entry", f));
|
||||
Function *dprintf = libc("dprintf", b.getInt32Ty(), {b.getInt32Ty(), b.getPtrTy()}, true);
|
||||
pb.CreateCall(dprintf, {pb.getInt32(2), pb.CreateGlobalString("%s:%lld: runtime error: %s\n", "panic.fmt"),
|
||||
pb.CreateGlobalString(g_file, "panic.file"), f->getArg(1), f->getArg(0)});
|
||||
pb.CreateCall(libc("exit", b.getVoidTy(), {b.getInt32Ty()}), {pb.getInt32(1)});
|
||||
pb.CreateUnreachable();
|
||||
return f;
|
||||
}
|
||||
|
||||
// if (cond) panic(msg)
|
||||
void panicIf(Value *cond, const char *msg, Pos p) {
|
||||
BasicBlock *bad = newBlock("panic");
|
||||
BasicBlock *ok = newBlock("ok");
|
||||
b.CreateCondBr(cond, bad, ok);
|
||||
b.SetInsertPoint(bad);
|
||||
b.CreateCall(panicFn(), {b.CreateGlobalString(msg, "panic.msg"), b.getInt64(p.line)});
|
||||
b.CreateUnreachable();
|
||||
b.SetInsertPoint(ok);
|
||||
}
|
||||
|
||||
// Dividing by zero is undefined in C; in Finch it is a clear error.
|
||||
void checkDivisor(const Value_ &l, const Value_ &r, Pos p) {
|
||||
auto *c = dyn_cast<ConstantInt>(r.v);
|
||||
if (c && c->isZero()) fail(p.line, p.col, "division by zero");
|
||||
if (!c) panicIf(b.CreateICmpEQ(r.v, ConstantInt::get(r.v->getType(), 0)), "division by zero", p);
|
||||
// the smallest signed number divided by -1 doesn't fit anywhere
|
||||
if (l.type.isSigned() && (!c || c->isMinusOne())) {
|
||||
Value *min = ConstantInt::get(l.v->getType(), APInt::getSignedMinValue(l.type.bits()));
|
||||
Value *minusOne = ConstantInt::get(r.v->getType(), -1, true);
|
||||
panicIf(b.CreateAnd(b.CreateICmpEQ(l.v, min), b.CreateICmpEQ(r.v, minusOne)),
|
||||
"division overflows (the smallest number divided by -1)", p);
|
||||
}
|
||||
}
|
||||
|
||||
Value_ compare(BinOp op, Value_ l, Value_ r, Pos p) {
|
||||
bool equality = op == BinOp::Eq || op == BinOp::NotEq;
|
||||
auto pointerLike = [](const FType &t) { return t.isPtr() || t.kind == FType::Str || t.kind == FType::Null; };
|
||||
|
||||
// Pointers (and `s == null`) compare addresses.
|
||||
if (pointerLike(l.type) && pointerLike(r.type) &&
|
||||
(l.type.isPtr() || r.type.isPtr() || l.type.kind == FType::Null || r.type.kind == FType::Null)) {
|
||||
if (!equality) badOperands(op, l, r, p);
|
||||
bool bothPtr = (l.type.isPtr() || l.type.kind == FType::Null) && (r.type.isPtr() || r.type.kind == FType::Null);
|
||||
bool strNull = (l.type.kind == FType::Str && r.type.kind == FType::Null) || (r.type.kind == FType::Str && l.type.kind == FType::Null);
|
||||
if (!bothPtr && !strNull) badOperands(op, l, r, p);
|
||||
return {op == BinOp::Eq ? b.CreateICmpEQ(l.v, r.v) : b.CreateICmpNE(l.v, r.v), FType::Bool};
|
||||
}
|
||||
|
||||
if (!unify(l, r)) badOperands(op, l, r, p);
|
||||
|
||||
if (l.type.kind == FType::Str) {
|
||||
if (!equality) badOperands(op, l, r, p);
|
||||
Function *strcmp = libc("strcmp", b.getInt32Ty(), {b.getPtrTy(), b.getPtrTy()});
|
||||
Value *c = b.CreateCall(strcmp, {l.v, r.v});
|
||||
return {op == BinOp::Eq ? b.CreateICmpEQ(c, b.getInt32(0)) : b.CreateICmpNE(c, b.getInt32(0)), FType::Bool};
|
||||
}
|
||||
if (l.type.isFloat()) {
|
||||
CmpInst::Predicate pr;
|
||||
switch (op) {
|
||||
case BinOp::Eq: pr = CmpInst::FCMP_OEQ; break;
|
||||
case BinOp::NotEq: pr = CmpInst::FCMP_UNE; break;
|
||||
case BinOp::Less: pr = CmpInst::FCMP_OLT; break;
|
||||
case BinOp::LessEq: pr = CmpInst::FCMP_OLE; break;
|
||||
case BinOp::Greater: pr = CmpInst::FCMP_OGT; break;
|
||||
default: pr = CmpInst::FCMP_OGE; break;
|
||||
}
|
||||
return {b.CreateFCmp(pr, l.v, r.v), FType::Bool};
|
||||
}
|
||||
if (l.type.kind == FType::Bool && !equality) badOperands(op, l, r, p);
|
||||
bool s = l.type.isSigned();
|
||||
CmpInst::Predicate pr;
|
||||
switch (op) {
|
||||
case BinOp::Eq: pr = CmpInst::ICMP_EQ; break;
|
||||
case BinOp::NotEq: pr = CmpInst::ICMP_NE; break;
|
||||
case BinOp::Less: pr = s ? CmpInst::ICMP_SLT : CmpInst::ICMP_ULT; break;
|
||||
case BinOp::LessEq: pr = s ? CmpInst::ICMP_SLE : CmpInst::ICMP_ULE; break;
|
||||
case BinOp::Greater: pr = s ? CmpInst::ICMP_SGT : CmpInst::ICMP_UGT; break;
|
||||
default: pr = s ? CmpInst::ICMP_SGE : CmpInst::ICMP_UGE; break;
|
||||
}
|
||||
return {b.CreateICmp(pr, l.v, r.v), FType::Bool};
|
||||
}
|
||||
|
||||
// && and || only evaluate the right side when needed.
|
||||
Value_ logic(const BinaryExpr &e) {
|
||||
bool isAnd = e.op == BinOp::And;
|
||||
Value *l = condition(*e.lhs);
|
||||
BasicBlock *from = b.GetInsertBlock();
|
||||
BasicBlock *rhsBB = newBlock(isAnd ? "and.rhs" : "or.rhs");
|
||||
BasicBlock *end = newBlock(isAnd ? "and.end" : "or.end");
|
||||
if (isAnd) b.CreateCondBr(l, rhsBB, end);
|
||||
else b.CreateCondBr(l, end, rhsBB);
|
||||
|
||||
b.SetInsertPoint(rhsBB);
|
||||
Value *r = condition(*e.rhs);
|
||||
BasicBlock *rhsEnd = b.GetInsertBlock();
|
||||
b.CreateBr(end);
|
||||
|
||||
b.SetInsertPoint(end);
|
||||
PHINode *phi = b.CreatePHI(b.getInt1Ty(), 2);
|
||||
phi->addIncoming(b.getInt1(!isAnd), from);
|
||||
phi->addIncoming(r, rhsEnd);
|
||||
return {phi, FType::Bool};
|
||||
}
|
||||
|
||||
Value_ binary(const BinaryExpr &e) {
|
||||
switch (e.op) {
|
||||
case BinOp::And:
|
||||
case BinOp::Or: return logic(e);
|
||||
case BinOp::Eq:
|
||||
case BinOp::NotEq:
|
||||
case BinOp::Less:
|
||||
case BinOp::LessEq:
|
||||
case BinOp::Greater:
|
||||
case BinOp::GreaterEq: {
|
||||
Value_ l = expr(*e.lhs);
|
||||
return compare(e.op, l, expr(*e.rhs), e.pos);
|
||||
}
|
||||
default: {
|
||||
Value_ l = expr(*e.lhs);
|
||||
return arith(e.op, l, expr(*e.rhs), e.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- calls & built-ins ----------
|
||||
|
||||
static bool isBuiltin(const std::string &n) {
|
||||
FType t;
|
||||
return n == "print" || n == "addr" || typeFromName(n, t);
|
||||
}
|
||||
|
||||
Value_ call(const CallExpr &c) {
|
||||
if (c.callee == "print") return print(c);
|
||||
if (c.callee == "addr") return addrOf(c);
|
||||
FType target;
|
||||
if (typeFromName(c.callee, target)) return convert(c, target);
|
||||
|
||||
auto it = fns.find(c.callee);
|
||||
if (it != fns.end()) return callFinch(c, it->second);
|
||||
|
||||
auto cf = cimports.fns.find(c.callee);
|
||||
if (cf != cimports.fns.end()) return callC(c, cf->second);
|
||||
|
||||
if (lookup(c.callee)) fail(c.pos.line, c.pos.col, "'" + c.callee + "' is a variable, not a function");
|
||||
fail(c.pos.line, c.pos.col, "there is no function named '" + c.callee + "'" + headerHint(c.callee));
|
||||
}
|
||||
|
||||
// Help people who forgot an import.
|
||||
static std::string headerHint(const std::string &name) {
|
||||
static const std::unordered_map<std::string, const char *> known = {
|
||||
{"printf", "stdio.h"}, {"puts", "stdio.h"}, {"putchar", "stdio.h"}, {"scanf", "stdio.h"},
|
||||
{"fopen", "stdio.h"}, {"getchar", "stdio.h"}, {"sqrt", "math.h"}, {"pow", "math.h"},
|
||||
{"sin", "math.h"}, {"cos", "math.h"}, {"floor", "math.h"}, {"fabs", "math.h"},
|
||||
{"malloc", "stdlib.h"}, {"free", "stdlib.h"}, {"rand", "stdlib.h"}, {"exit", "stdlib.h"},
|
||||
{"abs", "stdlib.h"}, {"atoi", "stdlib.h"}, {"strlen", "string.h"}, {"strcpy", "string.h"},
|
||||
{"memcpy", "string.h"}, {"time", "time.h"}, {"sleep", "unistd.h"},
|
||||
};
|
||||
auto it = known.find(name);
|
||||
return it == known.end() ? "" : std::string(" (it's from C: add import \"") + it->second + "\" at the top)";
|
||||
}
|
||||
|
||||
void checkArgCount(const CallExpr &c, size_t want, bool variadic) {
|
||||
if (c.args.size() == want || (variadic && c.args.size() > want)) return;
|
||||
fail(c.pos.line, c.pos.col, "'" + c.callee + "' takes " + (variadic ? "at least " : "") + std::to_string(want) +
|
||||
" argument(s), but got " + std::to_string(c.args.size()));
|
||||
}
|
||||
|
||||
Value_ callFinch(const CallExpr &c, const Fn &fn) {
|
||||
const FnDecl &d = *fn.decl;
|
||||
if (d.name == "main") fail(c.pos.line, c.pos.col, "main can't be called, it is where the program starts");
|
||||
checkArgCount(c, d.params.size(), false);
|
||||
std::vector<Value *> args;
|
||||
for (size_t i = 0; i < c.args.size(); i++)
|
||||
args.push_back(coerce(expr(*c.args[i]), d.params[i].type, c.args[i]->pos, "argument '" + d.params[i].name + "'"));
|
||||
return {b.CreateCall(fn.llvm, args), d.ret};
|
||||
}
|
||||
|
||||
// C wants small integers widened by the caller: signed ones sign-extended, the rest zero-extended.
|
||||
static Attribute::AttrKind extAttr(const FType &t) {
|
||||
if (t.bits() >= 32 || !(t.isInt() || t.kind == FType::Bool || t.kind == FType::Char)) return Attribute::None;
|
||||
return t.isSigned() || t.kind == FType::Char ? Attribute::SExt : Attribute::ZExt;
|
||||
}
|
||||
|
||||
Value_ callC(const CallExpr &c, const CFunc &f) {
|
||||
if (!f.unsupported.empty())
|
||||
fail(c.pos.line, c.pos.col, "the C function '" + f.name + "' can't be used from Finch yet: it " + f.unsupported);
|
||||
checkArgCount(c, f.params.size(), f.variadic);
|
||||
|
||||
std::vector<llvm::Type *> ptypes;
|
||||
for (const FType &t : f.params) ptypes.push_back(ty(t));
|
||||
Function *fn = libc(f.name.c_str(), ty(f.ret), ptypes, f.variadic);
|
||||
|
||||
std::vector<Value *> args;
|
||||
for (size_t i = 0; i < c.args.size(); i++) {
|
||||
Value_ v = expr(*c.args[i]);
|
||||
if (i < f.params.size()) {
|
||||
args.push_back(coerce(v, f.params[i], c.args[i]->pos, "argument " + std::to_string(i + 1) + " of " + f.name));
|
||||
continue;
|
||||
}
|
||||
// extra arguments of printf-like functions follow C's promotion rules
|
||||
const FType &t = v.type;
|
||||
if (t.kind == FType::Void) fail(c.args[i]->pos.line, c.args[i]->pos.col, "this gives back nothing");
|
||||
if (t.kind == FType::F32) v.v = b.CreateFPExt(v.v, b.getDoubleTy());
|
||||
else if (t.kind == FType::Bool || ((t.isInt() || t.kind == FType::Char) && t.bits() < 32))
|
||||
v.v = b.CreateIntCast(v.v, b.getInt32Ty(), t.isSigned() || t.kind == FType::Char);
|
||||
args.push_back(v.v);
|
||||
}
|
||||
|
||||
CallInst *call = b.CreateCall(fn, args);
|
||||
for (size_t i = 0; i < f.params.size(); i++) {
|
||||
Attribute::AttrKind a = extAttr(f.params[i]);
|
||||
if (a != Attribute::None) {
|
||||
fn->addParamAttr(i, a);
|
||||
call->addParamAttr(i, a);
|
||||
}
|
||||
}
|
||||
if (Attribute::AttrKind a = extAttr(f.ret); a != Attribute::None) {
|
||||
fn->addRetAttr(a);
|
||||
call->addRetAttr(a);
|
||||
}
|
||||
return {call, f.ret};
|
||||
}
|
||||
|
||||
Value *cGlobal(const std::string &name) {
|
||||
const CGlobal &g = cimports.globals.at(name);
|
||||
if (GlobalVariable *gv = mod->getNamedGlobal(name)) return gv;
|
||||
return new GlobalVariable(*mod, ty(g.type), false, GlobalValue::ExternalLinkage, nullptr, name);
|
||||
}
|
||||
|
||||
// addr(x) gives a pointer to x
|
||||
Value_ addrOf(const CallExpr &c) {
|
||||
if (c.args.size() != 1) fail(c.pos.line, c.pos.col, "addr(...) takes exactly one variable");
|
||||
const Expr &e = *c.args[0];
|
||||
if (e.kind != ExprKind::Var && e.kind != ExprKind::Member)
|
||||
fail(e.pos.line, e.pos.col, "addr(...) needs a variable, like addr(x)");
|
||||
if (e.kind == ExprKind::Var) {
|
||||
Var *v = lookup(static_cast<const VarExpr &>(e).name);
|
||||
if (v && v->readonly) fail(e.pos.line, e.pos.col, "can't take the address of a loop variable");
|
||||
}
|
||||
Place pl = place(e);
|
||||
return {pl.addr, FType::ptrTo(pl.type)};
|
||||
}
|
||||
|
||||
// print(a, b, c) prints the values separated by spaces, then a newline.
|
||||
Value_ print(const CallExpr &c) {
|
||||
std::string fmt;
|
||||
std::vector<Value *> args;
|
||||
for (size_t i = 0; i < c.args.size(); i++) {
|
||||
if (i) fmt += ' ';
|
||||
Value_ v = expr(*c.args[i]);
|
||||
const FType &t = v.type;
|
||||
if (t.isSigned()) { fmt += "%lld"; args.push_back(b.CreateSExt(v.v, b.getInt64Ty())); }
|
||||
else if (t.isUnsigned()) { fmt += "%llu"; args.push_back(b.CreateZExt(v.v, b.getInt64Ty())); }
|
||||
else if (t.isFloat()) { fmt += "%g"; args.push_back(b.CreateFPExt(v.v, b.getDoubleTy())); }
|
||||
else if (t.kind == FType::Char) { fmt += "%c"; args.push_back(b.CreateZExt(v.v, b.getInt32Ty())); }
|
||||
else if (t.kind == FType::Str) { fmt += "%s"; args.push_back(v.v); }
|
||||
else if (t.isPtr()) { fmt += "%p"; args.push_back(v.v); }
|
||||
else if (t.kind == FType::Null) fmt += "null";
|
||||
else if (t.kind == FType::Bool) {
|
||||
fmt += "%s";
|
||||
args.push_back(b.CreateSelect(v.v, b.CreateGlobalString("true", "true"), b.CreateGlobalString("false", "false")));
|
||||
} else fail(c.args[i]->pos.line, c.args[i]->pos.col, "this gives back nothing, so there is nothing to print");
|
||||
}
|
||||
fmt += '\n';
|
||||
args.insert(args.begin(), b.CreateGlobalString(fmt, "fmt"));
|
||||
b.CreateCall(libc("printf", b.getInt32Ty(), {b.getPtrTy()}, true), args);
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
|
||||
// Explicit conversions: int(x), u8(x), float(x), char(x), bool(x), ptr(x) ...
|
||||
Value_ convert(const CallExpr &c, const FType &to) {
|
||||
if (c.args.size() != 1) fail(c.pos.line, c.pos.col, c.callee + "(...) takes exactly one value");
|
||||
Value_ v = expr(*c.args[0]);
|
||||
const FType &from = v.type;
|
||||
Pos p = c.args[0]->pos;
|
||||
if (from == to) return v;
|
||||
|
||||
if (to.isInt() || to.kind == FType::Char) {
|
||||
if (from.isInt() || from.kind == FType::Char) return {b.CreateIntCast(v.v, ty(to), from.isSigned()), to};
|
||||
if (from.kind == FType::Bool) return {b.CreateZExt(v.v, ty(to)), to};
|
||||
if (from.isFloat() && to.isInt())
|
||||
return {to.isSigned() ? b.CreateFPToSI(v.v, ty(to)) : b.CreateFPToUI(v.v, ty(to)), to};
|
||||
}
|
||||
if (to.isFloat()) {
|
||||
if (from.isInt()) return {intToFloat(v.v, from, to), to};
|
||||
if (from.isFloat()) return {b.CreateFPCast(v.v, ty(to)), to};
|
||||
}
|
||||
if (to.kind == FType::Bool && from.isInt()) return {b.CreateICmpNE(v.v, ConstantInt::get(ty(from), 0)), to};
|
||||
if (to.isPtr() && (from.isPtr() || from.kind == FType::Str || from.kind == FType::Null)) return {v.v, to};
|
||||
if (to.kind == FType::Str && from.isPtr()) return {v.v, to};
|
||||
fail(p.line, p.col, "can't turn " + from.name() + " into " + to.name());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Module> generate(const Program &prog, const CImports &c, LLVMContext &ctx, TargetMachine &tm) {
|
||||
return Codegen(ctx, c, tm).run(prog);
|
||||
}
|
||||
14
src/codegen.h
Normal file
14
src/codegen.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "ast.h"
|
||||
#include "cimport.h"
|
||||
|
||||
#include <llvm/IR/LLVMContext.h>
|
||||
#include <llvm/IR/Module.h>
|
||||
#include <llvm/Target/TargetMachine.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
// `tm` describes the machine we compile for (sizes, alignment), which shapes the IR.
|
||||
std::unique_ptr<llvm::Module> generate(const Program &prog, const CImports &c, llvm::LLVMContext &ctx,
|
||||
llvm::TargetMachine &tm);
|
||||
31
src/error.h
Normal file
31
src/error.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
// Finch stops at the first error and shows exactly where it is.
|
||||
inline std::string g_file;
|
||||
inline std::string g_source;
|
||||
|
||||
[[noreturn]] inline void fail(int line, int col, const std::string &msg) {
|
||||
std::fprintf(stderr, "%s:%d:%d: error: %s\n", g_file.c_str(), line, col, msg.c_str());
|
||||
|
||||
size_t start = 0;
|
||||
for (int l = 1; l < line && start != std::string::npos; l++) {
|
||||
start = g_source.find('\n', start);
|
||||
if (start != std::string::npos) start++;
|
||||
}
|
||||
if (start != std::string::npos && start < g_source.size()) {
|
||||
std::string text = g_source.substr(start, g_source.find('\n', start) - start);
|
||||
std::string pad;
|
||||
int c = 1; // columns count characters; skip UTF-8 continuation bytes
|
||||
for (size_t k = 0; k < text.size() && c < col; k++) {
|
||||
if (((unsigned char)text[k] & 0xC0) == 0x80) continue;
|
||||
pad += text[k] == '\t' ? '\t' : ' ';
|
||||
c++;
|
||||
}
|
||||
std::fprintf(stderr, "%5d | %s\n | %s^\n", line, text.c_str(), pad.c_str());
|
||||
}
|
||||
std::exit(1);
|
||||
}
|
||||
211
src/lexer.cpp
Normal file
211
src/lexer.cpp
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
#include "lexer.h"
|
||||
#include "error.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <unordered_map>
|
||||
|
||||
static const std::unordered_map<std::string, Tok> keywords = {
|
||||
{"fn", Tok::Fn}, {"return", Tok::Return}, {"if", Tok::If},
|
||||
{"else", Tok::Else}, {"while", Tok::While}, {"for", Tok::For},
|
||||
{"in", Tok::In}, {"break", Tok::Break}, {"continue", Tok::Continue},
|
||||
{"true", Tok::True}, {"false", Tok::False}, {"null", Tok::Null},
|
||||
{"import", Tok::Import}, {"link", Tok::Link},
|
||||
};
|
||||
|
||||
std::vector<Token> lex(const std::string &src, const std::string &file) {
|
||||
g_file = file;
|
||||
g_source = src;
|
||||
std::vector<Token> out;
|
||||
size_t i = 0;
|
||||
int line = 1, col = 1;
|
||||
bool newline = true;
|
||||
|
||||
auto advance = [&]() {
|
||||
if (src[i] == '\n') { line++; col = 1; newline = true; }
|
||||
else if (((unsigned char)src[i] & 0xC0) != 0x80) col++; // count characters, not UTF-8 bytes
|
||||
i++;
|
||||
};
|
||||
auto peek = [&](size_t off = 0) -> char {
|
||||
return i + off < src.size() ? src[i + off] : '\0';
|
||||
};
|
||||
|
||||
while (i < src.size()) {
|
||||
char c = src[i];
|
||||
|
||||
if (std::isspace((unsigned char)c)) { advance(); continue; }
|
||||
if (c == '/' && peek(1) == '/') {
|
||||
while (i < src.size() && src[i] != '\n') advance();
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && peek(1) == '*') {
|
||||
int l = line, cl = col;
|
||||
advance(); advance();
|
||||
while (i < src.size() && !(src[i] == '*' && peek(1) == '/')) advance();
|
||||
if (i >= src.size()) fail(l, cl, "comment is never closed (missing */)");
|
||||
advance(); advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
Token t{Tok::End, "", line, col, newline};
|
||||
newline = false;
|
||||
|
||||
if (std::isalpha((unsigned char)c) || c == '_') {
|
||||
size_t start = i;
|
||||
while (std::isalnum((unsigned char)peek()) || peek() == '_') advance();
|
||||
t.text = src.substr(start, i - start);
|
||||
auto kw = keywords.find(t.text);
|
||||
t.kind = kw != keywords.end() ? kw->second : Tok::Ident;
|
||||
} else if (std::isdigit((unsigned char)c)) {
|
||||
size_t start = i;
|
||||
t.kind = Tok::Int;
|
||||
if (c == '0' && (peek(1) == 'x' || peek(1) == 'X')) { // 0xFF
|
||||
advance(); advance();
|
||||
while (std::isxdigit((unsigned char)peek()) || peek() == '_') advance();
|
||||
if (i - start == 2) fail(t.line, t.col, "'0x' needs hex digits after it, like 0xFF");
|
||||
} else {
|
||||
while (std::isdigit((unsigned char)peek()) || peek() == '_') advance();
|
||||
// "1..5" is a range, not the float "1."
|
||||
if (peek() == '.' && std::isdigit((unsigned char)peek(1))) {
|
||||
t.kind = Tok::Float;
|
||||
advance();
|
||||
while (std::isdigit((unsigned char)peek()) || peek() == '_') advance();
|
||||
}
|
||||
}
|
||||
for (size_t k = start; k < i; k++)
|
||||
if (src[k] != '_') t.text += src[k];
|
||||
} else if (c == '"' || c == '\'') {
|
||||
char quote = c;
|
||||
t.kind = quote == '"' ? Tok::String : Tok::Char;
|
||||
advance();
|
||||
while (peek() != quote) {
|
||||
if (peek() == '\0' || peek() == '\n')
|
||||
fail(t.line, t.col, quote == '"' ? "string is never closed (missing \")"
|
||||
: "char is never closed (missing ')");
|
||||
char ch = peek();
|
||||
advance();
|
||||
if (ch == '\\') {
|
||||
char e = peek();
|
||||
advance();
|
||||
switch (e) {
|
||||
case 'n': ch = '\n'; break;
|
||||
case 't': ch = '\t'; break;
|
||||
case 'r': ch = '\r'; break;
|
||||
case '0': ch = '\0'; break;
|
||||
case '\\': ch = '\\'; break;
|
||||
case '"': ch = '"'; break;
|
||||
case '\'': ch = '\''; break;
|
||||
default: fail(line, col - 1, std::string("unknown escape \\") + e);
|
||||
}
|
||||
}
|
||||
t.text += ch;
|
||||
}
|
||||
advance();
|
||||
if (t.kind == Tok::Char && t.text.size() != 1)
|
||||
fail(t.line, t.col, "a char holds exactly one character, like 'a'");
|
||||
} else {
|
||||
char n = peek(1);
|
||||
auto two = [&](Tok k) { t.kind = k; advance(); advance(); };
|
||||
auto one = [&](Tok k) { t.kind = k; advance(); };
|
||||
switch (c) {
|
||||
case '(': one(Tok::LParen); break;
|
||||
case ')': one(Tok::RParen); break;
|
||||
case '{': one(Tok::LBrace); break;
|
||||
case '}': one(Tok::RBrace); break;
|
||||
case '[': one(Tok::LBracket); break;
|
||||
case ']': one(Tok::RBracket); break;
|
||||
case ',': one(Tok::Comma); break;
|
||||
case '%': n == '=' ? two(Tok::PercentEq) : one(Tok::Percent); break;
|
||||
case '^': one(Tok::Caret); break;
|
||||
case '~': one(Tok::Tilde); break;
|
||||
case '.': n == '.' ? two(Tok::DotDot) : one(Tok::Dot); break;
|
||||
case '+': n == '=' ? two(Tok::PlusEq) : one(Tok::Plus); break;
|
||||
case '-': n == '=' ? two(Tok::MinusEq) : n == '>' ? two(Tok::Arrow) : one(Tok::Minus); break;
|
||||
case '*': n == '=' ? two(Tok::StarEq) : one(Tok::Star); break;
|
||||
case '/': n == '=' ? two(Tok::SlashEq) : one(Tok::Slash); break;
|
||||
case '=': n == '=' ? two(Tok::Eq) : one(Tok::Assign); break;
|
||||
case '!': n == '=' ? two(Tok::NotEq) : one(Tok::Not); break;
|
||||
case '<': n == '=' ? two(Tok::LessEq) : n == '<' ? two(Tok::Shl) : one(Tok::Less); break;
|
||||
case '>': n == '=' ? two(Tok::GreaterEq) : n == '>' ? two(Tok::Shr) : one(Tok::Greater); break;
|
||||
case ':':
|
||||
if (n != '=') fail(line, col, "unexpected ':' (did you mean ':='?)");
|
||||
two(Tok::Declare);
|
||||
break;
|
||||
case '&': n == '&' ? two(Tok::And) : one(Tok::Amp); break;
|
||||
case '|': n == '|' ? two(Tok::Or) : one(Tok::Pipe); break;
|
||||
default: {
|
||||
// show the whole UTF-8 character, not just its first byte
|
||||
size_t len = (c & 0xE0) == 0xC0 ? 2 : (c & 0xF0) == 0xE0 ? 3 : (c & 0xF8) == 0xF0 ? 4 : 1;
|
||||
std::string ch = src.substr(i, len);
|
||||
std::string hint = (unsigned char)c >= 0x80 ? " (names use only a-z, A-Z, 0-9 and _; other letters only go inside \"text\")" : "";
|
||||
fail(line, col, "unexpected character '" + ch + "'" + hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
out.push_back(t);
|
||||
}
|
||||
out.push_back({Tok::End, "", line, col, true});
|
||||
return out;
|
||||
}
|
||||
|
||||
const char *tokName(Tok t) {
|
||||
switch (t) {
|
||||
case Tok::End: return "end of file";
|
||||
case Tok::Ident: return "a name";
|
||||
case Tok::Int: return "a number";
|
||||
case Tok::Float: return "a number";
|
||||
case Tok::String: return "a string";
|
||||
case Tok::Char: return "a char";
|
||||
case Tok::Fn: return "'fn'";
|
||||
case Tok::Return: return "'return'";
|
||||
case Tok::If: return "'if'";
|
||||
case Tok::Else: return "'else'";
|
||||
case Tok::While: return "'while'";
|
||||
case Tok::For: return "'for'";
|
||||
case Tok::In: return "'in'";
|
||||
case Tok::Break: return "'break'";
|
||||
case Tok::Continue: return "'continue'";
|
||||
case Tok::True: return "'true'";
|
||||
case Tok::False: return "'false'";
|
||||
case Tok::Null: return "'null'";
|
||||
case Tok::Import: return "'import'";
|
||||
case Tok::Link: return "'link'";
|
||||
case Tok::LParen: return "'('";
|
||||
case Tok::RParen: return "')'";
|
||||
case Tok::LBrace: return "'{'";
|
||||
case Tok::RBrace: return "'}'";
|
||||
case Tok::LBracket: return "'['";
|
||||
case Tok::RBracket: return "']'";
|
||||
case Tok::Comma: return "','";
|
||||
case Tok::Dot: return "'.'";
|
||||
case Tok::Arrow: return "'->'";
|
||||
case Tok::DotDot: return "'..'";
|
||||
case Tok::Plus: return "'+'";
|
||||
case Tok::Minus: return "'-'";
|
||||
case Tok::Star: return "'*'";
|
||||
case Tok::Slash: return "'/'";
|
||||
case Tok::Percent: return "'%'";
|
||||
case Tok::Amp: return "'&'";
|
||||
case Tok::Pipe: return "'|'";
|
||||
case Tok::Caret: return "'^'";
|
||||
case Tok::Tilde: return "'~'";
|
||||
case Tok::Shl: return "'<<'";
|
||||
case Tok::Shr: return "'>>'";
|
||||
case Tok::PercentEq: return "'%='";
|
||||
case Tok::Assign: return "'='";
|
||||
case Tok::Declare: return "':='";
|
||||
case Tok::PlusEq: return "'+='";
|
||||
case Tok::MinusEq: return "'-='";
|
||||
case Tok::StarEq: return "'*='";
|
||||
case Tok::SlashEq: return "'/='";
|
||||
case Tok::Eq: return "'=='";
|
||||
case Tok::NotEq: return "'!='";
|
||||
case Tok::Less: return "'<'";
|
||||
case Tok::LessEq: return "'<='";
|
||||
case Tok::Greater: return "'>'";
|
||||
case Tok::GreaterEq: return "'>='";
|
||||
case Tok::And: return "'&&'";
|
||||
case Tok::Or: return "'||'";
|
||||
case Tok::Not: return "'!'";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
34
src/lexer.h
Normal file
34
src/lexer.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class Tok {
|
||||
End,
|
||||
Ident, Int, Float, String, Char,
|
||||
|
||||
// keywords
|
||||
Fn, Return, If, Else, While, For, In, Break, Continue, True, False, Null, Import, Link,
|
||||
|
||||
// punctuation
|
||||
LParen, RParen, LBrace, RBrace, LBracket, RBracket, Comma, Dot, DotDot, Arrow,
|
||||
|
||||
// operators
|
||||
Plus, Minus, Star, Slash, Percent,
|
||||
Amp, Pipe, Caret, Tilde, Shl, Shr, // & | ^ ~ << >>
|
||||
Assign, Declare, // = :=
|
||||
PlusEq, MinusEq, StarEq, SlashEq, PercentEq, // += -= *= /= %=
|
||||
Eq, NotEq, Less, LessEq, Greater, GreaterEq,
|
||||
And, Or, Not,
|
||||
};
|
||||
|
||||
struct Token {
|
||||
Tok kind;
|
||||
std::string text;
|
||||
int line;
|
||||
int col;
|
||||
bool newlineBefore; // token starts a new line: ends the previous statement
|
||||
};
|
||||
|
||||
std::vector<Token> lex(const std::string &src, const std::string &file);
|
||||
const char *tokName(Tok t);
|
||||
258
src/main.cpp
Normal file
258
src/main.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#include "cimport.h"
|
||||
#include "codegen.h"
|
||||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
|
||||
#include <llvm/IR/LegacyPassManager.h>
|
||||
#include <llvm/MC/TargetRegistry.h>
|
||||
#include <llvm/Passes/PassBuilder.h>
|
||||
#include <llvm/Support/FileSystem.h>
|
||||
#include <llvm/Support/TargetSelect.h>
|
||||
#include <llvm/Config/llvm-config.h>
|
||||
#include <llvm/Target/TargetMachine.h>
|
||||
#include <llvm/TargetParser/Host.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <sys/wait.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
static void usage() {
|
||||
std::fprintf(stderr,
|
||||
"Finch " FINCH_VERSION " - small, quick, sharp.\n\n"
|
||||
"usage:\n"
|
||||
" finch run <file.fn> compile and run\n"
|
||||
" finch build <file.fn> [-o name] compile to a program\n"
|
||||
"\n"
|
||||
" -l <lib> link a C library, same as link \"lib\" in the file\n"
|
||||
" -O0 skip optimizations (to read the raw IR)\n"
|
||||
" finch ir <file.fn> show the generated LLVM IR\n"
|
||||
" finch version show the version\n");
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
static std::string readFile(const std::string &path) {
|
||||
std::ifstream in(path);
|
||||
if (!in) {
|
||||
std::fprintf(stderr, "error: can't open '%s'\n", path.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << in.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static void optimize(Module &mod, TargetMachine *tm) {
|
||||
LoopAnalysisManager lam;
|
||||
FunctionAnalysisManager fam;
|
||||
CGSCCAnalysisManager cgam;
|
||||
ModuleAnalysisManager mam;
|
||||
PassBuilder pb(tm);
|
||||
pb.registerModuleAnalyses(mam);
|
||||
pb.registerCGSCCAnalyses(cgam);
|
||||
pb.registerFunctionAnalyses(fam);
|
||||
pb.registerLoopAnalyses(lam);
|
||||
pb.crossRegisterProxies(lam, fam, cgam, mam);
|
||||
pb.buildPerModuleDefaultPipeline(OptimizationLevel::O2).run(mod, mam);
|
||||
}
|
||||
|
||||
static TargetMachine *hostMachine() {
|
||||
InitializeNativeTarget();
|
||||
InitializeNativeTargetAsmPrinter();
|
||||
|
||||
Triple triple(sys::getDefaultTargetTriple());
|
||||
std::string err;
|
||||
const Target *target = TargetRegistry::lookupTarget(triple, err);
|
||||
if (!target) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
return target->createTargetMachine(triple, "generic", "", TargetOptions(), Reloc::PIC_);
|
||||
}
|
||||
|
||||
static void emitObject(Module &mod, TargetMachine *tm, const std::string &path) {
|
||||
std::error_code ec;
|
||||
raw_fd_ostream out(path, ec, sys::fs::OF_None);
|
||||
if (ec) {
|
||||
std::fprintf(stderr, "error: can't write '%s': %s\n", path.c_str(), ec.message().c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
legacy::PassManager pm;
|
||||
if (tm->addPassesToEmitFile(pm, out, nullptr, CodeGenFileType::ObjectFile)) {
|
||||
std::fprintf(stderr, "error: this machine can't produce object files\n");
|
||||
std::exit(1);
|
||||
}
|
||||
pm.run(mod);
|
||||
}
|
||||
|
||||
// Run a shell command; returns its exit status, everything it printed goes to `output`.
|
||||
static int capture(const std::string &cmd, std::string &output) {
|
||||
FILE *p = popen((cmd + " 2>&1").c_str(), "r");
|
||||
if (!p) return -1;
|
||||
char buf[4096];
|
||||
size_t n;
|
||||
while ((n = std::fread(buf, 1, sizeof buf, p)) > 0) output.append(buf, n);
|
||||
int status = pclose(p);
|
||||
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
}
|
||||
|
||||
// pkg-config knows the right paths for a library if it has one; otherwise plain -l<name>.
|
||||
static std::string libFlags(const std::string &lib) {
|
||||
std::string out;
|
||||
if (capture("pkg-config --libs '" + lib + "'", out) == 0) {
|
||||
while (!out.empty() && std::isspace((unsigned char)out.back())) out.pop_back();
|
||||
return " " + out;
|
||||
}
|
||||
return " '-l" + lib + "'";
|
||||
}
|
||||
|
||||
// "GLFW/glfw3.h" -> "glfw": a decent first guess for the library's name.
|
||||
static std::string guessLib(const std::string &header) {
|
||||
std::string name = header.substr(header.find_last_of('/') + 1);
|
||||
name = name.substr(0, name.rfind('.'));
|
||||
while (!name.empty() && std::isdigit((unsigned char)name.back())) name.pop_back();
|
||||
for (char &ch : name) ch = std::tolower((unsigned char)ch);
|
||||
return name;
|
||||
}
|
||||
|
||||
// The linker speaks in riddles; turn its complaints into something you can act on.
|
||||
[[noreturn]] static void linkFailed(const std::string &output, const CImports &c) {
|
||||
std::vector<std::string> missing, missingLibs;
|
||||
auto addOnce = [](std::vector<std::string> &v, const std::string &x) {
|
||||
if (std::find(v.begin(), v.end(), x) == v.end()) v.push_back(x);
|
||||
};
|
||||
std::istringstream lines(output);
|
||||
for (std::string l; std::getline(lines, l);) {
|
||||
size_t a;
|
||||
if ((a = l.find("undefined reference to `")) != std::string::npos) {
|
||||
a += 24;
|
||||
addOnce(missing, l.substr(a, l.find('\'', a) - a));
|
||||
} else if ((a = l.find("undefined symbol: ")) != std::string::npos) {
|
||||
a += 18;
|
||||
addOnce(missing, l.substr(a, l.find_first_of(" \n", a) - a));
|
||||
} else if ((a = l.find("cannot find -l")) != std::string::npos) {
|
||||
a += 14;
|
||||
addOnce(missingLibs, l.substr(a, l.find_first_of(": \n", a) - a));
|
||||
}
|
||||
}
|
||||
if (missing.empty() && missingLibs.empty()) {
|
||||
std::fprintf(stderr, "%serror: linking failed\n", output.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
for (const std::string &lib : missingLibs)
|
||||
std::fprintf(stderr, "error: the library '%s' wasn't found. Is it installed? (on Nix: add it to shell.nix)\n", lib.c_str());
|
||||
|
||||
// group the missing functions by the header they came from
|
||||
std::vector<std::pair<std::string, std::vector<std::string>>> byHeader;
|
||||
for (const std::string &name : missing) {
|
||||
auto f = c.fns.find(name);
|
||||
std::string header = f == c.fns.end() ? "" : f->second.header;
|
||||
auto it = std::find_if(byHeader.begin(), byHeader.end(), [&](auto &g) { return g.first == header; });
|
||||
if (it == byHeader.end()) byHeader.push_back({header, {name}});
|
||||
else it->second.push_back(name);
|
||||
}
|
||||
for (auto &[header, names] : byHeader) {
|
||||
std::string list;
|
||||
for (size_t k = 0; k < names.size() && k < 3; k++) list += (k ? ", " : "") + names[k];
|
||||
if (names.size() > 3) list += " and " + std::to_string(names.size() - 3) + " more";
|
||||
if (header.empty()) {
|
||||
std::fprintf(stderr, "error: no code found for %s\n", list.c_str());
|
||||
continue;
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"error: %s come%s from \"%s\", but its library isn't linked\n"
|
||||
" the header only says the functions exist; their code lives in a library.\n"
|
||||
" add this at the top of your file (with the library's real name):\n\n"
|
||||
" link \"%s\"\n\n",
|
||||
list.c_str(), names.size() == 1 ? "s" : "", header.c_str(), guessLib(header).c_str());
|
||||
}
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
static void link(const std::string &obj, const std::string &exe, const std::vector<std::string> &libs,
|
||||
const CImports &c) {
|
||||
const char *cc = std::getenv("CC");
|
||||
std::string cmd = std::string(cc ? cc : "cc") + " '" + obj + "' -o '" + exe + "' -lm";
|
||||
for (const std::string &l : libs) cmd += libFlags(l);
|
||||
std::string output;
|
||||
int status = capture(cmd, output);
|
||||
std::remove(obj.c_str());
|
||||
if (status == 127) {
|
||||
std::fprintf(stderr, "error: no C compiler found to link with (install gcc or clang, or set CC)\n");
|
||||
std::exit(1);
|
||||
}
|
||||
if (status != 0) linkFailed(output, c);
|
||||
std::fputs(output.c_str(), stderr); // warnings, if any
|
||||
}
|
||||
|
||||
static std::string stem(const std::string &path) {
|
||||
size_t slash = path.find_last_of('/');
|
||||
std::string name = slash == std::string::npos ? path : path.substr(slash + 1);
|
||||
size_t dot = name.rfind('.');
|
||||
return dot == std::string::npos ? name : name.substr(0, dot);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc == 2 && (std::string(argv[1]) == "version" || std::string(argv[1]) == "--version")) {
|
||||
std::printf("finch %s (LLVM %s)\n", FINCH_VERSION, LLVM_VERSION_STRING);
|
||||
return 0;
|
||||
}
|
||||
if (argc < 3) usage();
|
||||
std::string cmd = argv[1], file = argv[2];
|
||||
std::string out = stem(file);
|
||||
std::vector<std::string> libs;
|
||||
bool optimizeCode = true;
|
||||
for (int i = 3; i < argc; i++) {
|
||||
std::string a = argv[i];
|
||||
if (a == "-o" && i + 1 < argc) out = argv[++i];
|
||||
else if (a == "-O0") optimizeCode = false;
|
||||
else if (a == "-l" && i + 1 < argc) libs.push_back(argv[++i]);
|
||||
else if (a.rfind("-l", 0) == 0 && a.size() > 2) libs.push_back(a.substr(2));
|
||||
else usage();
|
||||
}
|
||||
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
|
||||
|
||||
Program prog = parse(lex(readFile(file), file));
|
||||
for (const Link &l : prog.links) libs.push_back(l.lib);
|
||||
size_t slash = file.find_last_of('/');
|
||||
CImports c = importHeaders(prog.imports, slash == std::string::npos ? "." : file.substr(0, slash));
|
||||
LLVMContext ctx;
|
||||
TargetMachine *tm = hostMachine();
|
||||
std::unique_ptr<Module> mod = generate(prog, c, ctx, *tm);
|
||||
if (optimizeCode) optimize(*mod, tm);
|
||||
|
||||
if (cmd == "ir") {
|
||||
mod->print(outs(), nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string obj = out + ".o";
|
||||
if (cmd == "run") {
|
||||
SmallString<128> tmp;
|
||||
sys::fs::createTemporaryFile("finch", "", tmp);
|
||||
out = tmp.str().str();
|
||||
obj = out + ".o";
|
||||
}
|
||||
emitObject(*mod, tm, obj);
|
||||
link(obj, out, libs, c);
|
||||
|
||||
if (cmd == "run") {
|
||||
int status = std::system(("'" + out + "'").c_str());
|
||||
std::remove(out.c_str());
|
||||
if (WIFSIGNALED(status)) {
|
||||
std::fprintf(stderr, "\nthe program crashed: %s\n", strsignal(WTERMSIG(status)));
|
||||
return 128 + WTERMSIG(status);
|
||||
}
|
||||
return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
425
src/parser.cpp
Normal file
425
src/parser.cpp
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
#include "parser.h"
|
||||
#include "error.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <unordered_map>
|
||||
|
||||
bool typeFromName(const std::string &name, Type &out) {
|
||||
static const std::unordered_map<std::string, Type::Kind> names = {
|
||||
{"int", Type::I64}, {"float", Type::F64}, {"bool", Type::Bool}, {"char", Type::Char},
|
||||
{"str", Type::Str}, {"ptr", Type::Ptr}, {"i8", Type::I8}, {"i16", Type::I16},
|
||||
{"i32", Type::I32}, {"i64", Type::I64}, {"u8", Type::U8}, {"u16", Type::U16},
|
||||
{"u32", Type::U32}, {"u64", Type::U64}, {"f32", Type::F32}, {"f64", Type::F64},
|
||||
};
|
||||
auto it = names.find(name);
|
||||
if (it == names.end()) return false;
|
||||
out = Type(it->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool isTypeName(const std::string &name) {
|
||||
Type t;
|
||||
return typeFromName(name, t);
|
||||
}
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(const std::vector<Token> &t) : toks(t) {}
|
||||
|
||||
Program program() {
|
||||
Program p;
|
||||
while (!at(Tok::End)) {
|
||||
if (at(Tok::Import)) p.imports.push_back(import());
|
||||
else if (at(Tok::Link)) p.links.push_back(link());
|
||||
else p.fns.push_back(function());
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Token> &toks;
|
||||
size_t i = 0;
|
||||
int parenDepth = 0; // inside ( ) newlines don't end anything
|
||||
|
||||
const Token &cur() const { return toks[i]; }
|
||||
const Token &peekTok(size_t off = 1) const { return toks[std::min(i + off, toks.size() - 1)]; }
|
||||
const Token &next() { return toks[i++]; }
|
||||
bool at(Tok k) const { return cur().kind == k; }
|
||||
Pos pos() const { return {cur().line, cur().col}; }
|
||||
|
||||
std::string describe(const Token &t) const {
|
||||
return t.kind == Tok::Ident ? "'" + t.text + "'" : tokName(t.kind);
|
||||
}
|
||||
|
||||
bool accept(Tok k) {
|
||||
if (!at(k)) return false;
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
const Token &expect(Tok k, const char *what = nullptr) {
|
||||
if (!at(k))
|
||||
fail(cur().line, cur().col,
|
||||
std::string("expected ") + (what ? what : tokName(k)) + ", found " + describe(cur()));
|
||||
return next();
|
||||
}
|
||||
[[noreturn]] void unexpected(const char *ctx) {
|
||||
fail(cur().line, cur().col, "unexpected " + describe(cur()) + " " + ctx);
|
||||
}
|
||||
|
||||
// A token can continue an expression only if it is on the same line (or we're inside parens).
|
||||
bool sameLine() const { return parenDepth > 0 || !cur().newlineBefore; }
|
||||
|
||||
// int, u8, ptr, ptr[int], ptr[ptr[u8]] ...
|
||||
Type type() {
|
||||
if (!at(Tok::Ident) || !isTypeName(cur().text))
|
||||
unexpected("(expected a type like int, float, u8, str or ptr[int])");
|
||||
Type t;
|
||||
typeFromName(next().text, t);
|
||||
if (t.kind == Type::Ptr && accept(Tok::LBracket)) {
|
||||
t = Type::ptrTo(type());
|
||||
expect(Tok::RBracket);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
// Does a declaration like `int x` or `ptr[int] p` start here?
|
||||
bool atDeclaration() const {
|
||||
if (!at(Tok::Ident) || !isTypeName(cur().text)) return false;
|
||||
Tok n = peekTok().kind;
|
||||
return n == Tok::Ident || (cur().text == "ptr" && n == Tok::LBracket);
|
||||
}
|
||||
|
||||
// ---------- top level ----------
|
||||
|
||||
Import import() {
|
||||
Import im;
|
||||
im.pos = pos();
|
||||
expect(Tok::Import);
|
||||
if (at(Tok::Ident))
|
||||
fail(im.pos.line, im.pos.col, "Finch modules are not ready yet; for C headers write import \"stdio.h\"");
|
||||
im.path = expect(Tok::String, "a header name in quotes, like \"stdio.h\"").text;
|
||||
return im;
|
||||
}
|
||||
|
||||
Link link() {
|
||||
Link l;
|
||||
l.pos = pos();
|
||||
expect(Tok::Link);
|
||||
l.lib = expect(Tok::String, "a library name in quotes, like link \"glfw\"").text;
|
||||
if (l.lib.rfind("lib", 0) == 0 || l.lib.find(".so") != std::string::npos || l.lib.find('/') != std::string::npos)
|
||||
fail(l.pos.line, l.pos.col, "write just the library's name: for libglfw.so that's link \"glfw\"");
|
||||
return l;
|
||||
}
|
||||
|
||||
FnDecl function() {
|
||||
FnDecl fn;
|
||||
fn.pos = pos();
|
||||
if (at(Tok::Ident) && isTypeName(cur().text))
|
||||
fail(fn.pos.line, fn.pos.col, "functions start with 'fn', like: fn add(int a, int b) -> int { ... }");
|
||||
expect(Tok::Fn, "'fn' (every function starts with fn)");
|
||||
|
||||
fn.name = expect(Tok::Ident, "a function name").text;
|
||||
expect(Tok::LParen);
|
||||
parenDepth++;
|
||||
if (!at(Tok::RParen)) {
|
||||
do {
|
||||
Param p;
|
||||
p.pos = pos();
|
||||
p.type = type();
|
||||
p.name = expect(Tok::Ident, "a parameter name").text;
|
||||
fn.params.push_back(p);
|
||||
} while (accept(Tok::Comma));
|
||||
}
|
||||
parenDepth--;
|
||||
expect(Tok::RParen);
|
||||
fn.ret = accept(Tok::Arrow) ? type() : Type(Type::Void);
|
||||
fn.body = block();
|
||||
return fn;
|
||||
}
|
||||
|
||||
// ---------- statements ----------
|
||||
|
||||
BlockPtr block() {
|
||||
auto b = std::make_unique<BlockStmt>(pos());
|
||||
expect(Tok::LBrace);
|
||||
int saved = parenDepth;
|
||||
parenDepth = 0;
|
||||
while (!at(Tok::RBrace)) {
|
||||
if (at(Tok::End)) fail(b->pos.line, b->pos.col, "this '{' is never closed (missing '}')");
|
||||
b->body.push_back(statement());
|
||||
endOfStatement();
|
||||
}
|
||||
parenDepth = saved;
|
||||
next();
|
||||
return b;
|
||||
}
|
||||
|
||||
void endOfStatement() {
|
||||
if (at(Tok::RBrace) || at(Tok::End) || cur().newlineBefore) return;
|
||||
unexpected("(put each statement on its own line)");
|
||||
}
|
||||
|
||||
StmtPtr statement() {
|
||||
Pos p = pos();
|
||||
switch (cur().kind) {
|
||||
case Tok::If: return ifStmt();
|
||||
case Tok::While: {
|
||||
next();
|
||||
auto s = std::make_unique<WhileStmt>(p);
|
||||
s->cond = expr();
|
||||
s->body = block();
|
||||
return s;
|
||||
}
|
||||
case Tok::For: {
|
||||
next();
|
||||
auto s = std::make_unique<ForStmt>(p);
|
||||
s->var = expect(Tok::Ident, "a loop variable name").text;
|
||||
expect(Tok::In);
|
||||
s->from = expr();
|
||||
expect(Tok::DotDot, "'..' (like 'for i in 0..10')");
|
||||
s->to = expr();
|
||||
s->body = block();
|
||||
return s;
|
||||
}
|
||||
case Tok::Return: {
|
||||
next();
|
||||
auto s = std::make_unique<ReturnStmt>(p);
|
||||
if (!at(Tok::RBrace) && !cur().newlineBefore) s->value = expr();
|
||||
return s;
|
||||
}
|
||||
case Tok::Break: next(); return std::make_unique<BreakStmt>(p);
|
||||
case Tok::Continue: next(); return std::make_unique<ContinueStmt>(p);
|
||||
case Tok::LBrace: return block();
|
||||
case Tok::Fn: fail(p.line, p.col, "functions can't be inside other functions (move it out)");
|
||||
case Tok::Import:
|
||||
case Tok::Link: fail(p.line, p.col, "import and link go at the top of the file, outside functions");
|
||||
default: break;
|
||||
}
|
||||
|
||||
// int x = 5
|
||||
if (atDeclaration()) {
|
||||
auto s = std::make_unique<VarDeclStmt>(p);
|
||||
s->hasType = true;
|
||||
s->type = type();
|
||||
s->name = next().text;
|
||||
if (accept(Tok::Assign)) s->init = expr();
|
||||
return s;
|
||||
}
|
||||
|
||||
// x := 5
|
||||
if (at(Tok::Ident) && peekTok().kind == Tok::Declare) {
|
||||
auto s = std::make_unique<VarDeclStmt>(p);
|
||||
s->hasType = false;
|
||||
s->name = next().text;
|
||||
next();
|
||||
s->init = expr();
|
||||
return s;
|
||||
}
|
||||
|
||||
ExprPtr e = expr();
|
||||
|
||||
// x = 5, p.value += 1, ...
|
||||
char op = 0;
|
||||
switch (cur().kind) {
|
||||
case Tok::Assign: op = '='; break;
|
||||
case Tok::PlusEq: op = '+'; break;
|
||||
case Tok::MinusEq: op = '-'; break;
|
||||
case Tok::StarEq: op = '*'; break;
|
||||
case Tok::SlashEq: op = '/'; break;
|
||||
case Tok::PercentEq: op = '%'; break;
|
||||
case Tok::Declare:
|
||||
fail(cur().line, cur().col, "':=' creates a new variable, so the left side must be a plain name");
|
||||
default: break;
|
||||
}
|
||||
if (op && sameLine()) {
|
||||
if (e->kind != ExprKind::Var && e->kind != ExprKind::Member)
|
||||
fail(p.line, p.col, "only a variable or p.value can be assigned to");
|
||||
auto s = std::make_unique<AssignStmt>(p);
|
||||
s->target = std::move(e);
|
||||
s->op = op;
|
||||
next();
|
||||
s->value = expr();
|
||||
return s;
|
||||
}
|
||||
|
||||
if (e->kind != ExprKind::Call) fail(p.line, p.col, "this value is computed but never used");
|
||||
return std::make_unique<ExprStmt>(p, std::move(e));
|
||||
}
|
||||
|
||||
StmtPtr ifStmt() {
|
||||
auto s = std::make_unique<IfStmt>(pos());
|
||||
expect(Tok::If);
|
||||
s->cond = expr();
|
||||
s->then = block();
|
||||
if (accept(Tok::Else)) {
|
||||
if (at(Tok::If)) s->otherwise = ifStmt();
|
||||
else s->otherwise = block();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// ---------- expressions (lowest to highest precedence) ----------
|
||||
|
||||
ExprPtr expr() { return orExpr(); }
|
||||
|
||||
ExprPtr orExpr() {
|
||||
ExprPtr l = andExpr();
|
||||
while (sameLine() && at(Tok::Or)) {
|
||||
Pos p = pos();
|
||||
next();
|
||||
l = std::make_unique<BinaryExpr>(p, BinOp::Or, std::move(l), andExpr());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
ExprPtr andExpr() {
|
||||
ExprPtr l = compare();
|
||||
while (sameLine() && at(Tok::And)) {
|
||||
Pos p = pos();
|
||||
next();
|
||||
l = std::make_unique<BinaryExpr>(p, BinOp::And, std::move(l), compare());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
ExprPtr compare() {
|
||||
ExprPtr l = additive();
|
||||
while (sameLine()) {
|
||||
BinOp op;
|
||||
switch (cur().kind) {
|
||||
case Tok::Eq: op = BinOp::Eq; break;
|
||||
case Tok::NotEq: op = BinOp::NotEq; break;
|
||||
case Tok::Less: op = BinOp::Less; break;
|
||||
case Tok::LessEq: op = BinOp::LessEq; break;
|
||||
case Tok::Greater: op = BinOp::Greater; break;
|
||||
case Tok::GreaterEq: op = BinOp::GreaterEq; break;
|
||||
default: return l;
|
||||
}
|
||||
Pos p = pos();
|
||||
next();
|
||||
l = std::make_unique<BinaryExpr>(p, op, std::move(l), additive());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
// Precedence is Go's, not C's: bit operators bind tighter than comparisons,
|
||||
// so `x & 1 == 0` means `(x & 1) == 0`.
|
||||
ExprPtr additive() { // + - | ^
|
||||
ExprPtr l = multiplicative();
|
||||
while (sameLine()) {
|
||||
BinOp op;
|
||||
switch (cur().kind) {
|
||||
case Tok::Plus: op = BinOp::Add; break;
|
||||
case Tok::Minus: op = BinOp::Sub; break;
|
||||
case Tok::Pipe: op = BinOp::BitOr; break;
|
||||
case Tok::Caret: op = BinOp::BitXor; break;
|
||||
default: return l;
|
||||
}
|
||||
Pos p = pos();
|
||||
next();
|
||||
l = std::make_unique<BinaryExpr>(p, op, std::move(l), multiplicative());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
ExprPtr multiplicative() { // * / % << >> &
|
||||
ExprPtr l = unary();
|
||||
while (sameLine()) {
|
||||
BinOp op;
|
||||
switch (cur().kind) {
|
||||
case Tok::Star: op = BinOp::Mul; break;
|
||||
case Tok::Slash: op = BinOp::Div; break;
|
||||
case Tok::Percent: op = BinOp::Mod; break;
|
||||
case Tok::Shl: op = BinOp::Shl; break;
|
||||
case Tok::Shr: op = BinOp::Shr; break;
|
||||
case Tok::Amp: op = BinOp::BitAnd; break;
|
||||
default: return l;
|
||||
}
|
||||
Pos p = pos();
|
||||
next();
|
||||
l = std::make_unique<BinaryExpr>(p, op, std::move(l), unary());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
ExprPtr unary() {
|
||||
Pos p = pos();
|
||||
if (accept(Tok::Minus)) return std::make_unique<UnaryExpr>(p, '-', unary());
|
||||
if (accept(Tok::Not)) return std::make_unique<UnaryExpr>(p, '!', unary());
|
||||
if (accept(Tok::Tilde)) return std::make_unique<UnaryExpr>(p, '~', unary());
|
||||
return postfix();
|
||||
}
|
||||
|
||||
// p.value
|
||||
ExprPtr postfix() {
|
||||
ExprPtr e = primary();
|
||||
while (sameLine() && at(Tok::Dot)) {
|
||||
Pos p = pos();
|
||||
next();
|
||||
e = std::make_unique<MemberExpr>(p, std::move(e), expect(Tok::Ident, "a name after '.'").text);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
// name(arg, arg, ...) -- we are at the '('
|
||||
ExprPtr callArgs(Pos p, const std::string &name) {
|
||||
auto call = std::make_unique<CallExpr>(p, name);
|
||||
expect(Tok::LParen);
|
||||
parenDepth++;
|
||||
if (!at(Tok::RParen)) {
|
||||
do call->args.push_back(expr());
|
||||
while (accept(Tok::Comma));
|
||||
}
|
||||
parenDepth--;
|
||||
expect(Tok::RParen);
|
||||
return call;
|
||||
}
|
||||
|
||||
ExprPtr primary() {
|
||||
Pos p = pos();
|
||||
const Token &t = cur();
|
||||
switch (t.kind) {
|
||||
case Tok::Int: {
|
||||
next();
|
||||
errno = 0;
|
||||
unsigned long long v = std::strtoull(t.text.c_str(), nullptr, t.text.size() > 1 && (t.text[1] == 'x' || t.text[1] == 'X') ? 16 : 10);
|
||||
if (errno == ERANGE || v > (unsigned long long)LLONG_MAX)
|
||||
fail(p.line, p.col, "number is too big for int");
|
||||
return std::make_unique<IntExpr>(p, (long long)v);
|
||||
}
|
||||
case Tok::Float: next(); return std::make_unique<FloatExpr>(p, std::strtod(t.text.c_str(), nullptr));
|
||||
case Tok::True: next(); return std::make_unique<BoolExpr>(p, true);
|
||||
case Tok::False: next(); return std::make_unique<BoolExpr>(p, false);
|
||||
case Tok::Null: next(); return std::make_unique<NullExpr>(p);
|
||||
case Tok::Char: next(); return std::make_unique<CharExpr>(p, t.text[0]);
|
||||
case Tok::String: next(); return std::make_unique<StrExpr>(p, t.text);
|
||||
case Tok::LParen: {
|
||||
next();
|
||||
parenDepth++;
|
||||
ExprPtr e = expr();
|
||||
parenDepth--;
|
||||
expect(Tok::RParen);
|
||||
return e;
|
||||
}
|
||||
case Tok::Ident: {
|
||||
next();
|
||||
if (at(Tok::LParen) && !cur().newlineBefore) return callArgs(p, t.text);
|
||||
if (isTypeName(t.text))
|
||||
fail(p.line, p.col, "a type name alone is not a value (did you mean " + t.text + "(...)?)");
|
||||
return std::make_unique<VarExpr>(p, t.text);
|
||||
}
|
||||
default: unexpected("(expected a value)");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Program parse(const std::vector<Token> &tokens) {
|
||||
return Parser(tokens).program();
|
||||
}
|
||||
6
src/parser.h
Normal file
6
src/parser.h
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
|
||||
Program parse(const std::vector<Token> &tokens);
|
||||
3
tests/fail/bad_link_name.fn
Normal file
3
tests/fail/bad_link_name.fn
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// expect: write just the library's name
|
||||
link "libm.so"
|
||||
fn main() {}
|
||||
4
tests/fail/bool_bitand.fn
Normal file
4
tests/fail/bool_bitand.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: for true/false use '&&'
|
||||
fn main() {
|
||||
print(true & false)
|
||||
}
|
||||
5
tests/fail/condition_not_bool.fn
Normal file
5
tests/fail/condition_not_bool.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: a condition must be bool
|
||||
fn main() {
|
||||
if 1 {
|
||||
}
|
||||
}
|
||||
4
tests/fail/div_zero_const.fn
Normal file
4
tests/fail/div_zero_const.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: division by zero
|
||||
fn main() {
|
||||
print(1 / 0)
|
||||
}
|
||||
5
tests/fail/div_zero_runtime.fn
Normal file
5
tests/fail/div_zero_runtime.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: 4: runtime error: division by zero
|
||||
fn main() {
|
||||
zero := 0
|
||||
print(10 / zero)
|
||||
}
|
||||
5
tests/fail/float_to_int.fn
Normal file
5
tests/fail/float_to_int.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: cuts off the fraction
|
||||
fn main() {
|
||||
float f = 2.5
|
||||
int n = f
|
||||
}
|
||||
4
tests/fail/forgot_import.fn
Normal file
4
tests/fail/forgot_import.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: import "math.h"
|
||||
fn main() {
|
||||
print(sqrt(2))
|
||||
}
|
||||
5
tests/fail/lib_hint.fn
Normal file
5
tests/fail/lib_hint.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: link "lib_hint"
|
||||
import "lib_hint.h"
|
||||
fn main() {
|
||||
print(finch_test_missing(1))
|
||||
}
|
||||
1
tests/fail/lib_hint.h
Normal file
1
tests/fail/lib_hint.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
int finch_test_missing(int x);
|
||||
4
tests/fail/literal_too_big.fn
Normal file
4
tests/fail/literal_too_big.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: doesn't fit in u8
|
||||
fn main() {
|
||||
u8 b = 300
|
||||
}
|
||||
6
tests/fail/loop_var_readonly.fn
Normal file
6
tests/fail/loop_var_readonly.fn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// expect: can't be changed
|
||||
fn main() {
|
||||
for i in 0..3 {
|
||||
i = 5
|
||||
}
|
||||
}
|
||||
3
tests/fail/missing_header.fn
Normal file
3
tests/fail/missing_header.fn
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// expect: can't find the C header
|
||||
import "nope_not_here.h"
|
||||
fn main() {}
|
||||
3
tests/fail/missing_lib.fn
Normal file
3
tests/fail/missing_lib.fn
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// expect: the library 'definitely_not_a_lib' wasn't found
|
||||
link "definitely_not_a_lib"
|
||||
fn main() {}
|
||||
5
tests/fail/missing_return.fn
Normal file
5
tests/fail/missing_return.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: can reach its end without returning int
|
||||
fn f() -> int {
|
||||
print(1)
|
||||
}
|
||||
fn main() {}
|
||||
6
tests/fail/mixed_signs.fn
Normal file
6
tests/fail/mixed_signs.fn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// expect: can't use '+' on i32 and u32
|
||||
fn main() {
|
||||
i32 a = 1
|
||||
u32 b = 2
|
||||
print(a + b)
|
||||
}
|
||||
2
tests/fail/no_main.fn
Normal file
2
tests/fail/no_main.fn
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// expect: there is no main function
|
||||
fn helper() {}
|
||||
4
tests/fail/non_ascii_name.fn
Normal file
4
tests/fail/non_ascii_name.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: unexpected character 'ż' (names use only
|
||||
fn main() {
|
||||
żółw := 1
|
||||
}
|
||||
5
tests/fail/null_value.fn
Normal file
5
tests/fail/null_value.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: 4: runtime error: used .value on a null pointer
|
||||
fn main() {
|
||||
ptr[int] p = null
|
||||
print(p.value)
|
||||
}
|
||||
4
tests/fail/old_function_syntax.fn
Normal file
4
tests/fail/old_function_syntax.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: functions start with 'fn'
|
||||
int add(int a) {
|
||||
return a
|
||||
}
|
||||
5
tests/fail/redeclare.fn
Normal file
5
tests/fail/redeclare.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: already exists
|
||||
fn main() {
|
||||
x := 1
|
||||
x := 2
|
||||
}
|
||||
4
tests/fail/two_statements.fn
Normal file
4
tests/fail/two_statements.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: put each statement on its own line
|
||||
fn main() {
|
||||
x := 1 print(x)
|
||||
}
|
||||
4
tests/fail/type_mismatch.fn
Normal file
4
tests/fail/type_mismatch.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: 'x' must be int, but this is str
|
||||
fn main() {
|
||||
int x = "hi"
|
||||
}
|
||||
3
tests/fail/unclosed_brace.fn
Normal file
3
tests/fail/unclosed_brace.fn
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// expect: never closed
|
||||
fn main() {
|
||||
print(1)
|
||||
4
tests/fail/undefined_var.fn
Normal file
4
tests/fail/undefined_var.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: there is no variable named 'y'
|
||||
fn main() {
|
||||
print(y)
|
||||
}
|
||||
5
tests/fail/unreachable.fn
Normal file
5
tests/fail/unreachable.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: this code can never run
|
||||
fn main() {
|
||||
return
|
||||
print(1)
|
||||
}
|
||||
4
tests/fail/untyped_null.fn
Normal file
4
tests/fail/untyped_null.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// expect: null needs a type
|
||||
fn main() {
|
||||
p := null
|
||||
}
|
||||
6
tests/fail/untyped_ptr.fn
Normal file
6
tests/fail/untyped_ptr.fn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// expect: this ptr has no type
|
||||
import "stdlib.h"
|
||||
fn main() {
|
||||
p := malloc(8)
|
||||
print(p.value)
|
||||
}
|
||||
36
tests/run.sh
Executable file
36
tests/run.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
# Finch test suite.
|
||||
# tests/run/*.fn must compile and print exactly what is in the matching .out file
|
||||
# tests/fail/*.fn must fail; the first line is "// expect: <text that must appear in the error>"
|
||||
set -u
|
||||
cd "$(dirname "$0")"
|
||||
FINCH=${FINCH:-../build/finch}
|
||||
pass=0; failed=0
|
||||
|
||||
for f in run/*.fn; do
|
||||
want="${f%.fn}.out"
|
||||
got=$("$FINCH" run "$f" 2>&1)
|
||||
if [ "$got" == "$(cat "$want")" ]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
echo "FAIL $f"
|
||||
diff <(echo "$got") "$want" | head -20
|
||||
fi
|
||||
done
|
||||
|
||||
for f in fail/*.fn; do
|
||||
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
||||
got=$("$FINCH" run "$f" 2>&1)
|
||||
status=$?
|
||||
if [ $status -ne 0 ] && [[ "$got" == *"$expect"* ]]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
echo "FAIL $f (wanted: $expect)"
|
||||
echo "$got" | head -5
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$pass passed, $failed failed"
|
||||
[ $failed -eq 0 ]
|
||||
18
tests/run/basics.fn
Normal file
18
tests/run/basics.fn
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
fn main() {
|
||||
int a = 7
|
||||
b := 2
|
||||
print(a + b, a - b, a * b, a / b, a % b)
|
||||
print(-a / b, -a % b)
|
||||
x := 1.5
|
||||
print(x * 2, x + 1, 7 / 2.0)
|
||||
print(true && false, true || false, !true)
|
||||
print('A', "text", 1 < 2, 2 <= 2, 3 > 4, 4 >= 4, 1 == 1, 1 != 1)
|
||||
a += 3
|
||||
a -= 1
|
||||
a *= 2
|
||||
a /= 3
|
||||
a %= 4
|
||||
print(a)
|
||||
print()
|
||||
print("tab\there", "quote\"inside")
|
||||
}
|
||||
8
tests/run/basics.out
Normal file
8
tests/run/basics.out
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
9 5 14 3 1
|
||||
-3 -1
|
||||
3 2.5 3.5
|
||||
false true false
|
||||
A text true true false true true false
|
||||
2
|
||||
|
||||
tab here quote"inside
|
||||
9
tests/run/bits.fn
Normal file
9
tests/run/bits.fn
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fn main() {
|
||||
print(6 & 3, 6 | 3, 6 ^ 3, 1 << 4, 256 >> 2, ~0)
|
||||
print(5 & 1 == 1)
|
||||
u8 flags = 0
|
||||
flags = flags | 4
|
||||
flags = flags | 1
|
||||
print(flags, flags & 4 != 0, flags & 2 != 0)
|
||||
print(-16 >> 2, u8(240) >> 4)
|
||||
}
|
||||
4
tests/run/bits.out
Normal file
4
tests/run/bits.out
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
2 7 5 16 64 -1
|
||||
true
|
||||
5 true false
|
||||
-4 15
|
||||
16
tests/run/cimport.fn
Normal file
16
tests/run/cimport.fn
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import "stdio.h"
|
||||
import "math.h"
|
||||
import "stdlib.h"
|
||||
import "string.h"
|
||||
|
||||
fn main() {
|
||||
printf("%d-%s\n", 42, "printf")
|
||||
print(sqrt(16), pow(2, 10), floor(2.7))
|
||||
print(strlen("finch"), abs(-5), atoi("123"))
|
||||
print(EOF, RAND_MAX > 0, M_PI > 3.14)
|
||||
ptr[i32] n = malloc(4)
|
||||
n.value = 7
|
||||
print(n.value)
|
||||
free(n)
|
||||
fflush(stdout)
|
||||
}
|
||||
5
tests/run/cimport.out
Normal file
5
tests/run/cimport.out
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
42-printf
|
||||
4 1024 2
|
||||
5 5 123
|
||||
-1 true true
|
||||
7
|
||||
42
tests/run/control.fn
Normal file
42
tests/run/control.fn
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
fn classify(int n) -> str {
|
||||
if n < 0 {
|
||||
return "negative"
|
||||
} else if n == 0 {
|
||||
return "zero"
|
||||
} else {
|
||||
return "positive"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print(classify(-5), classify(0), classify(5))
|
||||
|
||||
total := 0
|
||||
for i in 0..100 {
|
||||
if i % 2 == 0 {
|
||||
continue
|
||||
}
|
||||
if i > 10 {
|
||||
break
|
||||
}
|
||||
total += i
|
||||
}
|
||||
print("odd sum below 10:", total)
|
||||
|
||||
n := 0
|
||||
while true {
|
||||
n += 1
|
||||
if n == 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
print("n:", n)
|
||||
|
||||
for i in 3..3 {
|
||||
print("never")
|
||||
}
|
||||
{
|
||||
inner := "blocks have their own scope"
|
||||
print(inner)
|
||||
}
|
||||
}
|
||||
4
tests/run/control.out
Normal file
4
tests/run/control.out
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
negative zero positive
|
||||
odd sum below 10: 25
|
||||
n: 5
|
||||
blocks have their own scope
|
||||
4
tests/run/exitcode.fn
Normal file
4
tests/run/exitcode.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
fn main() -> int {
|
||||
print("exiting")
|
||||
return 0
|
||||
}
|
||||
1
tests/run/exitcode.out
Normal file
1
tests/run/exitcode.out
Normal file
|
|
@ -0,0 +1 @@
|
|||
exiting
|
||||
28
tests/run/functions.fn
Normal file
28
tests/run/functions.fn
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
fn fib(int n) -> int {
|
||||
if n < 2 {
|
||||
return n
|
||||
}
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
fn later() -> int {
|
||||
return defined_below()
|
||||
}
|
||||
|
||||
fn defined_below() -> int {
|
||||
return 42
|
||||
}
|
||||
|
||||
fn greet(str who) {
|
||||
if who == "" {
|
||||
return
|
||||
}
|
||||
print("hi", who)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print(fib(20))
|
||||
print(later())
|
||||
greet("")
|
||||
greet("finch")
|
||||
}
|
||||
3
tests/run/functions.out
Normal file
3
tests/run/functions.out
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
6765
|
||||
42
|
||||
hi finch
|
||||
25
tests/run/pointers.fn
Normal file
25
tests/run/pointers.fn
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
fn double(ptr[int] p) {
|
||||
p.value = p.value * 2
|
||||
}
|
||||
|
||||
fn swap(ptr[int] a, ptr[int] b) {
|
||||
t := a.value
|
||||
a.value = b.value
|
||||
b.value = t
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := 21
|
||||
double(addr(x))
|
||||
print(x)
|
||||
a := 1
|
||||
b := 2
|
||||
swap(addr(a), addr(b))
|
||||
print(a, b)
|
||||
p := addr(a)
|
||||
pp := addr(p)
|
||||
pp.value.value = 99
|
||||
print(a)
|
||||
ptr[int] none = null
|
||||
print(none == null, p != null, none)
|
||||
}
|
||||
4
tests/run/pointers.out
Normal file
4
tests/run/pointers.out
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
42
|
||||
2 1
|
||||
99
|
||||
true true (nil)
|
||||
17
tests/run/types.fn
Normal file
17
tests/run/types.fn
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
fn main() {
|
||||
u8 small = 255
|
||||
small += 1
|
||||
print(small)
|
||||
i8 tiny = -128
|
||||
print(tiny, i8(200), u8(-1), u16(70000))
|
||||
i32 p = 1000
|
||||
i64 wide = p
|
||||
print(wide, p + 1)
|
||||
u32 big = 4000000000
|
||||
print(big, big / 3)
|
||||
f32 half = 0.5
|
||||
print(half * 2, float(half))
|
||||
print(int(3.99), int(-3.99), float(7) / 2, int('A'), char(66), bool(0), bool(5))
|
||||
print(0xFF, 1_000_000)
|
||||
print(u8(250) + 10)
|
||||
}
|
||||
8
tests/run/types.out
Normal file
8
tests/run/types.out
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
0
|
||||
-128 -56 255 4464
|
||||
1000 1001
|
||||
4000000000 1333333333
|
||||
1 0.5
|
||||
3 -3 3.5 65 B false true
|
||||
255 1000000
|
||||
4
|
||||
Loading…
Reference in a new issue