Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Build

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Linux (GCC)
os: ubuntu-latest
compiler: g++
artifact: ascend-linux-x64
- name: Linux (Clang)
os: ubuntu-latest
compiler: clang++
- name: macOS
os: macos-latest
artifact: ascend-macos-arm64
- name: Windows (MSVC)
os: windows-latest
artifact: ascend-windows-x64

steps:
- uses: actions/checkout@v4

- name: Configure
if: ${{ !matrix.compiler }}
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

- name: Configure with ${{ matrix.compiler }}
if: ${{ matrix.compiler }}
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler }}

- name: Build
run: cmake --build build --config Release --parallel

- name: Test
run: ctest --test-dir build --build-config Release --output-on-failure

- name: Upload the interpreter
if: ${{ matrix.artifact }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: |
build/ascend
build/Release/ascend.exe
if-no-files-found: error

sanitizers:
name: Sanitizers (ASan + UBSan)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# Newer kernels use more address randomization than older ASan runtimes expect.
- name: Make address randomization compatible with ASan
run: sudo sysctl vm.mmap_rnd_bits=28

- name: Configure
run: >
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++
"-DCMAKE_CXX_FLAGS=-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all"

- name: Build
run: cmake --build build --parallel

- name: Test
run: ctest --test-dir build --output-on-failure
env:
ASAN_OPTIONS: detect_leaks=1
65 changes: 65 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Changelog

## 0.3.0

### New

- **Optional C-style types**: `type name = value` declarations, like
`u8 age = 30`, with `i8` `i16` `i32` `i64` `u8` `u16` `u32` `u64` `byte`
`int` `f32` `f64` `float` `bool` `char` `string` `list` `map` `func` `any`,
and nullable types like `string?`. Typed variables reject values of the
wrong type, and numbers that don't fit. `type name` without a value starts
at the type's zero value.
- Every number carries its type. Arithmetic stays in that type and reports
overflow as an error (`250u8 + 10` fails instead of wrapping).
- C-style typed functions: `f64 area(f64 w, f64 h) { }` and
`void log(string message) { }`. Untyped functions still use `func`, and
anonymous functions can declare a return type with `func(i32 x) -> i32 { }`.
- `const` declarations.
- `match` statements.
- f-strings with format specs: `f"{name:<10} {price:>8.2f}"`, and `format()`.
- `char` type and literals: `'a'`, `'\n'`, `'\u{1F600}'`; char arithmetic.
- Hex, binary and octal literals (`0xFF`, `0b1010`, `0o17`) and type
suffixes (`255u8`, `1.5f32`).
- Bitwise operators `& | ^ ~ << >>` and their compound assignments.
- Unicode strings: `len`, indexing, slicing and iteration count characters.
- Modules: `import("file")` returns a file's top-level names as a map.
- Type mismatches in literals (`u8 x = 300`) and in `return` values are
reported before the program runs.
- New builtins: type conversions (`i8()` ... `u64()`, `f32()`, `f64()`,
`char()`, `string()`, `byte()`), `format`, `hex`, `bin`, `chars`, `bytes`,
`from_bytes`, `lines`, `pad_left`, `pad_right`, `count`, `is_digit` and
friends, `zip`, `unique`, `flatten`, `extend`, `clear`, `find`, `any`, `all`,
`items`, `merge`, `clamp`, `sign`, `gcd`, trigonometry and logarithms,
`choice`, `shuffle`, `INF`, `NAN`, file functions (`read_file`,
`write_file`, `append_file`, `read_lines`, `file_exists`, `delete_file`),
`json_parse`, `json_stringify`, `clock`, `sleep`, `date`, `env`, `system`.
- `ascend --check file.asc` reports errors without running the program.
- Syntax highlighting for VS Code in `editors/vscode`.
- GitHub Actions builds and tests every change on Linux, macOS and Windows,
and uploads the built interpreters.

### Changed (may affect existing programs)

- Single quotes now make a `char`, not a string: write `"it's"` instead of
`'it\'s'`.
- `&` and `|` are now bitwise operators (they used to be errors). Use `and` /
`or` (or `&&` / `||`) for logic.
- Indexing a string gives a `char` instead of a one-character string. Chars
compare equal to the matching one-character string, so `s[0] == "a"` still
works.
- String lengths, indexes and slices count characters instead of bytes.
- `\xHH` in a string is now the character with that code (as in Python),
encoded as UTF-8.
- `type()` returns `"func"` for functions (was `"function"`).
- Builtins now live in a scope above the program's globals, so a program can
reuse their names for its own variables.

## 0.2.0

- Rewrote the interpreter as a lexer, parser, semantic checker and
tree-walking interpreter, fixing the bugs in `!=`, `while`, `for` and
function calls.
- Added floats, strings, bools, lists, maps, closures, `try` / `catch`,
`else if`, `break` / `continue`, method-call syntax, slices, a REPL, error
messages with source locations, a test suite and documentation.
14 changes: 13 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10)

project(ascend VERSION 0.2.0 LANGUAGES CXX)
project(ascend VERSION 0.3.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand All @@ -10,15 +10,27 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

# The sources and tests contain UTF-8 text; MSVC otherwise assumes the system code page.
if(MSVC)
add_compile_options(/utf-8)
endif()

# Everything except main() lives in a library so the tests can use it too.
add_library(ascend_core STATIC
src/error.cpp
src/types.cpp
src/utf8.cpp
src/numeric.cpp
src/typecheck.cpp
src/stack_limit.cpp
src/lexer.cpp
src/parser.cpp
src/semantic_analyzer.cpp
src/value.cpp
src/interpreter.cpp
src/stdlib.cpp
src/format.cpp
src/json.cpp
src/runner.cpp
src/repl.cpp
)
Expand Down
54 changes: 35 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Ascend

Ascend is a small, friendly, dynamically typed scripting language with an
interpreter written in C++17.
[![Build](https://github.com/csubbdev/Ascend/actions/workflows/build.yml/badge.svg)](https://github.com/csubbdev/Ascend/actions/workflows/build.yml)

Ascend is a small, friendly scripting language with optional types, written in
C++17.

```
func fizzbuzz(n) {
string fizzbuzz(i32 n) {
if n % 15 == 0 { return "FizzBuzz" }
if n % 3 == 0 { return "Fizz" }
if n % 5 == 0 { return "Buzz" }
Expand All @@ -15,45 +17,59 @@ for i in range(1, 16) {
print(fizzbuzz(i))
}

u8 level = 2
match level {
1 => print("easy")
2, 3 => print("normal")
else => print("hard")
}

scores := {ada: 95, linus: 88}
scores.grace = 91
for name, score in scores {
print(name, score >= 90 ? "top" : "ok")
print(f"{name:<6} {score:>3}")
}
```

## Features

- Ints, floats, strings, bools, `null`, lists and insertion-ordered maps
- `if` / `else if` / `else`, `while`, `for ... in` over ranges, lists, strings and maps, `break` / `continue`
- **Optional C-style types**: `u8 age = 30`, `i32 add(i32 a, i32 b) { }` with
`i8`–`i64`, `u8`–`u64`, `byte`, `f32`, `f64`, `bool`, `char`, `string`,
`list`, `map`, `func`, `void` and nullable types like `string?`. Typed
variables, parameters and return values are checked, and integer overflow is
an error instead of a silent wrap.
- Constants, `match`, `if` / `else if`, `while`, `for ... in` over ranges,
lists, strings and maps, `break` / `continue`
- Functions with default parameters, recursion, closures and anonymous functions
- Method-call syntax for builtins: `items.push(4)`, `"a,b".split(",")`
- f-strings with format specs: `f"{price:>8.2f}"`
- Hex, binary and octal literals, type suffixes (`255u8`) and bitwise operators
- Unicode strings and a `char` type
- Modules with `import("file")`
- `try` / `catch` / `throw` error handling
- 50+ builtins for strings, lists, maps, math and I/O
- Clear error messages that point at the problem:
- 100+ builtins for strings, lists, maps, math, files, JSON and the system
- Clear error messages that point at the problem, many caught before running:

```
script.asc:2:7: runtime error: undefined variable 'cuont' (did you mean 'count'?)
2 | print(cuont)
| ^
script.asc:3:10: error: 'age' is declared as u8, so it can't hold 300 (u8 is 0 to 255)
3 | u8 age = 300
| ^
```

- An interactive REPL with multi-line input
- An interactive REPL, a `--check` mode, and syntax highlighting for VS Code

## Quick start

```sh
cmake -S . -B build
cmake --build build
./build/ascend examples/fizzbuzz.asc
./build/ascend examples/types.asc
./build/ascend # interactive REPL
```

## Documentation

- [Language guide](docs/syntax.md)
- [Standard library](docs/standard_library.md)
- [Building, installing and testing](docs/installation.md)
- [Building, installing, testing and editor support](docs/installation.md)
- [Example programs](examples/)

## How it works
Expand All @@ -62,10 +78,10 @@ cmake --build build
|-------|--------|
| Lexer: source text to tokens | `src/lexer.cpp` |
| Parser: tokens to a syntax tree | `src/parser.cpp`, `src/include/ast.hpp` |
| Semantic checks (e.g. `break` outside a loop) | `src/semantic_analyzer.cpp` |
| Checks before running (types of literals, `break` outside loops, ...) | `src/semantic_analyzer.cpp`, `src/typecheck.cpp` |
| Tree-walking interpreter | `src/interpreter.cpp` |
| Values, and a cycle collector for closures | `src/value.cpp`, `src/include/environment.hpp` |
| Builtin functions | `src/stdlib.cpp` |
| Values, numeric types and checked arithmetic | `src/value.cpp`, `src/numeric.cpp`, `src/types.cpp` |
| Builtin functions, formatting and JSON | `src/stdlib.cpp`, `src/format.cpp`, `src/json.cpp` |
| Command line and REPL | `src/main.cpp`, `src/repl.cpp`, `src/runner.cpp` |

## License
Expand Down
16 changes: 16 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ with Visual Studio). Builds default to the optimized `Release` configuration.
./build/ascend examples/fizzbuzz.asc # run a program
./build/ascend # start the interactive REPL
./build/ascend -e 'print(2 ** 10)' # run a one-liner
./build/ascend --check my_program.asc # find errors without running
```

To run `ascend` from anywhere, copy `build/ascend` into a directory on your
Expand All @@ -46,3 +47,18 @@ filter them by name:
```

To skip building the tests, configure with `-DASCEND_BUILD_TESTS=OFF`.

## Continuous integration

Every push to `main` and every pull request is built and tested by GitHub
Actions (`.github/workflows/build.yml`) on Linux (GCC and Clang), macOS and
Windows (MSVC), plus a run with AddressSanitizer and UndefinedBehaviorSanitizer.
Each run also uploads ready-to-use interpreters for Linux, macOS and Windows:
open the run on the repository's **Actions** tab and download them under
**Artifacts**.

## Editor support

`editors/vscode` is a Visual Studio Code extension with syntax highlighting,
bracket matching and comment toggling for `.asc` files. See its
[README](../editors/vscode/README.md) to install it.
Loading
Loading