From d4acbe8a70285c43c1cfcfdfa2d85b4cd2418393 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 7 Aug 2015 08:10:55 -0700 Subject: [PATCH] A vertical slice of real libuv integration. This adds a "timer" module to the CLI that provides a Timer class with a static sleep() method. Not the most exciting functionality in the world, but it requires the full hunk of libuv integration: - The CLI sets up libuv and runs the event loop. - Added a new directory src/module for CLI modules. - Updated all the make scripts to handle it. - Reorganized some other CLI code. --- Makefile | 8 +- project/xcode/wren.xcodeproj/project.pbxproj | 169 +++++++++++++++++++ script/generate_builtins.py | 14 +- script/wren.mk | 40 +++-- src/README.md | 19 +++ src/cli/io.c | 4 + src/cli/main.c | 35 +--- src/cli/vm.c | 109 +++++++++++- src/cli/vm.h | 18 +- src/module/timer.c | 81 +++++++++ src/module/timer.h | 14 ++ src/module/timer.wren | 13 ++ test/api/main.c | 2 +- 13 files changed, 456 insertions(+), 70 deletions(-) create mode 100644 src/README.md create mode 100644 src/module/timer.c create mode 100644 src/module/timer.h create mode 100644 src/module/timer.wren diff --git a/Makefile b/Makefile index 5fbe208b..09de5a84 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,15 @@ # Executables are built to bin/. Libraries are built to lib/. -LIB_UV := build/libuv/build/Release/libuv.a +LIBUV := build/libuv/build/Release/libuv.a # A normal, optimized release build for the current CPU architecture. -release: $(LIB_UV) +release: $(LIBUV) @ $(MAKE) -f script/wren.mk @ cp bin/wren wren # For convenience, copy the interpreter to the top level. # A debug build for the current architecture. -debug: $(LIB_UV) +debug: $(LIBUV) @ $(MAKE) -f script/wren.mk MODE=debug # A release build of just the VM. Does not require libuv. @@ -33,7 +33,7 @@ all: debug release @ $(MAKE) -f script/wren.mk MODE=debug LANG=cpp ARCH=64 # Download and build libuv to a static library. -$(LIB_UV): script/libuv.py +$(LIBUV): script/libuv.py @ ./script/libuv.py # Remove all build outputs and intermediate files. diff --git a/project/xcode/wren.xcodeproj/project.pbxproj b/project/xcode/wren.xcodeproj/project.pbxproj index 621f42e9..09515484 100644 --- a/project/xcode/wren.xcodeproj/project.pbxproj +++ b/project/xcode/wren.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 2901D7641B74F4050083A2C8 /* timer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2901D7621B74F4050083A2C8 /* timer.c */; }; + 2901D7651B74F4050083A2C8 /* timer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2901D7621B74F4050083A2C8 /* timer.c */; }; 29205C8F1AB4E5C90073018D /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C8E1AB4E5C90073018D /* main.c */; }; 29205C991AB4E6430073018D /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C921AB4E6430073018D /* wren_compiler.c */; }; 29205C9A1AB4E6430073018D /* wren_core.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C931AB4E6430073018D /* wren_core.c */; }; @@ -19,6 +21,22 @@ 29C0888F1B6E7F0100B00CFD /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29C0888E1B6E7F0100B00CFD /* libuv.a */; }; 29C8A92F1AB71C1C00DEC81D /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A92E1AB71C1C00DEC81D /* io.c */; }; 29C8A9331AB71FFF00DEC81D /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A9311AB71FFF00DEC81D /* vm.c */; }; + 29CE20181B7257720057FBA3 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE20171B7257720057FBA3 /* main.c */; }; + 29CE201B1B72577C0057FBA3 /* return_bool.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE20191B72577C0057FBA3 /* return_bool.c */; }; + 29CE201E1B7257840057FBA3 /* return_double.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE201C1B7257840057FBA3 /* return_double.c */; }; + 29CE20211B72578C0057FBA3 /* return_null.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE201F1B72578C0057FBA3 /* return_null.c */; }; + 29CE20221B72586B0057FBA3 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A92E1AB71C1C00DEC81D /* io.c */; }; + 29CE20231B72586B0057FBA3 /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A9311AB71FFF00DEC81D /* vm.c */; }; + 29CE20241B72586B0057FBA3 /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C921AB4E6430073018D /* wren_compiler.c */; }; + 29CE20251B72586B0057FBA3 /* wren_core.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C931AB4E6430073018D /* wren_core.c */; }; + 29CE20261B72586B0057FBA3 /* wren_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C941AB4E6430073018D /* wren_debug.c */; }; + 29CE20271B72586B0057FBA3 /* wren_io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C951AB4E6430073018D /* wren_io.c */; }; + 29CE20281B72586B0057FBA3 /* wren_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29DE39511AC3A50A00987D41 /* wren_meta.c */; }; + 29CE20291B72586B0057FBA3 /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; }; + 29CE202A1B72586B0057FBA3 /* wren_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C961AB4E6430073018D /* wren_utils.c */; }; + 29CE202B1B72586B0057FBA3 /* wren_value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C971AB4E6430073018D /* wren_value.c */; }; + 29CE202C1B72586B0057FBA3 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; }; + 29CE202D1B7258E20057FBA3 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29C0888E1B6E7F0100B00CFD /* libuv.a */; }; 29DE39531AC3A50A00987D41 /* wren_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29DE39511AC3A50A00987D41 /* wren_meta.c */; }; /* End PBXBuildFile section */ @@ -32,9 +50,20 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + 29CE20081B7256660057FBA3 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2901D7621B74F4050083A2C8 /* timer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = timer.c; path = ../../src/module/timer.c; sourceTree = ""; }; + 2901D7631B74F4050083A2C8 /* timer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = timer.h; path = ../../src/module/timer.h; sourceTree = ""; }; 29205C8E1AB4E5C90073018D /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../src/cli/main.c; sourceTree = ""; }; 29205C901AB4E62B0073018D /* wren.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren.h; path = ../../src/include/wren.h; sourceTree = ""; }; 29205C921AB4E6430073018D /* wren_compiler.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_compiler.c; path = ../../src/vm/wren_compiler.c; sourceTree = ""; }; @@ -61,6 +90,14 @@ 29C8A9301AB71C3300DEC81D /* io.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = io.h; path = ../../src/cli/io.h; sourceTree = ""; }; 29C8A9311AB71FFF00DEC81D /* vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vm.c; path = ../../src/cli/vm.c; sourceTree = ""; }; 29C8A9321AB71FFF00DEC81D /* vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vm.h; path = ../../src/cli/vm.h; sourceTree = ""; }; + 29CE200A1B7256660057FBA3 /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; }; + 29CE20171B7257720057FBA3 /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/api/main.c; sourceTree = ""; }; + 29CE20191B72577C0057FBA3 /* return_bool.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = return_bool.c; path = ../../test/api/return_bool/return_bool.c; sourceTree = ""; }; + 29CE201A1B72577C0057FBA3 /* return_bool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = return_bool.h; path = ../../test/api/return_bool/return_bool.h; sourceTree = ""; }; + 29CE201C1B7257840057FBA3 /* return_double.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = return_double.c; path = ../../test/api/return_double/return_double.c; sourceTree = ""; }; + 29CE201D1B7257840057FBA3 /* return_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = return_double.h; path = ../../test/api/return_double/return_double.h; sourceTree = ""; }; + 29CE201F1B72578C0057FBA3 /* return_null.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = return_null.c; path = ../../test/api/return_null/return_null.c; sourceTree = ""; }; + 29CE20201B72578C0057FBA3 /* return_null.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = return_null.h; path = ../../test/api/return_null/return_null.h; sourceTree = ""; }; 29DE39511AC3A50A00987D41 /* wren_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_meta.c; path = ../../src/vm/wren_meta.c; sourceTree = ""; }; 29DE39521AC3A50A00987D41 /* wren_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_meta.h; path = ../../src/vm/wren_meta.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -74,9 +111,26 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 29CE20071B7256660057FBA3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 29CE202D1B7258E20057FBA3 /* libuv.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2901D7611B74F3E20083A2C8 /* module */ = { + isa = PBXGroup; + children = ( + 2901D7631B74F4050083A2C8 /* timer.h */, + 2901D7621B74F4050083A2C8 /* timer.c */, + ); + name = module; + sourceTree = ""; + }; 29205CA01AB4E6470073018D /* vm */ = { isa = PBXGroup; children = ( @@ -130,7 +184,9 @@ 29C0888E1B6E7F0100B00CFD /* libuv.a */, 29205CA91AB4E67B0073018D /* cli */, 29205CAA1AB4E6840073018D /* include */, + 2901D7611B74F3E20083A2C8 /* module */, 29205CA01AB4E6470073018D /* vm */, + 29CE20161B7257680057FBA3 /* api */, 29AB1F071816E3AD004B501E /* Products */, ); sourceTree = ""; @@ -139,10 +195,25 @@ isa = PBXGroup; children = ( 29AB1F061816E3AD004B501E /* wren */, + 29CE200A1B7256660057FBA3 /* api_test */, ); name = Products; sourceTree = ""; }; + 29CE20161B7257680057FBA3 /* api */ = { + isa = PBXGroup; + children = ( + 29CE20171B7257720057FBA3 /* main.c */, + 29CE20191B72577C0057FBA3 /* return_bool.c */, + 29CE201A1B72577C0057FBA3 /* return_bool.h */, + 29CE201C1B7257840057FBA3 /* return_double.c */, + 29CE201D1B7257840057FBA3 /* return_double.h */, + 29CE201F1B72578C0057FBA3 /* return_null.c */, + 29CE20201B72578C0057FBA3 /* return_null.h */, + ); + name = api; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -163,6 +234,23 @@ productReference = 29AB1F061816E3AD004B501E /* wren */; productType = "com.apple.product-type.tool"; }; + 29CE20091B7256660057FBA3 /* api_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 29CE20101B7256660057FBA3 /* Build configuration list for PBXNativeTarget "api_test" */; + buildPhases = ( + 29CE20061B7256660057FBA3 /* Sources */, + 29CE20071B7256660057FBA3 /* Frameworks */, + 29CE20081B7256660057FBA3 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = api_test; + productName = api_test; + productReference = 29CE200A1B7256660057FBA3 /* api_test */; + productType = "com.apple.product-type.tool"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -171,6 +259,11 @@ attributes = { LastUpgradeCheck = 0610; ORGANIZATIONNAME = "Bob Nystrom"; + TargetAttributes = { + 29CE20091B7256660057FBA3 = { + CreatedOnToolsVersion = 6.3.2; + }; + }; }; buildConfigurationList = 29AB1F011816E3AD004B501E /* Build configuration list for PBXProject "wren" */; compatibilityVersion = "Xcode 3.2"; @@ -185,6 +278,7 @@ projectRoot = ""; targets = ( 29AB1F051816E3AD004B501E /* wren */, + 29CE20091B7256660057FBA3 /* api_test */, ); }; /* End PBXProject section */ @@ -197,6 +291,7 @@ 29205C991AB4E6430073018D /* wren_compiler.c in Sources */, 2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */, 29205C9A1AB4E6430073018D /* wren_core.c in Sources */, + 2901D7641B74F4050083A2C8 /* timer.c in Sources */, 29C8A9331AB71FFF00DEC81D /* vm.c in Sources */, 29205C9B1AB4E6430073018D /* wren_debug.c in Sources */, 29205C9C1AB4E6430073018D /* wren_io.c in Sources */, @@ -209,6 +304,29 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 29CE20061B7256660057FBA3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29CE20221B72586B0057FBA3 /* io.c in Sources */, + 29CE20231B72586B0057FBA3 /* vm.c in Sources */, + 29CE20241B72586B0057FBA3 /* wren_compiler.c in Sources */, + 29CE20251B72586B0057FBA3 /* wren_core.c in Sources */, + 29CE20261B72586B0057FBA3 /* wren_debug.c in Sources */, + 29CE20271B72586B0057FBA3 /* wren_io.c in Sources */, + 29CE20281B72586B0057FBA3 /* wren_meta.c in Sources */, + 29CE20291B72586B0057FBA3 /* wren_primitive.c in Sources */, + 29CE202A1B72586B0057FBA3 /* wren_utils.c in Sources */, + 2901D7651B74F4050083A2C8 /* timer.c in Sources */, + 29CE202B1B72586B0057FBA3 /* wren_value.c in Sources */, + 29CE202C1B72586B0057FBA3 /* wren_vm.c in Sources */, + 29CE20181B7257720057FBA3 /* main.c in Sources */, + 29CE201B1B72577C0057FBA3 /* return_bool.c in Sources */, + 29CE201E1B7257840057FBA3 /* return_double.c in Sources */, + 29CE20211B72578C0057FBA3 /* return_null.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ @@ -310,6 +428,48 @@ }; name = Release; }; + 29CE200E1B7256660057FBA3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_PEDANTIC = NO; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LIBRARY_SEARCH_PATHS = ../../build/libuv/build/Release; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = ../../build/libuv/include; + }; + name = Debug; + }; + 29CE200F1B7256660057FBA3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_PEDANTIC = NO; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LIBRARY_SEARCH_PATHS = ../../build/libuv/build/Release; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = ../../build/libuv/include; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -331,6 +491,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 29CE20101B7256660057FBA3 /* Build configuration list for PBXNativeTarget "api_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29CE200E1B7256660057FBA3 /* Debug */, + 29CE200F1B7256660057FBA3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 29AB1EFE1816E3AD004B501E /* Project object */; diff --git a/script/generate_builtins.py b/script/generate_builtins.py index 99c8bfe1..0edea337 100755 --- a/script/generate_builtins.py +++ b/script/generate_builtins.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding: utf-8 import glob import os.path @@ -6,7 +7,7 @@ import re PATTERN = re.compile(r'LibSource =\n("(.|[\n])*?);') -def copy_builtin(filename): +def copy_builtin(filename, out_prefix): name = os.path.basename(filename) name = name.split('.')[0] @@ -26,16 +27,19 @@ def copy_builtin(filename): constant = "LibSource =\n" + wren_source + ";" - with open("src/vm/wren_" + name + ".c", "r") as f: + with open(out_prefix + name + ".c", "r") as f: c_source = f.read() c_source = PATTERN.sub(constant, c_source) - with open("src/vm/wren_" + name + ".c", "w") as f: + with open(out_prefix + name + ".c", "w") as f: f.write(c_source) - print(name) + print(filename.ljust(25) + " → " + out_prefix + name + ".c") for f in glob.iglob("builtin/*.wren"): - copy_builtin(f) + copy_builtin(f, "src/vm/wren_") + +for f in glob.iglob("src/module/*.wren"): + copy_builtin(f, "src/module/") diff --git a/script/wren.mk b/script/wren.mk index 04849dd5..c77e1808 100644 --- a/script/wren.mk +++ b/script/wren.mk @@ -20,9 +20,14 @@ # Files. CLI_HEADERS := $(wildcard src/cli/*.h) -VM_HEADERS := $(wildcard src/vm/*.h) CLI_SOURCES := $(wildcard src/cli/*.c) + +MODULE_HEADERS := $(wildcard src/module/*.h) +MODULE_SOURCES := $(wildcard src/module/*.c) + +VM_HEADERS := $(wildcard src/vm/*.h) VM_SOURCES := $(wildcard src/vm/*.c) + TEST_SOURCES := $(shell find test/api -name '*.c') BUILD_DIR := build @@ -94,11 +99,13 @@ endif CFLAGS := $(C_OPTIONS) $(C_WARNINGS) -CLI_OBJECTS := $(addprefix $(BUILD_DIR)/cli/, $(notdir $(CLI_SOURCES:.c=.o))) -VM_OBJECTS := $(addprefix $(BUILD_DIR)/vm/, $(notdir $(VM_SOURCES:.c=.o))) -TEST_OBJECTS := $(patsubst test/api/%.c, $(BUILD_DIR)/test/%.o, $(TEST_SOURCES)) +CLI_OBJECTS := $(addprefix $(BUILD_DIR)/cli/, $(notdir $(CLI_SOURCES:.c=.o))) +MODULE_OBJECTS := $(addprefix $(BUILD_DIR)/module/, $(notdir $(MODULE_SOURCES:.c=.o))) +VM_OBJECTS := $(addprefix $(BUILD_DIR)/vm/, $(notdir $(VM_SOURCES:.c=.o))) +TEST_OBJECTS := $(patsubst test/api/%.c, $(BUILD_DIR)/test/%.o, $(TEST_SOURCES)) -LIB_UV_DIR := build/libuv +LIBUV_DIR := build/libuv +LIBUV := $(LIBUV_DIR)/build/Release/libuv.a # Targets --------------------------------------------------------------------- @@ -115,10 +122,10 @@ cli: bin/$(WREN) test: $(BUILD_DIR)/test/$(WREN) # Command-line interpreter. -bin/$(WREN): $(CLI_OBJECTS) $(VM_OBJECTS) $(LIB_UV_DIR)/build/Release/libuv.a +bin/$(WREN): $(CLI_OBJECTS) $(MODULE_OBJECTS) $(VM_OBJECTS) $(LIBUV) @printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS)" @mkdir -p bin - @$(CC) $(CFLAGS) -L$(LIB_UV_DIR)/build/Release -l uv -o $@ $^ -lm + @$(CC) $(CFLAGS) -L$(LIBUV_DIR)/build/Release -l uv -o $@ $^ -lm # Static library. lib/lib$(WREN).a: $(VM_OBJECTS) @@ -133,16 +140,23 @@ lib/lib$(WREN).$(SHARED_EXT): $(VM_OBJECTS) @$(CC) $(CFLAGS) -shared $(SHARED_LIB_FLAGS) -o $@ $^ # Test executable. -$(BUILD_DIR)/test/$(WREN): $(TEST_OBJECTS) $(VM_OBJECTS) $(BUILD_DIR)/cli/io.o $(BUILD_DIR)/cli/vm.o +$(BUILD_DIR)/test/$(WREN): $(TEST_OBJECTS) $(MODULE_OBJECTS) $(VM_OBJECTS) \ + $(BUILD_DIR)/cli/io.o $(BUILD_DIR)/cli/vm.o $(LIBUV) @printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS)" @mkdir -p $(BUILD_DIR)/test - @$(CC) $(CFLAGS) -o $@ $^ -lm + @$(CC) $(CFLAGS) -L$(LIBUV_DIR)/build/Release -l uv -o $@ $^ -lm # CLI object files. -$(BUILD_DIR)/cli/%.o: src/cli/%.c $(CLI_HEADERS) $(VM_HEADERS) +$(BUILD_DIR)/cli/%.o: src/cli/%.c $(CLI_HEADERS) $(MODULE_HEADERS) $(VM_HEADERS) @printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)" @mkdir -p $(BUILD_DIR)/cli - @$(CC) -c $(CFLAGS) -Isrc/include -I$(LIB_UV_DIR)/include -o $@ $(FILE_FLAG) $< + @$(CC) -c $(CFLAGS) -Isrc/include -Isrc/module -I$(LIBUV_DIR)/include -o $@ $(FILE_FLAG) $< + +# Module object files. +$(BUILD_DIR)/module/%.o: src/module/%.c $(CLI_HEADERS) $(MODULE_HEADERS) $(VM_HEADERS) + @printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)" + @mkdir -p $(BUILD_DIR)/module + @$(CC) -c $(CFLAGS) -Isrc/include -Isrc/cli -I$(LIBUV_DIR)/include -o $@ $(FILE_FLAG) $< # VM object files. $(BUILD_DIR)/vm/%.o: src/vm/%.c $(VM_HEADERS) @@ -151,9 +165,9 @@ $(BUILD_DIR)/vm/%.o: src/vm/%.c $(VM_HEADERS) @$(CC) -c $(CFLAGS) -Isrc/include -o $@ $(FILE_FLAG) $< # Test object files. -$(BUILD_DIR)/test/%.o: test/api/%.c $(VM_HEADERS) +$(BUILD_DIR)/test/%.o: test/api/%.c $(MODULE_HEADERS) $(VM_HEADERS) @printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)" @mkdir -p $(dir $@) - @$(CC) -c $(CFLAGS) -Isrc/include -Isrc/cli -o $@ $(FILE_FLAG) $< + @$(CC) -c $(CFLAGS) -Isrc/include -I$(LIBUV_DIR)/include -Isrc/cli -o $@ $(FILE_FLAG) $< .PHONY: all cli test vm diff --git a/src/README.md b/src/README.md new file mode 100644 index 00000000..c5312ffd --- /dev/null +++ b/src/README.md @@ -0,0 +1,19 @@ +This contains the Wren source code. It is organized like so: + +* `cli`: the source code for the command line interface. This is a custom + exectuable that embeds the VM in itself. Code here handles reading + command-line, running the REPL, loading modules from disc, etc. + +* `include`: the public header directory for the VM. If you are embedding the + VM in your own application, you will add this to your include path. + +* `module`: the source code for the built-in modules that come with the CLI. + These modules are written in a mixture of C and Wren and generally use + [libuv][] to implement their underlying functionality. + +* `vm`: the source code for the Wren VM itself. Unlike code in `cli` and + `module`, this has no dependencies on libuv. If you are embedding the VM in + your own application from source, you will compile the files here into your + app. + +[libuv]: http://libuv.org/ diff --git a/src/cli/io.c b/src/cli/io.c index f31ddc01..18bb6d04 100644 --- a/src/cli/io.c +++ b/src/cli/io.c @@ -4,6 +4,8 @@ #include "io.h" +#include "timer.h" + char const* rootDirectory = NULL; // Reads the contents of the file at [path] and returns it as a heap allocated @@ -71,6 +73,8 @@ char* wrenFilePath(const char* name) char* readModule(WrenVM* vm, const char* module) { + if (strcmp(module, "timer") == 0) return timerGetSource(); + // First try to load the module with a ".wren" extension. char* modulePath = wrenFilePath(module); char* moduleContents = readFile(modulePath); diff --git a/src/cli/main.c b/src/cli/main.c index 2b8e5d61..e5fb9160 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -5,37 +5,6 @@ #include "vm.h" #include "wren.h" -#define MAX_LINE_LENGTH 1024 // TODO: Something less arbitrary. - -static int runRepl() -{ - WrenVM* vm = createVM(NULL); - - printf("\\\\/\"-\n"); - printf(" \\_/ wren v0.0.0\n"); - - char line[MAX_LINE_LENGTH]; - - for (;;) - { - printf("> "); - - if (!fgets(line, MAX_LINE_LENGTH, stdin)) - { - printf("\n"); - break; - } - - // TODO: Handle failure. - wrenInterpret(vm, "Prompt", line); - - // TODO: Automatically print the result of expressions. - } - - wrenFreeVM(vm); - return 0; -} - int main(int argc, const char* argv[]) { if (argc < 1 || argc > 2) @@ -43,14 +12,14 @@ int main(int argc, const char* argv[]) fprintf(stderr, "Usage: wren [file]\n"); return 64; // EX_USAGE. } - + if (argc == 1) { runRepl(); } else if (argc == 2) { - runFile(NULL, argv[1]); + runFile(argv[1], NULL); } return 0; diff --git a/src/cli/vm.c b/src/cli/vm.c index 5aedc3c8..400336bc 100644 --- a/src/cli/vm.c +++ b/src/cli/vm.c @@ -3,12 +3,41 @@ #include "io.h" #include "vm.h" +#include "timer.h" -WrenVM* createVM(WrenBindForeignMethodFn bindForeign) +#define MAX_LINE_LENGTH 1024 // TODO: Something less arbitrary. + +// The single VM instance that the CLI uses. +WrenVM* vm; + +WrenBindForeignMethodFn externalBindForeign; + +uv_loop_t* loop; + +static WrenForeignMethodFn bindForeignMethod(WrenVM* vm, + const char* module, + const char* className, + bool isStatic, + const char* signature) +{ + if (strcmp(module, "timer") == 0) + { + return timerBindForeign(vm, className, isStatic, signature); + } + + if (externalBindForeign != NULL) + { + return externalBindForeign(vm, module, className, isStatic, signature); + } + + return NULL; +} + +static void initVM() { WrenConfiguration config; - config.bindForeignMethodFn = bindForeign; + config.bindForeignMethodFn = bindForeignMethod; config.loadModuleFn = readModule; // Since we're running in a standalone process, be generous with memory. @@ -18,12 +47,28 @@ WrenVM* createVM(WrenBindForeignMethodFn bindForeign) config.reallocateFn = NULL; config.minHeapSize = 0; config.heapGrowthPercent = 0; - - return wrenNewVM(&config); + + vm = wrenNewVM(&config); + + // Initialize the event loop. + loop = (uv_loop_t*)malloc(sizeof(uv_loop_t)); + uv_loop_init(loop); } -void runFile(WrenBindForeignMethodFn bindForeign, const char* path) +static void freeVM() { + timerReleaseMethods(); + + uv_loop_close(loop); + free(loop); + + wrenFreeVM(vm); +} + +void runFile(const char* path, WrenBindForeignMethodFn bindForeign) +{ + externalBindForeign = bindForeign; + // Use the directory where the file is as the root to resolve imports // relative to. char* root = NULL; @@ -42,12 +87,18 @@ void runFile(WrenBindForeignMethodFn bindForeign, const char* path) fprintf(stderr, "Could not find file \"%s\".\n", path); exit(66); } - - WrenVM* vm = createVM(bindForeign); - + + initVM(); + WrenInterpretResult result = wrenInterpret(vm, path, source); + + if (result == WREN_RESULT_SUCCESS) + { + uv_run(loop, UV_RUN_DEFAULT); + } - wrenFreeVM(vm); + freeVM(); + free(source); free(root); @@ -55,3 +106,43 @@ void runFile(WrenBindForeignMethodFn bindForeign, const char* path) if (result == WREN_RESULT_COMPILE_ERROR) exit(65); // EX_DATAERR. if (result == WREN_RESULT_RUNTIME_ERROR) exit(70); // EX_SOFTWARE. } + +int runRepl() +{ + initVM(); + + printf("\\\\/\"-\n"); + printf(" \\_/ wren v0.0.0\n"); + + char line[MAX_LINE_LENGTH]; + + for (;;) + { + printf("> "); + + if (!fgets(line, MAX_LINE_LENGTH, stdin)) + { + printf("\n"); + break; + } + + // TODO: Handle failure. + wrenInterpret(vm, "Prompt", line); + + // TODO: Automatically print the result of expressions. + } + + freeVM(); + + return 0; +} + +WrenVM* getVM() +{ + return vm; +} + +uv_loop_t* getLoop() +{ + return loop; +} diff --git a/src/cli/vm.h b/src/cli/vm.h index 90fa491a..064d07d0 100644 --- a/src/cli/vm.h +++ b/src/cli/vm.h @@ -1,14 +1,22 @@ #ifndef vm_h #define vm_h +#include "uv.h" #include "wren.h" -// Creates a new Wren VM with the CLI's module loader and other configuration. -WrenVM* createVM(WrenBindForeignMethodFn bindForeign); - // Executes the Wren script at [path] in a new VM. // -// Exits if the script failed or could not be loaded. -void runFile(WrenBindForeignMethodFn bindForeign, const char* path); +// If [bindForeign] is not `NULL`, it will be called to register any foreign +// methods that the CLI itself doesn't handle. +void runFile(const char* path, WrenBindForeignMethodFn bindForeign); + +// Runs the Wren interactive REPL. +int runRepl(); + +// Gets the currently running VM. +WrenVM* getVM(); + +// Gets the event loop the VM is using. +uv_loop_t* getLoop(); #endif diff --git a/src/module/timer.c b/src/module/timer.c new file mode 100644 index 00000000..62c3eb48 --- /dev/null +++ b/src/module/timer.c @@ -0,0 +1,81 @@ +#include +#include + +#include "uv.h" + +#include "timer.h" +#include "wren.h" +#include "vm.h" + +// This is generated from builtin/module/timer.wren. Do not edit here. +static const char* timerLibSource = +"class Timer {\n" +" static sleep(milliseconds) {\n" +" startTimer_(milliseconds, Fiber.current)\n" +" Fiber.yield()\n" +" }\n" +"\n" +" foreign static startTimer_(milliseconds, fiber)\n" +"\n" +" // Called by native code.\n" +" static resumeTimer_(fiber) {\n" +" fiber.run()\n" +" }\n" +"}\n"; + +// The Wren method to call when a timer has completed. +static WrenMethod* resumeTimer; + +// Called by libuv when the timer has completed. +static void timerCallback(uv_timer_t* handle) +{ + WrenValue* fiber = (WrenValue*)handle->data; + free(handle); + + // Run the fiber that was sleeping. + wrenCall(getVM(), resumeTimer, "v", fiber); + wrenReleaseValue(getVM(), fiber); +} + +static void startTimer(WrenVM* vm) +{ + // If we haven't looked up the resume method yet, grab it now. + if (resumeTimer == NULL) + { + resumeTimer = wrenGetMethod(vm, "timer", "Timer", "resumeTimer_(_)"); + } + + int milliseconds = (int)wrenGetArgumentDouble(vm, 1); + WrenValue* fiber = wrenGetArgumentValue(vm, 2); + + // Store the fiber to resume when the timer completes. + uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t)); + handle->data = fiber; + + uv_timer_init(getLoop(), handle); + uv_timer_start(handle, timerCallback, milliseconds, 0); +} + +char* timerGetSource() +{ + size_t length = strlen(timerLibSource); + char* copy = malloc(length + 1); + strncpy(copy, timerLibSource, length); + + return copy; +} + +WrenForeignMethodFn timerBindForeign( + WrenVM* vm, const char* className, bool isStatic, const char* signature) +{ + if (strcmp(className, "Timer") != 0) return NULL; + + if (isStatic && strcmp(signature, "startTimer_(_,_)") == 0) return startTimer; + + return NULL; +} + +void timerReleaseMethods() +{ + if (resumeTimer != NULL) wrenReleaseMethod(getVM(), resumeTimer); +} diff --git a/src/module/timer.h b/src/module/timer.h new file mode 100644 index 00000000..0f8f311d --- /dev/null +++ b/src/module/timer.h @@ -0,0 +1,14 @@ +#ifndef timer_h +#define timer_h + +#include "wren.h" + +// TODO: Coherent naming scheme. +char* timerGetSource(); + +WrenForeignMethodFn timerBindForeign( + WrenVM* vm, const char* className, bool isStatic, const char* signature); + +void timerReleaseMethods(); + +#endif diff --git a/src/module/timer.wren b/src/module/timer.wren new file mode 100644 index 00000000..18f54eaf --- /dev/null +++ b/src/module/timer.wren @@ -0,0 +1,13 @@ +class Timer { + static sleep(milliseconds) { + startTimer_(milliseconds, Fiber.current) + Fiber.yield() + } + + foreign static startTimer_(milliseconds, fiber) + + // Called by native code. + static resumeTimer_(fiber) { + fiber.run() + } +} diff --git a/test/api/main.c b/test/api/main.c index 97eb121e..7c261987 100644 --- a/test/api/main.c +++ b/test/api/main.c @@ -60,6 +60,6 @@ int main(int argc, const char* argv[]) strcat(testPath, testName); strcat(testPath, ".wren"); - runFile(bindForeign, testPath); + runFile(testPath, bindForeign); return 0; }