展示 HN:OtterLang – 一种 Python 风格的脚本语言,编译成原生代码。
Show HN: OtterLang – Pythonic scripting language that compiles to native code

原始链接: https://github.com/jonathanmagambo/otterlang

## OtterLang:一种快速、简单和现代的编程语言 OtterLang 是一种新的、对缩进敏感的编程语言,旨在实现简单性和性能。它使用 LLVM 编译为原生二进制文件,提供原生速度,并具有透明的 Rust FFI 等功能——允许无手动绑定地与现有的 Rust 代码无缝集成。 主要特性包括简洁的语法、用于数学、I/O、时间、网络和 JSON 处理的内置模块,以及具有零成本抽象的现代异常处理。 OtterLang 还支持 WebAssembly 编译。 **入门:** 需要 Rust nightly 和 LLVM 18。安装方式因操作系统而异(推荐 Nix)。然后,您可以直接构建和运行程序,或者利用 FFI 通过 `rustdoc` 自动使用 Rust crates。 **目前:** OtterLang 处于早期访问阶段 (v0.1.0) 且尚未达到生产就绪状态。类型推断有限,模块系统存在一些限制。基准测试正在重新测试中。欢迎贡献! **在此处找到它:** [https://github.com/jonathanmagambo/otterlang](https://github.com/jonathanmagambo/otterlang)

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 展示 HN: OtterLang – 一种 Python 风格的脚本语言,编译为本机代码 (github.com/jonathanmagambo) 7 分,来自 otterlang 3 小时前 | 隐藏 | 过去 | 收藏 | 讨论 大家好!我一直在构建 OtterLang,一种小型实验性脚本语言,旨在感觉像 Python,但通过 LLVM 编译为本机二进制文件。 目标不是重新发明 Python 或 Rust,而是找到它们之间的中间地带: Python 般的易读性和语法 Rust 级别的性能和类型安全 快速构建和透明的 Rust FFI(您可以直接导入 Rust crates,而无需编写绑定) OtterLang 仍然处于早期阶段,并且非常具有实验性。编译器、运行时和 FFI 桥接正在被频繁重写。 请 star 仓库,并贡献以帮助这个项目。 考虑申请 YC 的 2026 年冬季批次!申请截止日期为 11 月 10 日 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系方式 搜索:
相关文章

原文

OtterLang Logo
Simple syntax, native performance, transparent Rust FFI.

Build Status Discord

An indentation-sensitive programming language with an LLVM backend. OtterLang compiles to native binaries with a focus on simplicity and performance.

git clone https://github.com/jonathanmagambo/otterlang.git
cd otterlang

# Using Nix (recommended)
nix develop
cargo +nightly build --release

# Create and run your first program
cat > hello.ot << 'EOF'
use otter:fmt

fn main():
    fmt.println("Hello from OtterLang!")
EOF

otter run hello.ot
nix develop
cargo +nightly build --release

The Nix flake automatically provides Rust nightly, LLVM 18, and all dependencies.

Prerequisites:

  • Rust (via rustup) - nightly required for FFI features
  • LLVM 18

macOS:

brew install llvm@18
export LLVM_SYS_181_PREFIX=$(brew --prefix llvm@18)
export LLVM_SYS_180_PREFIX=$LLVM_SYS_181_PREFIX
export PATH="$LLVM_SYS_181_PREFIX/bin:$PATH"
rustup toolchain install nightly
cargo +nightly build --release

Ubuntu/Debian:

sudo apt-get install -y llvm-18 llvm-18-dev clang-18
export LLVM_SYS_181_PREFIX=/usr/lib/llvm-18
export LLVM_SYS_180_PREFIX=$LLVM_SYS_181_PREFIX
rustup toolchain install nightly
cargo +nightly build --release

Windows:

# Install LLVM 18.1 using llvmenv (recommended)
cargo install llvmenv --locked
llvmenv install 18.1
llvmenv global 18.1

# Set environment variables
$llvmPath = llvmenv prefix
$env:LLVM_SYS_181_PREFIX = $llvmPath
$env:LLVM_SYS_180_PREFIX = $llvmPath
$env:Path = "$llvmPath\bin;$env:Path"

# Alternative: Install using winget or Chocolatey
# winget install --id LLVM.LLVM --silent --accept-package-agreements --accept-source-agreements
# choco install llvm -y
# $env:LLVM_SYS_181_PREFIX = "C:\Program Files\LLVM"
# $env:LLVM_SYS_180_PREFIX = $env:LLVM_SYS_181_PREFIX
# $env:Path = "$env:LLVM_SYS_181_PREFIX\bin;$env:Path"

# Install Rust nightly
rustup toolchain install nightly
rustup default nightly

# Build
cargo +nightly build --release

Note for Windows: If using winget/Chocolatey, LLVM may be installed in C:\Program Files\LLVM or C:\Program Files (x86)\LLVM.

Important: On Windows, you must use the x64 Native Tools Command Prompt for VS 2022 to build. The MSVC linker requires environment variables that are automatically set in the Developer Command Prompt. Open it from the Start menu, then navigate to your project directory and run the build commands. Regular PowerShell/CMD will not have the MSVC environment configured.

Once the build completes successfully, you can:

Run a program:

cargo +nightly run --release --bin otterlang -- run examples/basic/hello.ot

Build an executable:

cargo +nightly run --release --bin otterlang -- build examples/basic/hello.ot -o hello

Run tests:

cargo +nightly test --release

Use the compiler directly:

# The binary is located at:
# target/release/otterlang (or target/release/otterlang.exe on Windows)
./target/release/otterlang run program.ot
# Or on Windows:
# target\release\otterlang.exe run program.ot

Clean indentation-based syntax with modern features:

use otter:fmt
use otter:math

fn greet(name: str) -> str:
    return "Hello, " + name + "!"

struct Point:
    x: float
    y: float

    fn distance(self) -> float:
        return math.sqrt(self.x * self.x + self.y * self.y)

fn main():
    let message = greet("World")
    fmt.println(message)

    let p = Point(x=3.0, y=4.0)
    let dist = p.distance()
    fmt.println("Point: (" + stringify(p.x) + ", " + stringify(p.y) + "), distance: " + stringify(dist))

    if len(message) > 10:
        fmt.println("Long message")

    for i in 0..10:
        fmt.println(stringify(i))

Automatically use any Rust crate without manual configuration:

use rust:rand
use otter:fmt

fn main():
    let random = rand.random_f64()
    fmt.println("Random: " + stringify(random))

Key advantages:

  • No manual bindings needed
  • Automatic API extraction via rustdoc (requires Rust nightly)
  • Memory management handled automatically
  • Async/await support for Rust Futures
  • Type checking integrated

See docs/FFI_TRANSPARENT.md for details.

Built-in modules:

  • otter:math - Mathematical functions
  • otter:io - File I/O
  • otter:time - Time utilities
  • otter:task - Task-based concurrency
  • otter:rand - Random numbers
  • otter:json - JSON parsing
  • otter:net - Networking
  • otter:http - HTTP client/server

Modern exception handling with zero-cost success path:

use otter:fmt

fn divide(x: int, y: int) -> int:
    if y == 0:
        raise "Division by zero"
    return x / y

fn safe_operation():
    try:
        let result = divide(10, 0)
        fmt.println("Result: " + stringify(result))
    except Error as e:
        fmt.println("Caught error: " + stringify(e))
    else:
        fmt.println("No errors occurred")
    finally:
        fmt.println("Cleanup always runs")

fn nested_exceptions():
    try:
        try:
            raise "Inner error"
        except Error:
            fmt.println("Handled inner error")
            raise "Outer error"
    except Error:
        fmt.println("Handled outer error")

Features:

  • try/except/else/finally blocks
  • Exception propagation with automatic cleanup
  • Zero-cost abstractions (no overhead on success path)
  • Type-safe error handling at compile time

Note: Benchmarks are currently being retested and properly specified. Comprehensive performance metrics will be available in a future update. OtterLang compiles to native code via LLVM and is designed for high performance, with automatic memory management and zero-cost abstractions.

otterlang run program.ot          # Run program
otterlang build program.ot -o out # Build executable
otterlang build program.ot --target wasm32-unknown-unknown -o out.wasm # Build to WebAssembly
otterlang fmt                      # Format code
otterlang repl                     # Start REPL
otterlang profile memory program.ot # Profile memory

OtterLang can compile to WebAssembly! Use the --target flag:

# Compile to WebAssembly (wasm32-unknown-unknown)
otterlang build program.ot --target wasm32-unknown-unknown -o program.wasm

# Compile to WebAssembly System Interface (wasm32-wasi)
otterlang build program.ot --target wasm32-wasi -o program.wasm

Requirements:

  • LLVM 18 with WebAssembly target support
  • clang and wasm-ld in your PATH (usually included with LLVM)

When targeting wasm32-wasi the generated binary can talk directly to WASI's stdio and wall-clock APIs. For the more barebones wasm32-unknown-unknown target we import a minimal host surface so you can decide how to surface output:

  • env.otter_write_stdout(ptr: i32, len: i32) – write UTF-8 data to stdout
  • env.otter_write_stderr(ptr: i32, len: i32) – write UTF-8 data to stderr
  • env.otter_time_now_ms() -> i64 – optional wall-clock timestamp in ms

A tiny JavaScript host that wires these up under Node.js looks like:

import fs from 'node:fs';

const memory = new WebAssembly.Memory({ initial: 8 });
const decoder = new TextDecoder();

const env = {
  memory,
  otter_write_stdout(ptr, len) {
    const bytes = new Uint8Array(memory.buffer, ptr, len);
    process.stdout.write(decoder.decode(bytes));
  },
  otter_write_stderr(ptr, len) {
    const bytes = new Uint8Array(memory.buffer, ptr, len);
    process.stderr.write(decoder.decode(bytes));
  },
  otter_time_now_ms() {
    return BigInt(Date.now());
  },
};

const { instance } = await WebAssembly.instantiate(fs.readFileSync('program.wasm'), { env });
instance.exports.main?.();

The generated .wasm file can be run in any WebAssembly runtime (Node.js, browsers, wasmtime, etc.).

Basic Programs:

  • examples/basic/hello.ot - Hello world
  • examples/basic/exception_basics.ot - Exception handling basics
  • examples/basic/exception_advanced.ot - Advanced exceptions
  • examples/basic/exception_resource.ot - Resource management
  • examples/basic/exception_validation.ot - Data validation
  • examples/basic/struct_methods_demo.ot - Struct methods
  • examples/basic/struct_demo.ot - Struct usage
  • examples/basic/advanced_pipeline.ot - Complex computation
  • examples/basic/task_benchmark.ot - Task benchmarks
  • examples/basic/fibonacci.ot - Fibonacci sequence
  • examples/basic/pythonic_demo.ot - Pythonic style
  • examples/basic/multiline_test.ot - Multi-line strings

FFI Examples:

  • examples/ffi/ffi_rand_demo.ot - Random number generation
  • examples/ffi/ffi_rand_advanced.ot - Advanced FFI usage

Below are illustrative examples of how unit tests will look in OtterLang. A built-in otter test runner is planned (see roadmap.md).

use otter:fmt

struct User:
    id: int
    name: str

fn make_users() -> list<User>:
    return [User(id=1, name="Ana"), User(id=2, name="Bo")] 

fn to_map(users: list<User>) -> dict<int, str>:
    let m = { }
    for u in users:
        m[u.id] = u.name
    return m

fn test_user_list_basic():
    let xs = make_users()
    assert len(xs) == 2
    assert xs[0].name == "Ana"

fn test_dict_building():
    let m = to_map(make_users())
    assert m[1] == "Ana"
    assert m.get(3, default="none") == "none"

fn test_nested_structs_and_lists():
    struct Team:
        name: str
        members: list<User>

    let team = Team(name="core", members=make_users())
    assert team.members[1].id == 2

The test runner will discover functions prefixed with test_ and report pass/fail results with spans and diffs.

Early Access (v0.1.0) - Experimental, not production-ready.

  • Type inference is limited (explicit types recommended)
  • Module system has some limitations
  • Requires LLVM 18 and Rust nightly (for FFI features)

Contributions welcome! See CONTRIBUTING.md.

MIT License - see LICENSE.

联系我们 contact @ memedata.com