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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ build/
.DS_Store
deps/
design/
.vscode
.claude
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ test:
@passed=0; failed=0; errors=""; \
for f in tests/test_*.lv; do \
name=$$(basename $$f .lv); \
/tmp/lavina_next --emit-cpp $$f > /tmp/$$name.cpp 2>/dev/null && \
g++ -std=c++23 -I/tmp -o /tmp/$$name /tmp/$$name.cpp 2>/dev/null && \
/tmp/$$name 2>/dev/null; \
dir=$$(dirname $$f); \
/tmp/lavina_next compile $$f 2>/dev/null && \
$$dir/$$name 2>/dev/null; \
if [ $$? -eq 0 ]; then \
echo " PASS $$name"; \
passed=$$((passed + 1)); \
Expand All @@ -74,6 +74,7 @@ test:
failed=$$((failed + 1)); \
errors="$$errors $$name"; \
fi; \
rm -f $$dir/$$name $$dir/$$name.cpp; \
done; \
echo ""; \
echo "$$passed passed, $$failed failed"; \
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void fn main():
- **Block lambdas** — `(params):` with indented body
- **Module system** — `import a::b`, `public`/`private`, `as` aliases
- **Compile-time evaluation** — `comptime` / `comptime!`
- **FFI** — `extern` blocks, `cpp {}` inline C++, expanded types (`int8`/`int16`/`int32`, `float32`, `usize`, `cstring`, `ptr[T]`)
- **FFI** — `extern` blocks with `import`/`link` for automatic `-I`/`-l` flags, `cpp {}` inline C++, expanded types (`int8`/`int16`/`int32`, `float32`, `usize`, `cstring`, `ptr[T]`)
- **Package manager** — `lvpkg` for dependency management
- **Self-hosting** — the compiler bootstraps from a saved C++ snapshot

Expand Down Expand Up @@ -83,6 +83,7 @@ design/ language design documents
- `examples/complex/tree/` — file tree printer
- `examples/complex/lvg/` — Git client
- `examples/complex/webserver/` — HTTP todo app (uses httplib via lvpkg)
- `examples/complex/raylib/` — Raylib window with animated graphics
- `examples/sqlite.lv` — SQLite via FFI

## Package Manager
Expand All @@ -101,6 +102,9 @@ dep httplib https://github.com/yhirose/cpp-httplib v0.18.3 httplib.h

# directory of headers
dep json https://github.com/nlohmann/json v3.11.3 single_include/nlohmann/

# native library (pre-built binary + headers)
dep raylib https://github.com/raysan5/raylib 5.5 src/raylib.h
```

Then run:
Expand All @@ -112,7 +116,7 @@ lvpkg list # show dependency status
lvpkg clean # remove deps/
```

Dependencies are cloned into `deps/` and checked out at the pinned version.
Dependencies are cloned into `deps/` and checked out at the pinned version. Headers go to `deps/include/`, libraries to `deps/lib/`. The compiler automatically adds `-Ideps/include` and `-Ldeps/lib` when these directories exist.

## Documentation

Expand Down
8 changes: 8 additions & 0 deletions examples/complex/raylib/lavina.pkg
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Raylib example
#
# Headers are pulled from GitHub by lvpkg.
# The native library must be installed separately:
# macOS: brew install raylib
# Linux: sudo apt install libraylib-dev

dep raylib https://github.com/raysan5/raylib 5.5 src/raylib.h
71 changes: 71 additions & 0 deletions examples/complex/raylib/raylavina.lv
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Raylib example — basic window with animated circle
//
// Requires: raylib native library
// macOS: brew install raylib
// Linux: sudo apt install libraylib-dev
//
// Headers can be pulled via lvpkg (see lavina.pkg):
// lvpkg install
//
// Build:
// lavina compile raylavina.lv
//
// Note: import/link paths below are for Homebrew on macOS (arm64).
// Adjust paths for your platform if needed.

extern "raylib.h" import "/opt/homebrew/opt/raylib/include" link "/opt/homebrew/opt/raylib/lib/libraylib.dylib":
type Color
type Vector2

void fn InitWindow(int32 width, int32 height, cstring title)
void fn CloseWindow()
bool fn WindowShouldClose()
void fn BeginDrawing()
void fn EndDrawing()
void fn ClearBackground(Color color)
void fn DrawCircle(int32 x, int32 y, float32 radius, Color color)
void fn DrawText(cstring text, int32 x, int32 y, int32 fontSize, Color color)
void fn SetTargetFPS(int32 fps)
int32 fn GetScreenWidth()
int32 fn GetScreenHeight()
float fn GetTime() = "GetTime"

extern "cmath":
float fn sin_f(float x) = "std::sin"
float fn cos_f(float x) = "std::cos"
int fn lrint_f(float x) = "std::lrint"

// Lavina does not yet support extern struct literals, so we init Color via cpp helper
cpp {
Color make_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
Color c = {r, g, b, a};
return c;
}
}

extern "raylib.h":
Color fn make_color(int32 r, int32 g, int32 b, int32 a) = "make_color"

void fn main():
int32 width = 800
int32 height = 450

InitWindow(width, height, "Lavina + Raylib = <3")
SetTargetFPS(60)

Color bg = make_color(24, 24, 24, 255)
Color orange = make_color(255, 102, 0, 255)
Color white = make_color(238, 238, 238, 255)

while not WindowShouldClose():
float t = GetTime()
int32 cx = 400 + lrint_f(sin_f(t * 2.0) * 150.0)
int32 cy = 225 + lrint_f(cos_f(t * 3.0) * 100.0)

BeginDrawing()
ClearBackground(bg)
DrawCircle(cx, cy, 40.0, orange)
DrawText("Hello from Lavina!", 260, 20, 30, white)
EndDrawing()

CloseWindow()
5 changes: 1 addition & 4 deletions examples/complex/webserver/lavina.pkg
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
name = "webserver"
deps = [
"https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h"
]
dep httplib https://github.com/yhirose/cpp-httplib v0.18.3 httplib.h
57 changes: 54 additions & 3 deletions lvpkg/commands.lv
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
string DEPS_DIR = "deps"
string DEPS_SRC = "deps/src"
string DEPS_INCLUDE = "deps/include"
string DEPS_LIB = "deps/lib"

// Copy relevant files from a cloned dep into deps/include/
void fn link_dep(ref Dep dep):
Expand All @@ -26,6 +27,34 @@ void fn link_dep(ref Dep dep):
copy_dir_recursive(src_dir, dst)
info("${dep.name}: copied repo → include/${dep.name}/")

// Copy library files and headers from a cloned lib dep
void fn link_lib_dep(ref Dep dep):
string src_dir = "${DEPS_SRC}/${dep.name}"

// Copy library file to deps/lib/
string lib_src = "${src_dir}/${dep.lib_path}"
if ends_with(dep.lib_path, "/"):
// directory of libs — copy all into deps/lib/
copy_dir_recursive(lib_src, DEPS_LIB)
info("${dep.name}: copied ${dep.lib_path} → lib/")
else:
// single lib file
string lib_filename = basename(dep.lib_path)
copy_file(lib_src, "${DEPS_LIB}/${lib_filename}")
info("${dep.name}: copied ${lib_filename} → lib/")

// Copy headers if specified
if dep.path != "":
string hdr_src = "${src_dir}/${dep.path}"
if ends_with(dep.path, "/"):
string dst = "${DEPS_INCLUDE}/${dep.name}"
copy_dir_recursive(hdr_src, dst)
info("${dep.name}: copied ${dep.path} → include/${dep.name}/")
else:
string hdr_filename = basename(dep.path)
copy_file(hdr_src, "${DEPS_INCLUDE}/${hdr_filename}")
info("${dep.name}: copied ${hdr_filename} → include/")

// Install all dependencies: clone if missing, then link
void fn cmd_install():
auto deps = parse_pkg_file()
Expand All @@ -40,16 +69,26 @@ void fn cmd_install():
ensure_dir(DEPS_SRC)
ensure_dir(DEPS_INCLUDE)

bool has_libs = false
for ref dep in deps:
if dep.dep_type == "lib":
has_libs = true
if has_libs:
ensure_dir(DEPS_LIB)

for ref dep in deps:
string dest = "${DEPS_SRC}/${dep.name}"
if fs_is_dir(dest):
info("${dep.name}: already cloned, skipping (use 'update' to refresh)")
else:
git_clone(dep.url, dest, dep.version)
link_dep(dep)
if dep.dep_type == "lib":
link_lib_dep(dep)
else:
link_dep(dep)

print("")
print("${C_GREEN}${C_BOLD}Done!${C_RESET} Use ${C_CYAN}-I deps/include${C_RESET} when compiling.")
print("${C_GREEN}${C_BOLD}Done!${C_RESET}")

// Update all dependencies: fetch and checkout, then re-link
void fn cmd_update():
Expand All @@ -65,13 +104,23 @@ void fn cmd_update():
ensure_dir(DEPS_SRC)
ensure_dir(DEPS_INCLUDE)

bool has_libs = false
for ref dep in deps:
if dep.dep_type == "lib":
has_libs = true
if has_libs:
ensure_dir(DEPS_LIB)

for ref dep in deps:
string dest = "${DEPS_SRC}/${dep.name}"
if fs_is_dir(dest):
git_update(dest, dep.version)
else:
git_clone(dep.url, dest, dep.version)
link_dep(dep)
if dep.dep_type == "lib":
link_lib_dep(dep)
else:
link_dep(dep)

print("")
print("${C_GREEN}${C_BOLD}Done!${C_RESET}")
Expand All @@ -93,6 +142,8 @@ void fn cmd_list():
string path_info = ""
if dep.path != "":
path_info = " ${C_DIM}→ ${dep.path}${C_RESET}"
if dep.lib_path != "":
path_info += " ${C_DIM}lib: ${dep.lib_path}${C_RESET}"

print(" ${C_CYAN}${dep.name}${C_RESET} ${dep.url} @ ${C_YELLOW}${dep.version}${C_RESET}${path_info} ${status}")

Expand Down
23 changes: 21 additions & 2 deletions lvpkg/dep.lv
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
string PKG_FILE = "lavina.pkg"

struct Dep:
string dep_type
string name
string url
string version
string path
string lib_path

// Parse lavina.pkg and return a list of dependencies
// Format: dep <name> <url> <version> [path]
// Format: dep <name> <url> <version> [header_path]
// lib <name> <url> <version> <lib_path> [header_path]
vector[Dep] fn parse_pkg_file():
vector[Dep] deps = []
if not fs_exists(PKG_FILE):
Expand All @@ -35,7 +38,23 @@ vector[Dep] fn parse_pkg_file():
if parts.len() >= 5:
path = parts[4]

deps.push(Dep(name, url, version, path))
deps.push(Dep("dep", name, url, version, path, ""))

elif starts_with(trimmed, "lib "):
auto parts = trimmed.split(" ")
if parts.len() < 5:
warn("Invalid lib line (need: lib <name> <url> <version> <lib_path> [header_path]): ${trimmed}")
continue

string name = parts[1]
string url = parts[2]
string version = parts[3]
string lpath = parts[4]
string hpath = ""
if parts.len() >= 6:
hpath = parts[5]

deps.push(Dep("lib", name, url, version, hpath, lpath))
else:
warn("Unknown directive: ${trimmed}")

Expand Down
8 changes: 6 additions & 2 deletions lvpkg/lvpkg.lv
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// lvpkg — Lavina Package Manager
//
// A simple dependency manager for Lavina projects.
// Pulls C++ header-only libraries and Lavina modules from GitHub.
// Pulls C++ header-only libraries, native libraries, and Lavina modules from GitHub.
//
// Usage:
// lvpkg install Install dependencies from lavina.pkg
Expand All @@ -10,11 +10,13 @@
// lvpkg clean Remove deps/ directory
//
// Package file format (lavina.pkg):
// dep <name> <url> <version> [path]
// dep <name> <url> <version> [header_path]
// lib <name> <url> <version> <lib_path> [header_path]
//
// Examples:
// dep httplib https://github.com/yhirose/cpp-httplib v0.18.3 httplib.h
// dep json https://github.com/nlohmann/json v3.11.3 single_include/nlohmann/
// lib raylib https://github.com/raysan5/raylib 5.0 lib/libraylib.a include/raylib.h

import colors
import strutil
Expand All @@ -38,6 +40,8 @@ void fn print_help():
print(" ${C_CYAN}dep httplib https://github.com/yhirose/cpp-httplib v0.18.3 httplib.h${C_RESET}")
print(" ${C_DIM}# directory of headers${C_RESET}")
print(" ${C_CYAN}dep json https://github.com/nlohmann/json v3.11.3 single_include/nlohmann/${C_RESET}")
print(" ${C_DIM}# native library with headers${C_RESET}")
print(" ${C_CYAN}dep raylib https://github.com/raysan5/raylib 5.5 src/raylib.h${C_RESET}")

void fn main():
init_colors()
Expand Down
2 changes: 1 addition & 1 deletion src/ast.lv
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,4 @@ enum Stmt:
Continue(Token keyword)
Pass(Token keyword)
CppBlock(string code)
Extern(string header, string link_lib, vector[ExternType] types, vector[ExternFn] functions)
Extern(string header, string import_path, string link_lib, vector[ExternType] types, vector[ExternFn] functions)
4 changes: 2 additions & 2 deletions src/checker.lv
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ class Checker:
Namespace(name, body, visibility):
for ref ns_stmt in body:
this.collect_decl(ns_stmt)
Extern(header, link_lib, types, functions):
Extern(header, import_path, link_lib, types, functions):
for ref et in types:
vector[Stmt] empty_body = []
this.known_classes[et.lavina_name] = empty_body
Expand Down Expand Up @@ -563,7 +563,7 @@ class Checker:
pass
CppBlock(code):
pass
Extern(header, link_lib, types, functions):
Extern(header, import_path, link_lib, types, functions):
pass
_:
pass
Expand Down
Loading