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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sh text eol=lf
230 changes: 195 additions & 35 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ project(PiCo VERSION 0.1.0 LANGUAGES C)

include(GNUInstallDirs)

set(CMAKE_C_COMPILER "gcc")

if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU")
message(FATAL_ERROR "FATAL: This project requires the GCC compiler due to specific GNU C extensions. "
"The attempt to set GCC as the compiler failed. Please ensure 'gcc' is installed and in your PATH.")
message(FATAL_ERROR
"PiCo requires GCC because it uses GNU C extensions. "
"Configure CMake with a GCC-based toolchain or preset."
)
endif()

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS ON)

file(GLOB_RECURSE CORE_SRC "core/*.c")
file(GLOB_RECURSE VM_SRC "vm/*.c")
file(GLOB_RECURSE COMPILER_SRC "compiler/*.c")
file(GLOB_RECURSE CORE_SRC CONFIGURE_DEPENDS "core/*.c")
file(GLOB_RECURSE VM_SRC CONFIGURE_DEPENDS "vm/*.c")
file(GLOB_RECURSE COMPILER_SRC CONFIGURE_DEPENDS "compiler/*.c")
file(GLOB_RECURSE STD_SRC CONFIGURE_DEPENDS "std/*.c")
file(GLOB_RECURSE CMD_SRC "cmd/*.c")
file(GLOB_RECURSE CMD_SRC CONFIGURE_DEPENDS "cmd/*.c")

include_directories(
set(PICO_INTERNAL_INCLUDE_DIRS
cmd
core
vm
Expand All @@ -30,57 +30,214 @@ include_directories(
vendor/xxhash
)

add_library(xxhash_lib STATIC vendor/xxhash/xxhash.c)
target_include_directories(xxhash_lib PUBLIC vendor/xxhash)
set(PICO_API_SRC
api/pico.c
)

if(NOT WIN32)
add_library(linenoise_lib STATIC vendor/linenoise/linenoise.c)
target_include_directories(linenoise_lib PUBLIC vendor/linenoise)
add_definitions(-DUSE_LINENOISE)
else()
endif()
set(PICO_VENDOR_SRC
vendor/xxhash/xxhash.c
)

set(PICO_RUNTIME_SRC
${PICO_API_SRC}
${PICO_VENDOR_SRC}
${CORE_SRC}
${VM_SRC}
${COMPILER_SRC}
${STD_SRC}
)

add_compile_options(
$<$<CONFIG:Debug>:-g>
$<$<CONFIG:Debug>:-O0>
$<$<CONFIG:Release>:-O3>
)

add_executable(pico
${CORE_SRC}
${VM_SRC}
${COMPILER_SRC}
${STD_SRC}
add_library(picort STATIC
${PICO_RUNTIME_SRC}
)

target_include_directories(picort
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE
${PICO_INTERNAL_INCLUDE_DIRS}
)

set_target_properties(picort PROPERTIES
OUTPUT_NAME pico
)

target_link_libraries(picort
PRIVATE
m
)

add_executable(pico
${CMD_SRC}
)

if(WIN32)
target_link_libraries(pico PRIVATE xxhash_lib m)
else()
target_link_libraries(pico PRIVATE xxhash_lib linenoise_lib m)
target_include_directories(pico
PRIVATE
${PICO_INTERNAL_INCLUDE_DIRS}
)

target_link_libraries(pico
PRIVATE
picort
)

if(NOT WIN32)
add_library(linenoise_lib STATIC
vendor/linenoise/linenoise.c
)

target_include_directories(linenoise_lib
PUBLIC
vendor/linenoise
)

target_compile_definitions(pico
PRIVATE
USE_LINENOISE
)

target_link_libraries(pico
PRIVATE
linenoise_lib
)
endif()

add_executable(pico_embed_basic
examples/embedding/basic.c
)

target_link_libraries(pico_embed_basic
PRIVATE
picort
)

add_executable(pico_embed_exit_policy
tests/embedding_exit_policy.c
)

target_link_libraries(pico_embed_exit_policy
PRIVATE
picort
)

add_executable(pico_embed_native_function
tests/embedding_native_function.c
)

target_link_libraries(pico_embed_native_function
PRIVATE
picort
)

add_executable(pico_embed_output_callback
tests/embedding_output_callback.c
)

target_link_libraries(pico_embed_output_callback
PRIVATE
picort
)

add_executable(pico_embed_call_script
tests/embedding_call_script.c
)

target_link_libraries(pico_embed_call_script
PRIVATE
picort
)

include(CTest)

if(BUILD_TESTING)
if(WIN32)
find_program(
PICO_BASH_EXECUTABLE
NAMES bash.exe
PATHS
"$ENV{ProgramFiles}/Git/bin"
"$ENV{ProgramFiles}/Git/usr/bin"
"C:/Program Files/Git/bin"
"C:/Program Files/Git/usr/bin"
"C:/msys64/usr/bin"
NO_DEFAULT_PATH
)
else()
find_program(
PICO_BASH_EXECUTABLE
NAMES bash
)
endif()

if(NOT PICO_BASH_EXECUTABLE)
message(FATAL_ERROR
"Bash was not found. On Windows, install Git for Windows or MSYS2."
)
endif()

add_test(
NAME pico_script_tests
COMMAND ${CMAKE_COMMAND} -E env
"PICO_EXEC=$<TARGET_FILE:pico>"
"TIMEOUT_SEC=60"
bash run_tests.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND
"${PICO_BASH_EXECUTABLE}"
"${CMAKE_SOURCE_DIR}/tests/run_tests.sh"
"$<TARGET_FILE:pico>"
"60"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/tests"
)

add_test(
NAME pico_embedding_basic
COMMAND $<TARGET_FILE:pico_embed_basic>
)

add_test(
NAME pico_embedding_exit_policy
COMMAND $<TARGET_FILE:pico_embed_exit_policy>
)

add_test(
NAME pico_embedding_native_function
COMMAND $<TARGET_FILE:pico_embed_native_function>
)

add_test(
NAME pico_embedding_call_script
COMMAND $<TARGET_FILE:pico_embed_call_script>
)

add_test(
NAME pico_embedding_output_callback
COMMAND $<TARGET_FILE:pico_embed_output_callback>
)
endif()

add_custom_target(check
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
DEPENDS pico
DEPENDS
pico
pico_embed_basic
pico_embed_exit_policy
pico_embed_native_function
pico_embed_call_script
pico_embed_output_callback
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

install(TARGETS pico
install(TARGETS pico picort
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(FILES include/pico.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

install(FILES README.md manual.md LICENSE gpl-3.0.txt
Expand All @@ -89,7 +246,10 @@ install(FILES README.md manual.md LICENSE gpl-3.0.txt

install(DIRECTORY examples/
DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples
FILES_MATCHING PATTERN "*.pcs"
FILES_MATCHING
PATTERN "*.pcs"
PATTERN "*.c"
PATTERN "README.md"
)

message(STATUS "Project 'PiCo' configured to use C Compiler: ${CMAKE_C_COMPILER}")
message(STATUS "Project 'PiCo' configured to use C Compiler: ${CMAKE_C_COMPILER}")
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,83 @@ To install system-wide:
sudo cmake --install build/release
```

## Embedding PiCo in C

PiCo can also be used as an embedded scripting VM inside a C program. The host program owns a `PicoVM`, loads PiCo source code, registers native C functions, calls PiCo functions, and can capture script output and runtime errors.

A minimal embedded program looks like this:

```c
#include <stdio.h>
#include <pico.h>

int main(void){
PicoVM* vm = pico_vm_create();

if(vm == NULL){
return 1;
}

PicoStatus status = pico_vm_eval(vm,
"func add(a, b){ return a + b; }\n",
"<embedded>"
);

if(status != PICO_STATUS_OK){
fprintf(stderr, "%s\n", pico_vm_last_error(vm));
pico_vm_destroy(vm);
return 1;
}

PicoValue args[] = {
pico_value_number(20),
pico_value_number(22)
};

PicoValue result;
status = pico_vm_call(vm, "add", 2, args, &result);

if(status == PICO_STATUS_OK && result.type == PICO_VALUE_NUMBER){
printf("%.14g\n", result.as.number);
}

pico_vm_destroy(vm);
return status == PICO_STATUS_OK ? 0 : 1;
}
```

The third argument of `pico_vm_eval()` is a diagnostic source name. It does not need to be a real file path; names like `"<embedded>"` or `"<plugin>"` are useful for error messages.

The embedding API currently supports these core operations:

- `pico_vm_create()` / `pico_vm_destroy()` for VM lifetime management
- `pico_vm_eval()` for loading PiCo source code
- `pico_vm_register_native()` for exposing C functions to PiCo
- `pico_vm_call()` for calling global PiCo functions from C
- `pico_vm_set_output()` and `pico_vm_set_error_output()` for capturing `print` output and runtime error output
- `pico_vm_last_error()` for reading the latest compile or runtime error

More complete examples are in `examples/embedding/`.

### Linking against an installed libpico

After installation, an external C program can include `pico.h` and link against `libpico.a`:

```sh
gcc main.c -I /path/to/pico/include -L /path/to/pico/lib -lpico -lm -o embed_app
```

On Windows with MinGW, the command is the same except paths usually point to the local install prefix, for example:

```powershell
gcc .\main.c `
-I .\install-debug\include `
-L .\install-debug\lib `
-lpico `
-lm `
-o .\embed_app.exe
```

## Testing

Run the test suite with *CTest*:
Expand Down
Loading
Loading