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
79 changes: 79 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Build

on:
push:
branches: [main]
tags: ['v*']
pull_request:

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-26-intel, macos-latest]

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Build wheels
uses: pypa/cibuildwheel@v3.4.1
with:
output-dir: wheelhouse

- uses: actions/upload-artifact@v4
with:
name: autumn-cpp-wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl

build_wasm:
name: Build wasm interpreter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- uses: emscripten-core/setup-emsdk@v15

- name: Build Autumn.wasm
run: |
emcmake cmake -S . -B build-wasm -DCMAKE_BUILD_TYPE=Release
cmake --build build-wasm -j

- name: Stage artifacts
run: |
mkdir autumn.wasm
cp build-wasm/interpreter_web.js \
build-wasm/interpreter_web.wasm \
autumnstdlib/stdlib.sexp \
autumn.wasm/
tar cJf autumn.wasm.tar.xz autumn.wasm

- uses: actions/upload-artifact@v4
with:
name: autumn-wasm-tarball
path: autumn.wasm.tar.xz

release:
if: github.ref_type == 'tag'
needs: [build_wheels, build_wasm]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

- name: Release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/*.whl
artifacts/autumn.wasm.tar.xz
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
build/
build-wasm/
.cache/
.venv/
stderr*
stdout*
std*err*
Expand Down
150 changes: 106 additions & 44 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,60 +1,77 @@
cmake_minimum_required(VERSION 3.5)
project(FunctionalInterpreter)
cmake_minimum_required(VERSION 3.18)
project(FunctionalInterpreter VERSION 1.0.0)

# Force use of the specified Python3_EXECUTABLE if provided
if(DEFINED Python3_EXECUTABLE)
set(Python3_EXECUTABLE "${Python3_EXECUTABLE}" CACHE FILEPATH "Python3 executable" FORCE)
set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}" CACHE FILEPATH "Python executable" FORCE)
message(STATUS "Using explicitly provided Python: ${Python3_EXECUTABLE}")
endif()

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Enable lto by default
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)

# Emit compile_commands.json for use with clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# If CXX contains clang, set -O3 -flto -fwhole-program-vtables
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(contain_clang TRUE)
else()
set(contain_clang FALSE)
endif()

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(contain_gcc TRUE)
endif()

if(MSVC) # built-in shortcut for 'CMAKE_CXX_COMPILER_ID STREQUAL "MSVC"'
set(contain_msvc TRUE)
endif()
message("CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}")
if(contain_clang)
message("Contain Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -flto -fwhole-program-vtables")
elseif(contain_gcc)
message("Contain GCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -flto \
-fno-semantic-interposition -fvisibility=hidden -fvisibility-inlines-hidden")
elseif(contain_msvc)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2 /flto /GL")
file(GLOB_RECURSE SOURCES "src/*.cpp")
include_directories(include include/interpreter include/lexer include/parser
include/interpreter/valuesystem include/interpreter/typesystem)

# Emscripten / WebAssembly build
# Usage:
# emcmake cmake -S . -B build-wasm -DCMAKE_BUILD_TYPE=Release
# cmake --build build-wasm --target interpreter_web
if(EMSCRIPTEN)
message(STATUS "Building for Emscripten/WASM (build type: ${CMAKE_BUILD_TYPE})")

add_executable(interpreter_web tools/bindings.cpp ${SOURCES})
set_target_properties(interpreter_web PROPERTIES SUFFIX ".js")

target_compile_options(interpreter_web PRIVATE
-fvisibility=hidden -fvisibility-inlines-hidden
-msimd128
-fwasm-exceptions)

target_link_options(interpreter_web PRIVATE
--bind
-fwasm-exceptions
"SHELL:-s EXPORT_ES6=1"
"SHELL:-s MODULARIZE=1"
"SHELL:-s EXPORT_NAME=createInterpreterModule"
"SHELL:-s ALLOW_MEMORY_GROWTH=1")

# Skip all the native-only machinery below (pybind11, Julia, shell tools).
return()
endif()

option(AUTUMN_DEV_MODE "Enable development mode with debug prints" OFF)
option(AUTUMN_BUILD_PYTHON_MODULE "Build the pybind11 Python extension (interpreter_module)" ON)

if(AUTUMN_DEV_MODE)
add_definitions(-DAUTUMN_DEV_MODE)
endif()

file(GLOB_RECURSE SOURCES "src/*.cpp")

# Include directories
include_directories(include include/interpreter include/lexer include/parser include/interpreter/valuesystem include/interpreter/typesystem)
# SOURCES and include_directories are declared near the top of this file so
# both the WASM and native branches can share them.

add_executable(sexp_parser tools/sexp_parser.cpp src/parser/sexpresso.cpp)
add_executable(generate_ast tools/generate_ast.cpp)
add_executable(lexer tools/lexer.cpp src/Token.cpp)
add_executable(parser tools/parser.cpp src/Token.cpp src/parser/sexpresso.cpp)


add_library(AutumnLib ${SOURCES})
target_include_directories(AutumnLib PUBLIC
${PROJECT_SOURCE_DIR}
)

target_link_libraries(parser PRIVATE AutumnLib)

add_executable(TokenTypeTest
test_suites/test_token_type.cpp
)
Expand All @@ -64,20 +81,65 @@ target_link_libraries(TokenTypeTest PRIVATE AutumnLib)
enable_testing()
add_test(NAME TokenTypeTest COMMAND TokenTypeTest)

# Set python executable path
# Check if /opt/homebrew/bin/python exists
if(EXISTS "/opt/homebrew/bin/python")
set(PYTHON_EXECUTABLE "/opt/homebrew/bin/python")
endif()

message("SOURCES: ${SOURCES}")
# Create the executable
# Create the native CLI executable
add_executable(interpreter tools/main.cpp ${SOURCES})

find_package(pybind11 REQUIRED)
if(AUTUMN_BUILD_PYTHON_MODULE)
# Set python executable path
# If Python3_EXECUTABLE was passed to CMake, use it exclusively
if(NOT DEFINED Python3_EXECUTABLE OR NOT Python3_EXECUTABLE)
# Prefer in-tree venv at <source>/.venv, then Homebrew, then system Python
set(VENV_PYTHON "${CMAKE_SOURCE_DIR}/.venv/bin/python")

if(EXISTS "${VENV_PYTHON}")
set(Python3_EXECUTABLE "${VENV_PYTHON}" CACHE FILEPATH "Python3 executable" FORCE)
message(STATUS "Auto-detected venv Python: ${Python3_EXECUTABLE}")
elseif(EXISTS "/opt/homebrew/bin/python")
set(Python3_EXECUTABLE "/opt/homebrew/bin/python" CACHE FILEPATH "Python3 executable" FORCE)
message(STATUS "Auto-detected Homebrew Python: ${Python3_EXECUTABLE}")
else()
# Fall back to system Python
find_program(Python3_EXECUTABLE python3 python)
if(Python3_EXECUTABLE)
message(STATUS "Auto-detected system Python: ${Python3_EXECUTABLE}")
endif()
endif()
endif()

# Create the Python module
pybind11_add_module(interpreter_module tools/bindings_python.cpp ${SOURCES})
# Set PYTHON_EXECUTABLE for backwards compatibility
set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}")
message(STATUS "Final Python for build: ${Python3_EXECUTABLE}")

# Find Python3 FIRST with our specified executable
# This locks in the Python version before pybind11 tries to find it
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
message(STATUS "Locked Python3: ${Python3_EXECUTABLE} (version ${Python3_VERSION})")

# Find pybind11 - it will now use the Python3 we just locked in
find_package(pybind11 REQUIRED)

# Create the Python module
pybind11_add_module(interpreter_module tools/bindings_python.cpp ${SOURCES})

install(
TARGETS interpreter_module
LIBRARY DESTINATION MARA/autumn_cpp
COMPONENT python_extension
)

install(
FILES python_pkg/__init__.py
DESTINATION MARA/autumn_cpp
COMPONENT python_extension
)

install(
FILES python_pkg/autumnstdlib/__init__.py autumnstdlib/stdlib.sexp
DESTINATION MARA/autumn_cpp/autumnstdlib
COMPONENT python_extension
)
endif()

# Try to find Julia
find_package(Julia QUIET)
Expand All @@ -97,7 +159,7 @@ if(NOT Julia_FOUND)
COMMAND ${Julia_EXECUTABLE} --startup-file=no -e "print(joinpath(Sys.BINDIR, \"../include/julia\"))"
OUTPUT_VARIABLE Julia_INCLUDE_DIRS
)

find_library(Julia_LIBRARY
NAMES julia libjulia
PATHS ${Julia_LIBRARY_DIR}
Expand All @@ -115,7 +177,7 @@ endif()
if(Julia_FOUND)
message(STATUS "Found Julia: Building Julia bindings")
find_package(JlCxx QUIET)

if(JlCxx_FOUND)
# Get Julia package directory
execute_process(
Expand Down Expand Up @@ -209,4 +271,4 @@ end
endif()
else()
message(STATUS "Julia not found: Skipping Julia bindings")
endif()
endif()
Loading
Loading