diff --git a/Makefile b/Makefile index 95539a4..6dcb5c2 100644 --- a/Makefile +++ b/Makefile @@ -34,14 +34,11 @@ PTLUA_LDLIBS = -llua -lm # Compilation targets # =================== -.PHONY: library examples tests all install uninstall clean +.PHONY: library tests all install uninstall clean library: \ pt-lua -examples: library \ - examples/fibonacci/fibonacci.so - tests: library \ spec/tracebacks/anon_lua/module.so \ spec/tracebacks/depth_recursion/module.so \ @@ -51,7 +48,7 @@ tests: library \ spec/tracebacks/multimod/module_b.so \ spec/tracebacks/singular/module.so -all: library examples tests +all: library tests install: library $(INSTALL_EXEC) pt-lua $(BINDIR) @@ -62,7 +59,7 @@ uninstall: rm -rf $(BINDIR)/pt-run clean: - rm -rf pt-lua examples/*/*.so spec/tracebacks/*/*.so + rm -rf pt-run spec/tracebacks/*/*.so %.so: %.c $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LIBFLAG) $< -o $@ @@ -70,7 +67,6 @@ clean: pt-lua: pt-lua.c ptracer.h $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(PTLUA_LDFLAGS) $< -o $@ $(PTLUA_LDLIBS) -examples/fibonacci/fibonacci.so: examples/fibonacci/fibonacci.c ptracer.h spec/tracebacks/anon_lua/module.so: spec/tracebacks/anon_lua/module.c ptracer.h spec/tracebacks/depth_recursion/module.so: spec/tracebacks/depth_recursion/module.c ptracer.h spec/tracebacks/dispatch/module.so: spec/tracebacks/dispatch/module.c ptracer.h diff --git a/examples/connected-components/Makefile b/examples/connected-components/Makefile new file mode 100644 index 0000000..49a7c29 --- /dev/null +++ b/examples/connected-components/Makefile @@ -0,0 +1,27 @@ +# Copyright (c) 2024, The Pallene Developers +# Pallene Tracer is licensed under the MIT license. +# Please refer to the LICENSE and AUTHORS files for details +# SPDX-License-Identifier: MIT + +OUTPUT_FILE = component.so + +CC = cc +CFLAGS = -O2 -std=c99 -pedantic -Wall -Wextra -Wformat-security +LIBFLAGS = -fPIC -shared + +ifneq ($(findstring debug, $(MAKECMDGOALS)), ) + CFLAGS += -DPT_DEBUG +endif + +.PHONY: all debug clean + +all: $(OUTPUT_FILE) +debug: $(OUTPUT_FILE) + +clean: + rm -rf $(OUTPUT_FILE) + +%.so: %.c + $(CC) $(CFLAGS) $(LDFLAGS) $(LIBFLAGS) $< -o $@ + +%.c: ptracer.h diff --git a/examples/connected-components/component.c b/examples/connected-components/component.c new file mode 100644 index 0000000..ca0e25f --- /dev/null +++ b/examples/connected-components/component.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024, The Pallene Developers + * Pallene Tracer is licensed under the MIT license. + * Please refer to the LICENSE and AUTHORS files for details + * SPDX-License-Identifier: MIT + */ + +#include + +#define PT_IMPLEMENTATION +#include "ptracer.h" + +/* ---------------- PALLENE TRACER LUA INERFACE ---------------- */ +#define CON_LUA_FRAMEENTER(fnptr) \ + PALLENE_TRACER_LUA_FRAMEENTER(L, fnstack, fnptr, \ + lua_upvalueindex(1), _frame) +/* ---------------- PALLENE TRACER LUA INERFACE END ---------------- */ + +/* ---------------- PALLENE TRACER C INTERFACE ---------------- */ + +#define CON_C_FRAMEENTER() \ + PALLENE_TRACER_GENERIC_C_FRAMEENTER(fnstack, _frame) + +#define CON_C_SETLINE() \ + PALLENE_TRACER_GENERIC_C_SETLINE(fnstack) + +#define CON_C_FRAMEEXIT() \ + PALLENE_TRACER_FRAMEEXIT(fnstack) + +/* ---------------- PALLENE TRACER C INTERFACE END ---------------- */ + +/* This is one way to work with Pallene Tracer call-stack, but certainly not recommended because + this way you loose the flexibility of using multiple Lua states. */ +pt_fnstack_t *fnstack; + +static void internal_dfs(lua_State *L, int *visited, int node) { + CON_C_FRAMEENTER(); + + if(visited[node]) { + CON_C_FRAMEEXIT(); + return; + } + + visited[node] = 1; + + lua_rawgeti(L, 1, node); /* get the connected adjacent nodes of current node */ + CON_C_SETLINE(); + int n = luaL_len(L, -1); /* this line may trigger error */ + lua_pop(L, 1); + + /* for all the adjacent nodes */ + for(int i = 1; i <= n; i++) { + lua_rawgeti(L, 1, node); + lua_rawgeti(L, -1, i); + int adjacent_node = lua_tointeger(L, -1); + lua_pop(L, 2); /* avoid Lua value-stack overflow by not keeping values on the stack. */ + + CON_C_SETLINE(); + internal_dfs(L, visited, adjacent_node); + } + + CON_C_FRAMEEXIT(); +} + +static int find_connected_components(lua_State *L, int *visited, int n) { + CON_C_FRAMEENTER(); + + int result = 0; + + for(int i = 1; i <= n; i++) { + if(!visited[i]) { + result++; + CON_C_SETLINE(); + internal_dfs(L, visited, i); + } + } + + CON_C_FRAMEEXIT(); + return result; +} + +static int find_connected_components_lua(lua_State *L) { + CON_LUA_FRAMEENTER(find_connected_components_lua); + + if(!lua_istable(L, 1)) + luaL_error(L, "expected the first argument to be table"); + if(!lua_isinteger(L, 2)) + luaL_error(L, "expected the second argument to be integer"); + + int nodes = lua_tointeger(L, 2); /* get total number of nodes */ + int *visited = calloc(nodes + 1, sizeof(int)); /* Lua prefers 1 based indexing */ + + /* Dispatch and push the result. */ + lua_pushinteger(L, find_connected_components(L, visited, nodes)); + + free(visited); + return 1; +} + +int luaopen_component(lua_State *L) { + fnstack = pallene_tracer_init(L); + + lua_newtable(L); + int table = lua_gettop(L); + + /* ---- find_connected_components ---- */ + /* `pallene_tracer_init` function pushes the frameexit finalizer to the stack. */ + lua_pushvalue(L, -2); /* passing the finalizer object as upvalue is generally the way to go. */ + lua_pushcclosure(L, find_connected_components_lua, 1); + lua_setfield(L, table, "find_connected_components"); + + return 1; +} diff --git a/examples/connected-components/main.lua b/examples/connected-components/main.lua new file mode 100644 index 0000000..c00fc52 --- /dev/null +++ b/examples/connected-components/main.lua @@ -0,0 +1,20 @@ +local component = require "component" + +local nodes = 12 +local graph = { + { 2 }, -- 1st node + { 1, 5 }, -- 2nd node + { 5 }, -- 3rd node + { 5 }, + { 2, 3, 4 }, + { 7, 8 }, + { 6 }, + { 6 }, + { 10, 12 }, + { 9, 11, 12 }, + { 10, 12 }, + { 9, 10, 11 } -- 12th node +}; + +local total_connected_components = component.find_connected_components(graph, nodes); +print(total_connected_components) -- expect: 3 diff --git a/examples/fibonacci/Makefile b/examples/fibonacci/Makefile new file mode 100644 index 0000000..bca8087 --- /dev/null +++ b/examples/fibonacci/Makefile @@ -0,0 +1,27 @@ +# Copyright (c) 2024, The Pallene Developers +# Pallene Tracer is licensed under the MIT license. +# Please refer to the LICENSE and AUTHORS files for details +# SPDX-License-Identifier: MIT + +OUTPUT_FILE = fibonacci.so + +CC = cc +CFLAGS = -O2 -std=c99 -pedantic -Wall -Wextra -Wformat-security +LIBFLAGS = -fPIC -shared + +ifneq ($(findstring debug, $(MAKECMDGOALS)), ) + CFLAGS += -DPT_DEBUG +endif + +.PHONY: all debug clean + +all: $(OUTPUT_FILE) +debug: $(OUTPUT_FILE) + +clean: + rm -rf $(OUTPUT_FILE) + +%.so: %.c + $(CC) $(CFLAGS) $(LDFLAGS) $(LIBFLAGS) $< -o $@ + +%.c: ptracer.h diff --git a/examples/fibonacci/main.lua b/examples/fibonacci/main.lua index 95dca0d..f9a02bc 100644 --- a/examples/fibonacci/main.lua +++ b/examples/fibonacci/main.lua @@ -3,7 +3,7 @@ -- Please refer to the LICENSE and AUTHORS files for details -- SPDX-License-Identifier: MIT +local N = tonumber(arg[1]) or 40 + local fibonacci = require "fibonacci" -print(fibonacci.fib(40)) --- Uncomment this and trigger an error. You can debug using the 'pallene-debug' script. --- print(fibonacci.fib(40.0)) +print(fibonacci.fib(N)) diff --git a/spec/examples_spec.lua b/spec/examples_spec.lua new file mode 100644 index 0000000..13efc8f --- /dev/null +++ b/spec/examples_spec.lua @@ -0,0 +1,32 @@ +-- Copyright (c) 2024, The Pallene Developers +-- Pallene Tracer is licensed under the MIT license. +-- Please refer to the LICENSE and AUTHORS files for details +-- SPDX-License-Identifier: MIT + +local util = require "spec.util" + +local function assert_example(example, expected_content) + local cdir = util.shell_quote("examples/"..example) + local ok, err, output_content, _ = + util.outputs_of_execute(string.format("cd %s && make --quiet && ../../pt-lua main.lua", cdir)) + assert(ok, err) + assert.are.same(expected_content, output_content) + + -- With Pallene Tracer tracebacks enabled + local ok, err, output_content, _ = + util.outputs_of_execute(string.format([[ + cd %s + make clean --quiet + make debug --quiet + ../../pt-lua main.lua ]], cdir)) + assert(ok, err) + assert.are.same(expected_content, output_content) +end + +it("Fibonacci", function() + assert_example("fibonacci", "102334155\n") +end) + +it("Find Connected Components", function() + assert_example("connected-components", "3\n") +end) diff --git a/spec/tracebacks_spec.lua b/spec/tracebacks_spec.lua index e381c8a..a988b9e 100644 --- a/spec/tracebacks_spec.lua +++ b/spec/tracebacks_spec.lua @@ -5,10 +5,8 @@ local util = require "spec.util" -local function assert_test(example, expected_content) - assert(util.execute("make --quiet tests")) - - local dir = util.shell_quote("spec/tracebacks/"..example) +local function assert_test(test, expected_content) + local dir = util.shell_quote("spec/tracebacks/"..test) local ok, _, output_content, err_content = util.outputs_of_execute("./pt-lua "..dir.."/main.lua") assert(not ok, output_content)