diff --git a/AUTHORS.txt b/AUTHORS.txt index ef72b0bd4df..a9bb2820ee1 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -23,6 +23,7 @@ Benjamin Peterson Brendan Eich Brian Anderson Brian J. Burg +Brian Leibig Chris Double Chris Peterson Damian Gryski diff --git a/Makefile.in b/Makefile.in index 51f4d269768..d8db8b3fcb2 100644 --- a/Makefile.in +++ b/Makefile.in @@ -460,8 +460,9 @@ TSREQS := \ FUZZ := $(HBIN2_H_$(CFG_HOST_TRIPLE))/fuzzer$(X) CARGO := $(HBIN2_H_$(CFG_HOST_TRIPLE))/cargo$(X) RUSTDOC := $(HBIN2_H_$(CFG_HOST_TRIPLE))/rustdoc$(X) +RUSTI := $(HBIN2_H_$(CFG_HOST_TRIPLE))/rusti$(X) -all: rustc $(GENERATED) docs $(FUZZ) $(CARGO) $(RUSTDOC) +all: rustc $(GENERATED) docs $(FUZZ) $(CARGO) $(RUSTDOC) $(RUSTI) endif diff --git a/configure b/configure index a8c3ba59e61..c09f2c7a9e5 100755 --- a/configure +++ b/configure @@ -529,7 +529,7 @@ for t in $CFG_TARGET_TRIPLES do make_dir rt/$t for i in \ - isaac bigint sync test arch/i386 arch/x86_64 \ + isaac linenoise bigint sync test arch/i386 arch/x86_64 \ libuv libuv/src/ares libuv/src/eio libuv/src/ev do make_dir rt/$t/$i diff --git a/mk/dist.mk b/mk/dist.mk index dd6a7aba4d6..0bdf764c153 100644 --- a/mk/dist.mk +++ b/mk/dist.mk @@ -22,6 +22,7 @@ PKG_FILES := \ $(addprefix $(S)src/, \ README.txt \ cargo \ + rusti \ rustc \ compiletest \ etc \ diff --git a/mk/install.mk b/mk/install.mk index a26572f413a..1ba5fe63912 100644 --- a/mk/install.mk +++ b/mk/install.mk @@ -75,6 +75,7 @@ install-host: $(SREQ$(ISTAGE)_T_$(CFG_HOST_TRIPLE)_H_$(CFG_HOST_TRIPLE)) $(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_LIBFUZZER)) $(Q)$(call INSTALL,$(HB2),$(PHB),cargo$(X)) $(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_LIBCARGO)) + $(Q)$(call INSTALL,$(HB2),$(PHB),rusti$(X)) $(Q)$(call INSTALL,$(HB2),$(PHB),rustdoc$(X)) $(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_LIBRUSTDOC)) $(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_RUNTIME)) @@ -95,6 +96,7 @@ HOST_LIB_FROM_HL_GLOB = \ uninstall: $(Q)rm -f $(PHB)/rustc$(X) $(Q)rm -f $(PHB)/cargo$(X) + $(Q)rm -f $(PHB)/rusti$(X) $(Q)rm -f $(PHB)/rustdoc$(X) $(Q)rm -f $(PHL)/$(CFG_RUSTLLVM) $(Q)rm -f $(PHL)/$(CFG_FUZZER) diff --git a/mk/platform.mk b/mk/platform.mk index 67eaa1b878b..ba0d0765524 100644 --- a/mk/platform.mk +++ b/mk/platform.mk @@ -103,7 +103,7 @@ endif # Hack: not sure how to test if a file exists in make other than this OS_SUPP = $(patsubst %,--suppressions=%,\ - $(wildcard $(CFG_SRC_DIR)src/etc/$(CFG_OSTYPE).supp*)) + $(wildcard $(CFG_SRC_DIR)src/etc/$(CFG_OSTYPE).supp*)) ifneq ($(findstring mingw,$(CFG_OSTYPE)),) CFG_WINDOWSY := 1 @@ -212,24 +212,37 @@ ifeq ($(CFG_C_COMPILER),clang) ifeq ($(origin CPP),default) CPP=clang -E endif - CFG_GCCISH_CFLAGS += -Wall -Werror -fno-rtti -g + CFG_GCCISH_CFLAGS += -Wall -Werror -g + CFG_GCCISH_CXXFLAGS += -fno-rtti CFG_GCCISH_LINK_FLAGS += -g # These flags will cause the compiler to produce a .d file # next to the .o file that lists header deps. CFG_DEPEND_FLAGS = -MMD -MP -MT $(1) -MF $(1:%.o=%.d) define CFG_MAKE_CC - CFG_COMPILE_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ - $$(CFG_GCCISH_CFLAGS) $$(CFG_CLANG_CFLAGS) \ - $$(CFG_GCCISH_CFLAGS_$$(HOST_$(1))) \ - $$(CFG_CLANG_CFLAGS_$$(HOST_$(1))) \ + CFG_COMPILE_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CC) \ + $$(CFG_GCCISH_CFLAGS) $$(CFG_CLANG_CFLAGS) \ + $$(CFG_GCCISH_CFLAGS_$$(HOST_$(1))) \ + $$(CFG_CLANG_CFLAGS_$$(HOST_$(1))) \ $$(CFG_DEPEND_FLAGS) \ - -c -o $$(1) $$(2) - CFG_LINK_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ - $$(CFG_GCCISH_LINK_FLAGS) -o $$(1) \ - $$(CFG_GCCISH_LINK_FLAGS_$$(HOST_$(1))) \ - $$(CFG_GCCISH_DEF_FLAG)$$(3) $$(2) \ - $$(call CFG_INSTALL_NAME,$$(4)) + -c -o $$(1) $$(2) + CFG_LINK_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CC) \ + $$(CFG_GCCISH_LINK_FLAGS) -o $$(1) \ + $$(CFG_GCCISH_LINK_FLAGS_$$(HOST_$(1))) \ + $$(CFG_GCCISH_DEF_FLAG)$$(3) $$(2) \ + $$(call CFG_INSTALL_NAME,$$(4)) + CFG_COMPILE_CXX_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ + $$(CFG_GCCISH_CFLAGS) $$(CFG_CLANG_CFLAGS) \ + $$(CFG_GCCISH_CXXFLAGS) \ + $$(CFG_GCCISH_CFLAGS_$$(HOST_$(1))) \ + $$(CFG_CLANG_CFLAGS_$$(HOST_$(1))) \ + $$(CFG_DEPEND_FLAGS) \ + -c -o $$(1) $$(2) + CFG_LINK_CXX_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ + $$(CFG_GCCISH_LINK_FLAGS) -o $$(1) \ + $$(CFG_GCCISH_LINK_FLAGS_$$(HOST_$(1))) \ + $$(CFG_GCCISH_DEF_FLAG)$$(3) $$(2) \ + $$(call CFG_INSTALL_NAME,$$(4)) endef $(foreach target,$(CFG_TARGET_TRIPLES), \ @@ -245,24 +258,38 @@ ifeq ($(CFG_C_COMPILER),gcc) ifeq ($(origin CPP),default) CPP=gcc -E endif - CFG_GCCISH_CFLAGS += -Wall -Werror -fno-rtti -g + CFG_GCCISH_CFLAGS += -Wall -Werror -g + CFG_GCCISH_CXXFLAGS += -fno-rtti CFG_GCCISH_LINK_FLAGS += -g # These flags will cause the compiler to produce a .d file # next to the .o file that lists header deps. CFG_DEPEND_FLAGS = -MMD -MP -MT $(1) -MF $(1:%.o=%.d) define CFG_MAKE_CC - CFG_COMPILE_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ - $$(CFG_GCCISH_CFLAGS) \ - $$(CFG_GCCISH_CFLAGS_$$(HOST_$(1))) \ - $$(CFG_GCC_CFLAGS) \ - $$(CFG_GCC_CFLAGS_$$(HOST_$(1))) \ + CFG_COMPILE_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CC) \ + $$(CFG_GCCISH_CFLAGS) \ + $$(CFG_GCCISH_CFLAGS_$$(HOST_$(1))) \ + $$(CFG_GCC_CFLAGS) \ + $$(CFG_GCC_CFLAGS_$$(HOST_$(1))) \ $$(CFG_DEPEND_FLAGS) \ -c -o $$(1) $$(2) - CFG_LINK_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ - $$(CFG_GCCISH_LINK_FLAGS) -o $$(1) \ - $$(CFG_GCCISH_LINK_FLAGS_$$(HOST_$(1))) \ - $$(CFG_GCCISH_DEF_FLAG)$$(3) $$(2) \ + CFG_LINK_C_$(1) = $$(CFG_GCCISH_CROSS)$$(CC) \ + $$(CFG_GCCISH_LINK_FLAGS) -o $$(1) \ + $$(CFG_GCCISH_LINK_FLAGS_$$(HOST_$(1))) \ + $$(CFG_GCCISH_DEF_FLAG)$$(3) $$(2) \ + $$(call CFG_INSTALL_NAME,$$(4)) + CFG_COMPILE_CXX_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ + $$(CFG_GCCISH_CFLAGS) \ + $$(CFG_GCCISH_CXXFLAGS) \ + $$(CFG_GCCISH_CFLAGS_$$(HOST_$(1))) \ + $$(CFG_GCC_CFLAGS) \ + $$(CFG_GCC_CFLAGS_$$(HOST_$(1))) \ + $$(CFG_DEPEND_FLAGS) \ + -c -o $$(1) $$(2) + CFG_LINK_CXX_$(1) = $$(CFG_GCCISH_CROSS)$$(CXX) \ + $$(CFG_GCCISH_LINK_FLAGS) -o $$(1) \ + $$(CFG_GCCISH_LINK_FLAGS_$$(HOST_$(1))) \ + $$(CFG_GCCISH_DEF_FLAG)$$(3) $$(2) \ $$(call CFG_INSTALL_NAME,$$(4)) endef @@ -285,4 +312,4 @@ define CFG_MAKE_ASSEMBLER endef $(foreach target,$(CFG_TARGET_TRIPLES),\ - $(eval $(call CFG_MAKE_ASSEMBLER,$(target)))) + $(eval $(call CFG_MAKE_ASSEMBLER,$(target)))) \ No newline at end of file diff --git a/mk/pp.mk b/mk/pp.mk index dd956216bbe..34ca47463fd 100644 --- a/mk/pp.mk +++ b/mk/pp.mk @@ -8,7 +8,8 @@ else $(wildcard $(S)src/test/*/*.rs \ $(S)src/test/*/*/*.rs) \ $(wildcard $(S)src/fuzzer/*.rs) \ - $(wildcard $(S)src/cargo/*.rs) + $(wildcard $(S)src/cargo/*.rs) \ + $(wildcard $(S)src/rusti/*.rs) PP_INPUTS_FILTERED = $(shell echo $(PP_INPUTS) | xargs grep -L \ "no-reformat\|xfail-pretty\|xfail-test") diff --git a/mk/rt.mk b/mk/rt.mk index e2e166ff2d6..fd948fbc53a 100644 --- a/mk/rt.mk +++ b/mk/rt.mk @@ -42,7 +42,7 @@ define DEF_RUNTIME_TARGETS # Runtime (C++) library variables ###################################################################### -RUNTIME_CS_$(1) := \ +RUNTIME_CXXS_$(1) := \ rt/sync/timer.cpp \ rt/sync/lock_and_signal.cpp \ rt/sync/rust_thread.cpp \ @@ -77,6 +77,8 @@ RUNTIME_CS_$(1) := \ rt/arch/$$(HOST_$(1))/context.cpp \ rt/arch/$$(HOST_$(1))/gpr.cpp +RUNTIME_CS_$(1) := rt/linenoise/linenoise.c rt/linenoise/utf8.c + RUNTIME_S_$(1) := rt/arch/$$(HOST_$(1))/_context.S \ rt/arch/$$(HOST_$(1))/ccall.S \ rt/arch/$$(HOST_$(1))/record_sp.S @@ -103,9 +105,11 @@ endif RUNTIME_DEF_$(1) := rt/rustrt$$(CFG_DEF_SUFFIX) RUNTIME_INCS_$(1) := -I $$(S)src/rt -I $$(S)src/rt/isaac -I $$(S)src/rt/uthash \ - -I $$(S)src/rt/arch/$$(HOST_$(1)) \ - -I $$(S)src/libuv/include -RUNTIME_OBJS_$(1) := $$(RUNTIME_CS_$(1):rt/%.cpp=rt/$(1)/%.o) \ + -I $$(S)src/rt/arch/$$(HOST_$(1)) \ + -I $$(S)src/rt/linenoise \ + -I $$(S)src/libuv/include +RUNTIME_OBJS_$(1) := $$(RUNTIME_CXXS_$(1):rt/%.cpp=rt/$(1)/%.o) \ + $$(RUNTIME_CS_$(1):rt/%.c=rt/$(1)/%.o) \ $$(RUNTIME_S_$(1):rt/%.S=rt/$(1)/%.o) ALL_OBJ_FILES += $$(RUNTIME_OBJS_$(1)) @@ -115,6 +119,11 @@ ALL_OBJ_FILES += $$(MORESTACK_OBJS_$(1)) RUNTIME_LIBS_$(1) := $$(LIBUV_LIB_$(1)) rt/$(1)/%.o: rt/%.cpp $$(MKFILE_DEPS) + @$$(call E, compile: $$@) + $$(Q)$$(call CFG_COMPILE_CXX_$(1), $$@, $$(RUNTIME_INCS_$(1)) \ + $$(SNAP_DEFINES)) $$< + +rt/$(1)/%.o: rt/%.c $$(MKFILE_DEPS) @$$(call E, compile: $$@) $$(Q)$$(call CFG_COMPILE_C_$(1), $$@, $$(RUNTIME_INCS_$(1)) \ $$(SNAP_DEFINES)) $$< @@ -132,7 +141,7 @@ rt/$(1)/$(CFG_RUNTIME): $$(RUNTIME_OBJS_$(1)) $$(MKFILE_DEPS) \ $$(RUNTIME_DEF_$(1)) \ $$(RUNTIME_LIBS_$(1)) @$$(call E, link: $$@) - $$(Q)$$(call CFG_LINK_C_$(1),$$@, $$(RUNTIME_OBJS_$(1)) \ + $$(Q)$$(call CFG_LINK_CXX_$(1),$$@, $$(RUNTIME_OBJS_$(1)) \ $$(CFG_GCCISH_POST_LIB_FLAGS) $$(RUNTIME_LIBS_$(1)) \ $$(CFG_LIBUV_LINK_FLAGS),$$(RUNTIME_DEF_$(1)),$$(CFG_RUNTIME)) diff --git a/mk/rustllvm.mk b/mk/rustllvm.mk index 622f7d4fa09..c56220af05e 100644 --- a/mk/rustllvm.mk +++ b/mk/rustllvm.mk @@ -25,14 +25,14 @@ ALL_OBJ_FILES += $$(RUSTLLVM_OBJS_OBJS_$(1)) rustllvm/$(1)/$(CFG_RUSTLLVM): $$(RUSTLLVM_OBJS_OBJS_$(1)) \ $$(MKFILE_DEPS) $$(RUSTLLVM_DEF_$(1)) @$$(call E, link: $$@) - $$(Q)$$(call CFG_LINK_C_$(1),$$@,$$(RUSTLLVM_OBJS_OBJS_$(1)) \ + $$(Q)$$(call CFG_LINK_CXX_$(1),$$@,$$(RUSTLLVM_OBJS_OBJS_$(1)) \ $$(CFG_GCCISH_PRE_LIB_FLAGS) $$(LLVM_LIBS_$(1)) \ $$(CFG_GCCISH_POST_LIB_FLAGS) \ $$(LLVM_LDFLAGS_$(1)),$$(RUSTLLVM_DEF_$(1)),$$(CFG_RUSTLLVM)) rustllvm/$(1)/%.o: rustllvm/%.cpp $$(MKFILE_DEPS) $$(LLVM_CONFIG_$(1)) @$$(call E, compile: $$@) - $$(Q)$$(call CFG_COMPILE_C_$(1), $$@, $$(LLVM_CXXFLAGS_$(1)) $$(RUSTLLVM_INCS_$(1))) $$< + $$(Q)$$(call CFG_COMPILE_CXX_$(1), $$@, $$(LLVM_CXXFLAGS_$(1)) $$(RUSTLLVM_INCS_$(1))) $$< endef # Instantiate template for all stages diff --git a/mk/tests.mk b/mk/tests.mk index d59a3403a62..2eec607ff81 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -122,6 +122,7 @@ ALL_CS := $(wildcard $(S)src/rt/*.cpp \ ALL_CS := $(filter-out $(S)src/rt/bigint/bigint_ext.cpp \ $(S)src/rt/bigint/bigint_int.cpp \ $(S)src/rt/miniz.cpp \ + $(S)src/rt/linenoise/linenoise.c \ ,$(ALL_CS)) ALL_HS := $(wildcard $(S)src/rt/*.h \ $(S)src/rt/*/*.h \ @@ -135,6 +136,7 @@ ALL_HS := $(filter-out $(S)src/rt/vg/valgrind.h \ $(S)src/rt/msvc/stdint.h \ $(S)src/rt/msvc/inttypes.h \ $(S)src/rt/bigint/bigint.h \ + $(S)src/rt/linenoise/linenoise.h \ ,$(ALL_HS)) tidy: @@ -229,6 +231,7 @@ check-stage$(1)-T-$(2)-H-$(3): \ check-stage$(1)-T-$(2)-H-$(3)-bench \ check-stage$(1)-T-$(2)-H-$(3)-pretty \ check-stage$(1)-T-$(2)-H-$(3)-rustdoc \ + check-stage$(1)-T-$(2)-H-$(3)-rusti \ check-stage$(1)-T-$(2)-H-$(3)-cargo \ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial \ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-ffi \ @@ -289,6 +292,9 @@ check-stage$(1)-T-$(2)-H-$(3)-pretty-pretty: \ check-stage$(1)-T-$(2)-H-$(3)-rustdoc: \ check-stage$(1)-T-$(2)-H-$(3)-rustdoc-dummy +check-stage$(1)-T-$(2)-H-$(3)-rusti: \ + check-stage$(1)-T-$(2)-H-$(3)-rusti-dummy + check-stage$(1)-T-$(2)-H-$(3)-cargo: \ check-stage$(1)-T-$(2)-H-$(3)-cargo-dummy @@ -371,6 +377,24 @@ check-stage$(1)-T-$(2)-H-$(3)-rustdoc-dummy: \ $$(Q)$$(call CFG_RUN_TEST,$$<,$(2),$(3)) $$(TESTARGS) \ --logfile tmp/check-stage$(1)-T-$(2)-H-$(3)-rustdoc.log +# Rules for the rusti test runner + +$(3)/test/rustitest.stage$(1)-$(2)$$(X): \ + $$(RUSTI_CRATE) $$(RUSTI_INPUTS) \ + $$(TSREQ$(1)_T_$(2)_H_$(3)) \ + $$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_CORELIB) \ + $$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_STDLIB) \ + $$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_LIBRUSTC) + @$$(call E, compile_and_link: $$@) + $$(STAGE$(1)_T_$(2)_H_$(3)) -o $$@ $$< --test + +check-stage$(1)-T-$(2)-H-$(3)-rusti-dummy: \ + $(3)/test/rustitest.stage$(1)-$(2)$$(X) + @$$(call E, run: $$<) + $$(Q)$$(call CFG_RUN_TEST,$$<,$(2),$(3)) $$(TESTARGS) \ + --logfile tmp/check-stage$(1)-T-$(2)-H-$(3)-rusti.log + + # Rules for the cargo test runner $(3)/test/cargotest.stage$(1)-$(2)$$(X): \ @@ -756,6 +780,9 @@ check-stage$(1)-H-$(2)-pretty-pretty: \ check-stage$(1)-H-$(2)-rustdoc: \ $$(foreach target,$$(CFG_TARGET_TRIPLES), \ check-stage$(1)-T-$$(target)-H-$(2)-rustdoc) +check-stage$(1)-H-$(2)-rusti: \ + $$(foreach target,$$(CFG_TARGET_TRIPLES), \ + check-stage$(1)-T-$$(target)-H-$(2)-rusti) check-stage$(1)-H-$(2)-cargo: \ $$(foreach target,$$(CFG_TARGET_TRIPLES), \ check-stage$(1)-T-$$(target)-H-$(2)-cargo) diff --git a/mk/tools.mk b/mk/tools.mk index 011a7912852..98356985654 100644 --- a/mk/tools.mk +++ b/mk/tools.mk @@ -18,6 +18,10 @@ CARGO_INPUTS := $(wildcard $(S)src/cargo/*rs) RUSTDOC_LIB := $(S)src/librustdoc/rustdoc.rc RUSTDOC_INPUTS := $(wildcard $(S)src/rustdoc/*.rs) +# Rusti, the JIT REPL +RUSTI_CRATE := $(S)src/rusti/rusti.rc +RUSTI_INPUTS := $(wildcard $(S)src/rusti/*.rs) + # FIXME: These are only built for the host arch. Eventually we'll # have tools that need to built for other targets. define TOOLS_STAGE_N @@ -126,6 +130,21 @@ $$(HBIN$(2)_H_$(4))/rustdoc$$(X): \ @$$(call E, cp: $$@) $$(Q)cp $$< $$@ +$$(TBIN$(1)_T_$(4)_H_$(3))/rusti$$(X): \ + $$(RUSTI_CRATE) $$(RUSTI_INPUTS) \ + $$(TSREQ$(1)_T_$(4)_H_$(3)) \ + $$(TLIB$(1)_T_$(4)_H_$(3))/$$(CFG_CORELIB) \ + $$(TLIB$(1)_T_$(4)_H_$(3))/$$(CFG_STDLIB) \ + $$(TLIB$(1)_T_$(4)_H_$(3))/$$(CFG_LIBRUSTC) + @$$(call E, compile_and_link: $$@) + $$(STAGE$(1)_T_$(4)_H_$(3)) -o $$@ $$< + +$$(HBIN$(2)_H_$(4))/rusti$$(X): \ + $$(TBIN$(1)_T_$(4)_H_$(3))/rusti$$(X) \ + $$(HSREQ$(2)_H_$(4)) + @$$(call E, cp: $$@) + $$(Q)cp $$< $$@ + endef $(foreach host,$(CFG_TARGET_TRIPLES), \ diff --git a/src/README.txt b/src/README.txt index 5e0d9bd0e84..d32751cf576 100644 --- a/src/README.txt +++ b/src/README.txt @@ -30,6 +30,8 @@ compiletest/ The test runner cargo/ The package manager +rusti/ The JIT REPL + rustdoc/ The Rust API documentation tool llvm/ The LLVM submodule diff --git a/src/libstd/rl.rs b/src/libstd/rl.rs new file mode 100644 index 00000000000..dc098586382 --- /dev/null +++ b/src/libstd/rl.rs @@ -0,0 +1,69 @@ +use libc::{c_char, c_int}; + +extern mod rustrt { + #[legacy_exports]; + fn linenoise(prompt: *c_char) -> *c_char; + fn linenoiseHistoryAdd(line: *c_char) -> c_int; + fn linenoiseHistorySetMaxLen(len: c_int) -> c_int; + fn linenoiseHistorySave(file: *c_char) -> c_int; + fn linenoiseHistoryLoad(file: *c_char) -> c_int; + fn linenoiseSetCompletionCallback(callback: *u8); + fn linenoiseAddCompletion(completions: *(), line: *c_char); +} + +/// Add a line to history +pub fn add_history(line: ~str) -> bool { + do str::as_c_str(line) |buf| { + rustrt::linenoiseHistoryAdd(buf) == 1 as c_int + } +} + +/// Set the maximum amount of lines stored +pub fn set_history_max_len(len: int) -> bool { + rustrt::linenoiseHistorySetMaxLen(len as c_int) == 1 as c_int +} + +/// Save line history to a file +pub fn save_history(file: ~str) -> bool { + do str::as_c_str(file) |buf| { + rustrt::linenoiseHistorySave(buf) == 1 as c_int + } +} + +/// Load line history from a file +pub fn load_history(file: ~str) -> bool { + do str::as_c_str(file) |buf| { + rustrt::linenoiseHistoryLoad(buf) == 1 as c_int + } +} + +/// Print out a prompt and then wait for input and return it +pub fn read(prompt: ~str) -> Option<~str> { + do str::as_c_str(prompt) |buf| unsafe { + let line = rustrt::linenoise(buf); + + if line.is_null() { None } + else { Some(str::raw::from_c_str(line)) } + } +} + +pub type CompletionCb = fn~(~str, fn(~str)); + +fn complete_key(_v: @CompletionCb) {} + +/// Bind to the main completion callback +pub fn complete(cb: CompletionCb) unsafe { + task::local_data::local_data_set(complete_key, @(move cb)); + + extern fn callback(line: *c_char, completions: *()) unsafe { + let cb = copy *task::local_data::local_data_get(complete_key).get(); + + do cb(str::raw::from_c_str(line)) |suggestion| { + do str::as_c_str(suggestion) |buf| { + rustrt::linenoiseAddCompletion(completions, buf); + } + } + } + + rustrt::linenoiseSetCompletionCallback(callback); +} \ No newline at end of file diff --git a/src/libstd/std.rc b/src/libstd/std.rc index 0c48cd7ce00..ee1b8ab5773 100644 --- a/src/libstd/std.rc +++ b/src/libstd/std.rc @@ -83,6 +83,7 @@ pub mod arena; pub mod par; pub mod cmp; pub mod base64; +pub mod rl; #[cfg(unicode)] mod unicode; diff --git a/src/rt/linenoise/README.markdown b/src/rt/linenoise/README.markdown new file mode 100644 index 00000000000..f008d2d3d5e --- /dev/null +++ b/src/rt/linenoise/README.markdown @@ -0,0 +1,47 @@ +# Linenoise + +A minimal, zero-config, BSD licensed, readline replacement. + +News: linenoise now includes minimal completion support, thanks to Pieter Noordhuis (@pnoordhuis). + +News: linenoise is now part of [Android](http://android.git.kernel.org/?p=platform/system/core.git;a=tree;f=liblinenoise;h=56450eaed7f783760e5e6a5993ef75cde2e29dea;hb=HEAD Android)! + +## Can a line editing library be 20k lines of code? + +Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing? + +So what usually happens is either: + + * Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Real world example of this problem: Tclsh). + * Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance). + +The result is a pollution of binaries without line editing support. + +So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to linenoise if not. + +## Terminals, in 2010. + +Apparently almost every terminal you can happen to use today has some kind of support for VT100 alike escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it. + +Since it's so young I guess there are a few bugs, or the lib may not compile or work with some operating system, but it's a matter of a few weeks and eventually we'll get it right, and there will be no excuses for not shipping command line tools without built-in line editing support. + +The library is currently less than 400 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software. + +## Tested with... + + * Linux text only console ($TERM = linux) + * Linux KDE terminal application ($TERM = xterm) + * Linux xterm ($TERM = xterm) + * Mac OS X iTerm ($TERM = xterm) + * Mac OS X default Terminal.app ($TERM = xterm) + * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen) + * IBM AIX 6.1 + * FreeBSD xterm ($TERM = xterm) + +Please test it everywhere you can and report back! + +## Let's push this forward! + +Please fork it and add something interesting and send me a pull request. What's especially interesting are fixes, new key bindings, completion. + +Send feedbacks to antirez at gmail diff --git a/src/rt/linenoise/example.c b/src/rt/linenoise/example.c new file mode 100644 index 00000000000..cb51a0af8f9 --- /dev/null +++ b/src/rt/linenoise/example.c @@ -0,0 +1,30 @@ +#include +#include +#include "linenoise.h" + +#ifndef NO_COMPLETION +void completion(const char *buf, linenoiseCompletions *lc) { + if (buf[0] == 'h') { + linenoiseAddCompletion(lc,"hello"); + linenoiseAddCompletion(lc,"hello there"); + } +} +#endif + +int main(void) { + char *line; + +#ifndef NO_COMPLETION + linenoiseSetCompletionCallback(completion); +#endif + linenoiseHistoryLoad("history.txt"); /* Load the history at startup */ + while((line = linenoise("hello> ")) != NULL) { + if (line[0] != '\0') { + printf("echo: '%s'\n", line); + linenoiseHistoryAdd(line); + linenoiseHistorySave("history.txt"); /* Save every new entry */ + } + free(line); + } + return 0; +} diff --git a/src/rt/linenoise/linenoise.c b/src/rt/linenoise/linenoise.c new file mode 100644 index 00000000000..5e3216e4591 --- /dev/null +++ b/src/rt/linenoise/linenoise.c @@ -0,0 +1,1391 @@ +/* linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/msteveb/linenoise + * (forked from http://github.com/antirez/linenoise) + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010, Salvatore Sanfilippo + * Copyright (c) 2010, Pieter Noordhuis + * Copyright (c) 2011, Steve Bennett + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Bloat: + * - Completion? + * + * Unix/termios + * ------------ + * List of escape sequences used by this program, we do everything just + * a few sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward of n chars + * + * CR (Carriage Return) + * Sequence: \r + * Effect: moves cursor to column 1 + * + * The following are used to clear the screen: ESC [ H ESC [ 2 J + * This is actually composed of two sequences: + * + * cursorhome + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED2 (Clear entire screen) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + * == For highlighting control characters, we also use the following two == + * SO (enter StandOut) + * Sequence: ESC [ 7 m + * Effect: Uses some standout mode such as reverse video + * + * SE (Standout End) + * Sequence: ESC [ 0 m + * Effect: Exit standout mode + * + * == Only used if TIOCGWINSZ fails == + * DSR/CPR (Report cursor position) + * Sequence: ESC [ 6 n + * Effect: reports current cursor position as ESC [ NNN ; MMM R + * + * win32/console + * ------------- + * If __MINGW32__ is defined, the win32 console API is used. + * This could probably be made to work for the msvc compiler too. + * This support based in part on work by Jon Griffiths. + */ + +#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */ +#include +#include +#define USE_WINCONSOLE +#ifdef __MINGW32__ +#define HAVE_UNISTD_H +#else +/* Microsoft headers don't like old POSIX names */ +#define strdup _strdup +#define snprintf _snprintf +#endif +#else +#include +#include +#include +#define USE_TERMIOS +#define HAVE_UNISTD_H +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "linenoise.h" +#include "utf8.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 + +#define ctrl(C) ((C) - '@') + +/* Use -ve numbers here to co-exist with normal unicode chars */ +enum { + SPECIAL_NONE, + SPECIAL_UP = -20, + SPECIAL_DOWN = -21, + SPECIAL_LEFT = -22, + SPECIAL_RIGHT = -23, + SPECIAL_DELETE = -24, + SPECIAL_HOME = -25, + SPECIAL_END = -26, +}; + +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; + +/* Structure to contain the status of the current (being edited) line */ +struct current { + char *buf; /* Current buffer. Always null terminated */ + int bufmax; /* Size of the buffer, including space for the null termination */ + int len; /* Number of bytes in 'buf' */ + int chars; /* Number of chars in 'buf' (utf-8 chars) */ + int pos; /* Cursor position, measured in chars */ + int cols; /* Size of the window, in chars */ + const char *prompt; +#if defined(USE_TERMIOS) + int fd; /* Terminal fd */ +#elif defined(USE_WINCONSOLE) + HANDLE outh; /* Console output handle */ + HANDLE inh; /* Console input handle */ + int rows; /* Screen rows */ + int x; /* Current column during output */ + int y; /* Current row */ +#endif +}; + +static int fd_read(struct current *current); +static int getWindowSize(struct current *current); + +void linenoiseHistoryFree(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + history = NULL; + } +} + +#if defined(USE_TERMIOS) +static void linenoiseAtExit(void); +static struct termios orig_termios; /* in order to restore at exit */ +static int rawmode = 0; /* for atexit() function to check if restore is needed*/ +static int atexit_registered = 0; /* register atexit just 1 time */ + +static const char *unsupported_term[] = {"dumb","cons25",NULL}; + +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + + if (term) { + int j; + for (j = 0; unsupported_term[j]; j++) { + if (strcasecmp(term, unsupported_term[j]) == 0) { + return 1; + } + } + } + return 0; +} + +static int enableRawMode(struct current *current) { + struct termios raw; + + current->fd = STDIN_FILENO; + + if (!isatty(current->fd) || isUnsupportedTerm() || + tcgetattr(current->fd, &orig_termios) == -1) { +fatal: + errno = ENOTTY; + return -1; + } + + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) { + goto fatal; + } + rawmode = 1; + + current->cols = 0; + return 0; +} + +static void disableRawMode(struct current *current) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1) + rawmode = 0; +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + if (rawmode) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios); + } + linenoiseHistoryFree(); +} + +/* gcc/glibc insists that we care about the return code of write! */ +#define IGNORE_RC(EXPR) if (EXPR) {} + +/* This is fdprintf() on some systems, but use a different + * name to avoid conflicts + */ +static void fd_printf(int fd, const char *format, ...) +{ + va_list args; + char buf[64]; + int n; + + va_start(args, format); + n = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + IGNORE_RC(write(fd, buf, n)); +} + +static void clearScreen(struct current *current) +{ + fd_printf(current->fd, "\x1b[H\x1b[2J"); +} + +static void cursorToLeft(struct current *current) +{ + fd_printf(current->fd, "\r"); +} + +static int outputChars(struct current *current, const char *buf, int len) +{ + return write(current->fd, buf, len); +} + +static void outputControlChar(struct current *current, char ch) +{ + fd_printf(current->fd, "\x1b[7m^%c\x1b[0m", ch); +} + +static void eraseEol(struct current *current) +{ + fd_printf(current->fd, "\x1b[0K"); +} + +static void setCursorPos(struct current *current, int x) +{ + fd_printf(current->fd, "\r\x1b[%dC", x); +} + +/** + * Reads a char from 'fd', waiting at most 'timeout' milliseconds. + * + * A timeout of -1 means to wait forever. + * + * Returns -1 if no char is received within the time or an error occurs. + */ +static int fd_read_char(int fd, int timeout) +{ + struct pollfd p; + unsigned char c; + + p.fd = fd; + p.events = POLLIN; + + if (poll(&p, 1, timeout) == 0) { + /* timeout */ + return -1; + } + if (read(fd, &c, 1) != 1) { + return -1; + } + return c; +} + +/** + * Reads a complete utf-8 character + * and returns the unicode value, or -1 on error. + */ +static int fd_read(struct current *current) +{ +#ifdef USE_UTF8 + char buf[4]; + int n; + int i; + int c; + + if (read(current->fd, &buf[0], 1) != 1) { + return -1; + } + n = utf8_charlen(buf[0]); + if (n < 1 || n > 3) { + return -1; + } + for (i = 1; i < n; i++) { + if (read(current->fd, &buf[i], 1) != 1) { + return -1; + } + } + buf[n] = 0; + /* decode and return the character */ + utf8_tounicode(buf, &c); + return c; +#else + return fd_read_char(current->fd, -1); +#endif +} + +static int getWindowSize(struct current *current) +{ + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) { + current->cols = ws.ws_col; + return 0; + } + + /* Failed to query the window size. Perhaps we are on a serial terminal. + * Try to query the width by sending the cursor as far to the right + * and reading back the cursor position. + * Note that this is only done once per call to linenoise rather than + * every time the line is refreshed for efficiency reasons. + */ + if (current->cols == 0) { + current->cols = 80; + + /* Move cursor far right and report cursor position, then back to the left */ + fd_printf(current->fd, "\x1b[999C" "\x1b[6n"); + + /* Parse the response: ESC [ rows ; cols R */ + if (fd_read_char(current->fd, 100) == 0x1b && fd_read_char(current->fd, 100) == '[') { + int n = 0; + while (1) { + int ch = fd_read_char(current->fd, 100); + if (ch == ';') { + /* Ignore rows */ + n = 0; + } + else if (ch == 'R') { + /* Got cols */ + if (n != 0 && n < 1000) { + current->cols = n; + } + break; + } + else if (ch >= 0 && ch <= '9') { + n = n * 10 + ch - '0'; + } + else { + break; + } + } + } + } + return 0; +} + +/** + * If escape (27) was received, reads subsequent + * chars to determine if this is a known special key. + * + * Returns SPECIAL_NONE if unrecognised, or -1 if EOF. + * + * If no additional char is received within a short time, + * 27 is returned. + */ +static int check_special(int fd) +{ + int c = fd_read_char(fd, 50); + int c2; + + if (c < 0) { + return 27; + } + + c2 = fd_read_char(fd, 50); + if (c2 < 0) { + return c2; + } + if (c == '[' || c == 'O') { + /* Potential arrow key */ + switch (c2) { + case 'A': + return SPECIAL_UP; + case 'B': + return SPECIAL_DOWN; + case 'C': + return SPECIAL_RIGHT; + case 'D': + return SPECIAL_LEFT; + case 'F': + return SPECIAL_END; + case 'H': + return SPECIAL_HOME; + } + } + if (c == '[' && c2 >= '1' && c2 <= '8') { + /* extended escape */ + c = fd_read_char(fd, 50); + if (c == '~') { + switch (c2) { + case '3': + return SPECIAL_DELETE; + case '7': + return SPECIAL_HOME; + case '8': + return SPECIAL_END; + } + } + while (c != -1 && c != '~') { + /* .e.g \e[12~ or '\e[11;2~ discard the complete sequence */ + c = fd_read_char(fd, 50); + } + } + + return SPECIAL_NONE; +} +#elif defined(USE_WINCONSOLE) + +static DWORD orig_consolemode = 0; + +static int enableRawMode(struct current *current) { + DWORD n; + INPUT_RECORD irec; + + current->outh = GetStdHandle(STD_OUTPUT_HANDLE); + current->inh = GetStdHandle(STD_INPUT_HANDLE); + + if (!PeekConsoleInput(current->inh, &irec, 1, &n)) { + return -1; + } + if (getWindowSize(current) != 0) { + return -1; + } + if (GetConsoleMode(current->inh, &orig_consolemode)) { + SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT); + } + return 0; +} + +static void disableRawMode(struct current *current) +{ + SetConsoleMode(current->inh, orig_consolemode); +} + +static void clearScreen(struct current *current) +{ + COORD topleft = { 0, 0 }; + DWORD n; + + FillConsoleOutputCharacter(current->outh, ' ', + current->cols * current->rows, topleft, &n); + FillConsoleOutputAttribute(current->outh, + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, + current->cols * current->rows, topleft, &n); + SetConsoleCursorPosition(current->outh, topleft); +} + +static void cursorToLeft(struct current *current) +{ + COORD pos = { 0, (SHORT)current->y }; + DWORD n; + + FillConsoleOutputAttribute(current->outh, + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n); + current->x = 0; +} + +static int outputChars(struct current *current, const char *buf, int len) +{ + COORD pos = { (SHORT)current->x, (SHORT)current->y }; + DWORD n; + + WriteConsoleOutputCharacter(current->outh, buf, len, pos, &n); + current->x += len; + return 0; +} + +static void outputControlChar(struct current *current, char ch) +{ + COORD pos = { (SHORT)current->x, (SHORT)current->y }; + DWORD n; + + FillConsoleOutputAttribute(current->outh, BACKGROUND_INTENSITY, 2, pos, &n); + outputChars(current, "^", 1); + outputChars(current, &ch, 1); +} + +static void eraseEol(struct current *current) +{ + COORD pos = { (SHORT)current->x, (SHORT)current->y }; + DWORD n; + + FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n); +} + +static void setCursorPos(struct current *current, int x) +{ + COORD pos = { (SHORT)x, (SHORT)current->y }; + + SetConsoleCursorPosition(current->outh, pos); + current->x = x; +} + +static int fd_read(struct current *current) +{ + while (1) { + INPUT_RECORD irec; + DWORD n; + if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) { + break; + } + if (!ReadConsoleInput (current->inh, &irec, 1, &n)) { + break; + } + if (irec.EventType == KEY_EVENT && irec.Event.KeyEvent.bKeyDown) { + KEY_EVENT_RECORD *k = &irec.Event.KeyEvent; + if (k->dwControlKeyState & ENHANCED_KEY) { + switch (k->wVirtualKeyCode) { + case VK_LEFT: + return SPECIAL_LEFT; + case VK_RIGHT: + return SPECIAL_RIGHT; + case VK_UP: + return SPECIAL_UP; + case VK_DOWN: + return SPECIAL_DOWN; + case VK_DELETE: + return SPECIAL_DELETE; + case VK_HOME: + return SPECIAL_HOME; + case VK_END: + return SPECIAL_END; + } + } + /* Note that control characters are already translated in AsciiChar */ + else { +#ifdef USE_UTF8 + return k->uChar.UnicodeChar; +#else + return k->uChar.AsciiChar; +#endif + } + } + } + return -1; +} + +static int getWindowSize(struct current *current) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(current->outh, &info)) { + return -1; + } + current->cols = info.dwSize.X; + current->rows = info.dwSize.Y; + if (current->cols <= 0 || current->rows <= 0) { + current->cols = 80; + return -1; + } + current->y = info.dwCursorPosition.Y; + current->x = info.dwCursorPosition.X; + return 0; +} +#endif + +static int utf8_getchars(char *buf, int c) +{ +#ifdef USE_UTF8 + return utf8_fromunicode(buf, c); +#else + *buf = c; + return 1; +#endif +} + +/** + * Returns the unicode character at the given offset, + * or -1 if none. + */ +static int get_char(struct current *current, int pos) +{ + if (pos >= 0 && pos < current->chars) { + int c; + int i = utf8_index(current->buf, pos); + (void)utf8_tounicode(current->buf + i, &c); + return c; + } + return -1; +} + +static void refreshLine(const char *prompt, struct current *current) +{ + int plen; + int pchars; + int backup = 0; + int i; + const char *buf = current->buf; + int chars = current->chars; + int pos = current->pos; + int b; + int ch; + int n; + + /* Should intercept SIGWINCH. For now, just get the size every time */ + getWindowSize(current); + + plen = strlen(prompt); + pchars = utf8_strlen(prompt, plen); + + /* Account for a line which is too long to fit in the window. + * Note that control chars require an extra column + */ + + /* How many cols are required to the left of 'pos'? + * The prompt, plus one extra for each control char + */ + n = pchars + utf8_strlen(buf, current->len); + b = 0; + for (i = 0; i < pos; i++) { + b += utf8_tounicode(buf + b, &ch); + if (ch < ' ') { + n++; + } + } + + /* If too many are need, strip chars off the front of 'buf' + * until it fits. Note that if the current char is a control character, + * we need one extra col. + */ + if (current->pos < current->chars && get_char(current, current->pos) < ' ') { + n++; + } + + while (n >= current->cols && pos > 0) { + b = utf8_tounicode(buf, &ch); + if (ch < ' ') { + n--; + } + n--; + buf += b; + pos--; + chars--; + } + + /* Cursor to left edge, then the prompt */ + cursorToLeft(current); + outputChars(current, prompt, plen); + + /* Now the current buffer content */ + + /* Need special handling for control characters. + * If we hit 'cols', stop. + */ + b = 0; /* unwritted bytes */ + n = 0; /* How many control chars were written */ + for (i = 0; i < chars; i++) { + int ch; + int w = utf8_tounicode(buf + b, &ch); + if (ch < ' ') { + n++; + } + if (pchars + i + n >= current->cols) { + break; + } + if (ch < ' ') { + /* A control character, so write the buffer so far */ + outputChars(current, buf, b); + buf += b + w; + b = 0; + outputControlChar(current, ch + '@'); + if (i < pos) { + backup++; + } + } + else { + b += w; + } + } + outputChars(current, buf, b); + + /* Erase to right, move cursor to original position */ + eraseEol(current); + setCursorPos(current, pos + pchars + backup); +} + +static void set_current(struct current *current, const char *str) +{ + strncpy(current->buf, str, current->bufmax); + current->buf[current->bufmax - 1] = 0; + current->len = strlen(current->buf); + current->pos = current->chars = utf8_strlen(current->buf, current->len); +} + +static int has_room(struct current *current, int bytes) +{ + return current->len + bytes < current->bufmax - 1; +} + +/** + * Removes the char at 'pos'. + * + * Returns 1 if the line needs to be refreshed, 2 if not + * and 0 if nothing was removed + */ +static int remove_char(struct current *current, int pos) +{ + if (pos >= 0 && pos < current->chars) { + int p1, p2; + int ret = 1; + p1 = utf8_index(current->buf, pos); + p2 = p1 + utf8_index(current->buf + p1, 1); + +#ifdef USE_TERMIOS + /* optimise remove char in the case of removing the last char */ + if (current->pos == pos + 1 && current->pos == current->chars) { + if (current->buf[pos] >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) { + ret = 2; + fd_printf(current->fd, "\b \b"); + } + } +#endif + + /* Move the null char too */ + memmove(current->buf + p1, current->buf + p2, current->len - p2 + 1); + current->len -= (p2 - p1); + current->chars--; + + if (current->pos > pos) { + current->pos--; + } + return ret; + } + return 0; +} + +/** + * Insert 'ch' at position 'pos' + * + * Returns 1 if the line needs to be refreshed, 2 if not + * and 0 if nothing was inserted (no room) + */ +static int insert_char(struct current *current, int pos, int ch) +{ + char buf[3]; + int n = utf8_getchars(buf, ch); + + if (has_room(current, n) && pos >= 0 && pos <= current->chars) { + int p1, p2; + int ret = 1; + p1 = utf8_index(current->buf, pos); + p2 = p1 + n; + +#ifdef USE_TERMIOS + /* optimise the case where adding a single char to the end and no scrolling is needed */ + if (current->pos == pos && current->chars == pos) { + if (ch >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) { + IGNORE_RC(write(current->fd, buf, n)); + ret = 2; + } + } +#endif + + memmove(current->buf + p2, current->buf + p1, current->len - p1); + memcpy(current->buf + p1, buf, n); + current->len += n; + + current->chars++; + if (current->pos >= pos) { + current->pos++; + } + return ret; + } + return 0; +} + +/** + * Returns 0 if no chars were removed or non-zero otherwise. + */ +static int remove_chars(struct current *current, int pos, int n) +{ + int removed = 0; + while (n-- && remove_char(current, pos)) { + removed++; + } + return removed; +} + +#ifndef NO_COMPLETION +static linenoiseCompletionCallback *completionCallback = NULL; + +static void beep() { +#ifdef USE_TERMIOS + fprintf(stderr, "\x7"); + fflush(stderr); +#endif +} + +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + free(lc->cvec); +} + +static int completeLine(struct current *current) { + linenoiseCompletions lc = { 0, NULL }; + int c = 0; + + completionCallback(current->buf,&lc); + if (lc.len == 0) { + beep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct current tmp = *current; + tmp.buf = lc.cvec[i]; + tmp.pos = tmp.len = strlen(tmp.buf); + tmp.chars = utf8_strlen(tmp.buf, tmp.len); + refreshLine(current->prompt, &tmp); + } else { + refreshLine(current->prompt, current); + } + + c = fd_read(current); + if (c == -1) { + break; + } + + switch(c) { + case '\t': /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) beep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) { + refreshLine(current->prompt, current); + } + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + set_current(current,lc.cvec[i]); + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + lc->cvec[lc->len++] = strdup(str); +} + +#endif + +static int linenoisePrompt(struct current *current) { + int history_index = 0; + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + set_current(current, ""); + refreshLine(current->prompt, current); + + while(1) { + int dir = -1; + int c = fd_read(current); + +#ifndef NO_COMPLETION + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == '\t' && current->pos == current->chars && completionCallback != NULL) { + c = completeLine(current); + /* Return on errors */ + if (c < 0) return current->len; + /* Read next character when 0 */ + if (c == 0) continue; + } +#endif + +process_char: + if (c == -1) return current->len; +#ifdef USE_TERMIOS + if (c == 27) { /* escape sequence */ + c = check_special(current->fd); + } +#endif + switch(c) { + case '\r': /* enter */ + history_len--; + free(history[history_len]); + return current->len; + case ctrl('C'): /* ctrl-c */ + errno = EAGAIN; + return -1; + case 127: /* backspace */ + case ctrl('H'): + if (remove_char(current, current->pos - 1) == 1) { + refreshLine(current->prompt, current); + } + break; + case ctrl('D'): /* ctrl-d */ + if (current->len == 0) { + /* Empty line, so EOF */ + history_len--; + free(history[history_len]); + return -1; + } + /* Otherwise fall through to delete char to right of cursor */ + case SPECIAL_DELETE: + if (remove_char(current, current->pos) == 1) { + refreshLine(current->prompt, current); + } + break; + case ctrl('W'): /* ctrl-w */ + /* eat any spaces on the left */ + { + int pos = current->pos; + while (pos > 0 && get_char(current, pos - 1) == ' ') { + pos--; + } + + /* now eat any non-spaces on the left */ + while (pos > 0 && get_char(current, pos - 1) != ' ') { + pos--; + } + + if (remove_chars(current, pos, current->pos - pos)) { + refreshLine(current->prompt, current); + } + } + break; + case ctrl('R'): /* ctrl-r */ + { + /* Display the reverse-i-search prompt and process chars */ + char rbuf[50]; + char rprompt[80]; + int rchars = 0; + int rlen = 0; + int searchpos = history_len - 1; + + rbuf[0] = 0; + while (1) { + int n = 0; + const char *p = NULL; + int skipsame = 0; + int searchdir = -1; + + snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf); + refreshLine(rprompt, current); + c = fd_read(current); + if (c == ctrl('H') || c == 127) { + if (rchars) { + int p = utf8_index(rbuf, --rchars); + rbuf[p] = 0; + rlen = strlen(rbuf); + } + continue; + } +#ifdef USE_TERMIOS + if (c == 27) { + c = check_special(current->fd); + } +#endif + if (c == ctrl('P') || c == SPECIAL_UP) { + /* Search for the previous (earlier) match */ + if (searchpos > 0) { + searchpos--; + } + skipsame = 1; + } + else if (c == ctrl('N') || c == SPECIAL_DOWN) { + /* Search for the next (later) match */ + if (searchpos < history_len) { + searchpos++; + } + searchdir = 1; + skipsame = 1; + } + else if (c >= ' ') { + if (rlen >= (int)sizeof(rbuf) + 3) { + continue; + } + + n = utf8_getchars(rbuf + rlen, c); + rlen += n; + rchars++; + rbuf[rlen] = 0; + + /* Adding a new char resets the search location */ + searchpos = history_len - 1; + } + else { + /* Exit from incremental search mode */ + break; + } + + /* Now search through the history for a match */ + for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) { + p = strstr(history[searchpos], rbuf); + if (p) { + /* Found a match */ + if (skipsame && strcmp(history[searchpos], current->buf) == 0) { + /* But it is identical, so skip it */ + continue; + } + /* Copy the matching line and set the cursor position */ + set_current(current,history[searchpos]); + current->pos = utf8_strlen(history[searchpos], p - history[searchpos]); + break; + } + } + if (!p && n) { + /* No match, so don't add it */ + rchars--; + rlen -= n; + rbuf[rlen] = 0; + } + } + if (c == ctrl('G') || c == ctrl('C')) { + /* ctrl-g terminates the search with no effect */ + set_current(current, ""); + c = 0; + } + else if (c == ctrl('J')) { + /* ctrl-j terminates the search leaving the buffer in place */ + c = 0; + } + /* Go process the char normally */ + refreshLine(current->prompt, current); + goto process_char; + } + break; + case ctrl('T'): /* ctrl-t */ + if (current->pos > 0 && current->pos < current->chars) { + c = get_char(current, current->pos); + remove_char(current, current->pos); + insert_char(current, current->pos - 1, c); + refreshLine(current->prompt, current); + } + break; + case ctrl('V'): /* ctrl-v */ + if (has_room(current, 3)) { + /* Insert the ^V first */ + if (insert_char(current, current->pos, c)) { + refreshLine(current->prompt, current); + /* Now wait for the next char. Can insert anything except \0 */ + c = fd_read(current); + + /* Remove the ^V first */ + remove_char(current, current->pos - 1); + if (c != -1) { + /* Insert the actual char */ + insert_char(current, current->pos, c); + } + refreshLine(current->prompt, current); + } + } + break; + case ctrl('B'): + case SPECIAL_LEFT: + if (current->pos > 0) { + current->pos--; + refreshLine(current->prompt, current); + } + break; + case ctrl('F'): + case SPECIAL_RIGHT: + if (current->pos < current->chars) { + current->pos++; + refreshLine(current->prompt, current); + } + break; + case ctrl('P'): + case SPECIAL_UP: + dir = 1; + case ctrl('N'): + case SPECIAL_DOWN: + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with tne next one. */ + free(history[history_len-1-history_index]); + history[history_len-1-history_index] = strdup(current->buf); + /* Show the new entry */ + history_index += dir; + if (history_index < 0) { + history_index = 0; + break; + } else if (history_index >= history_len) { + history_index = history_len-1; + break; + } + set_current(current, history[history_len-1-history_index]); + refreshLine(current->prompt, current); + } + break; + case ctrl('A'): /* Ctrl+a, go to the start of the line */ + case SPECIAL_HOME: + current->pos = 0; + refreshLine(current->prompt, current); + break; + case ctrl('E'): /* ctrl+e, go to the end of the line */ + case SPECIAL_END: + current->pos = current->chars; + refreshLine(current->prompt, current); + break; + case ctrl('U'): /* Ctrl+u, delete to beginning of line. */ + if (remove_chars(current, 0, current->pos)) { + refreshLine(current->prompt, current); + } + break; + case ctrl('K'): /* Ctrl+k, delete from current to end of line. */ + if (remove_chars(current, current->pos, current->chars - current->pos)) { + refreshLine(current->prompt, current); + } + break; + case ctrl('L'): /* Ctrl+L, clear screen */ + clearScreen(current); + /* Force recalc of window size for serial terminals */ + current->cols = 0; + refreshLine(current->prompt, current); + break; + default: + /* Only tab is allowed without ^V */ + if (c == '\t' || c >= ' ') { + if (insert_char(current, current->pos, c) == 1) { + refreshLine(current->prompt, current); + } + } + break; + } + } + return current->len; +} + +char *linenoise(const char *prompt) +{ + int count; + struct current current; + char buf[LINENOISE_MAX_LINE]; + + if (enableRawMode(¤t) == -1) { + printf("%s", prompt); + fflush(stdout); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + return NULL; + } + count = strlen(buf); + if (count && buf[count-1] == '\n') { + count--; + buf[count] = '\0'; + } + } + else + { + current.buf = buf; + current.bufmax = sizeof(buf); + current.len = 0; + current.chars = 0; + current.pos = 0; + current.prompt = prompt; + + count = linenoisePrompt(¤t); + disableRawMode(¤t); + printf("\n"); + if (count == -1) { + return NULL; + } + } + return strdup(buf); +} + +/* Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + if (history_max_len == 0) return 0; + if (history == NULL) { + history = (char **)malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* do not insert duplicate lines into history */ + if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) { + return 0; + } + + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == history_max_len) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +int linenoiseHistorySetMaxLen(int len) { + char **newHistory; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + newHistory = (char **)malloc(sizeof(char*)*len); + if (newHistory == NULL) return 0; + if (len < tocopy) tocopy = len; + memcpy(newHistory,history+(history_max_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = newHistory; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + FILE *fp = fopen(filename,"w"); + int j; + + if (fp == NULL) return -1; + for (j = 0; j < history_len; j++) { + const char *str = history[j]; + /* Need to encode backslash, nl and cr */ + while (*str) { + if (*str == '\\') { + fputs("\\\\", fp); + } + else if (*str == '\n') { + fputs("\\n", fp); + } + else if (*str == '\r') { + fputs("\\r", fp); + } + else { + fputc(*str, fp); + } + str++; + } + fputc('\n', fp); + } + + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename,"r"); + char buf[LINENOISE_MAX_LINE]; + + if (fp == NULL) return -1; + + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *src, *dest; + + /* Decode backslash escaped values */ + for (src = dest = buf; *src; src++) { + char ch = *src; + + if (ch == '\\') { + src++; + if (*src == 'n') { + ch = '\n'; + } + else if (*src == 'r') { + ch = '\r'; + } else { + ch = *src; + } + } + *dest++ = ch; + } + /* Remove trailing newline */ + if (dest != buf && (dest[-1] == '\n' || dest[-1] == '\r')) { + dest--; + } + *dest = 0; + + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} + +/* Provide access to the history buffer. + * + * If 'len' is not NULL, the length is stored in *len. + */ +char **linenoiseHistory(int *len) { + if (len) { + *len = history_len; + } + return history; +} diff --git a/src/rt/linenoise/linenoise.h b/src/rt/linenoise/linenoise.h new file mode 100644 index 00000000000..59f28976d67 --- /dev/null +++ b/src/rt/linenoise/linenoise.h @@ -0,0 +1,59 @@ +/* linenoise.h -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010, Salvatore Sanfilippo + * Copyright (c) 2010, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +#ifndef NO_COMPLETION +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); +#endif + +char *linenoise(const char *prompt); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); +void linenoiseHistoryFree(void); +char **linenoiseHistory(int *len); + +#endif /* __LINENOISE_H */ diff --git a/src/rt/linenoise/utf8.c b/src/rt/linenoise/utf8.c new file mode 100644 index 00000000000..26924b46c19 --- /dev/null +++ b/src/rt/linenoise/utf8.c @@ -0,0 +1,115 @@ +/** + * UTF-8 utility functions + * + * (c) 2010 Steve Bennett + * + * See LICENCE for licence details. + */ + +#include +#include +#include +#include +#include "utf8.h" + +#ifdef USE_UTF8 +int utf8_fromunicode(char *p, unsigned short uc) +{ + if (uc <= 0x7f) { + *p = uc; + return 1; + } + else if (uc <= 0x7ff) { + *p++ = 0xc0 | ((uc & 0x7c0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 2; + } + else { + *p++ = 0xe0 | ((uc & 0xf000) >> 12); + *p++ = 0x80 | ((uc & 0xfc0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 3; + } +} + +int utf8_charlen(int c) +{ + if ((c & 0x80) == 0) { + return 1; + } + if ((c & 0xe0) == 0xc0) { + return 2; + } + if ((c & 0xf0) == 0xe0) { + return 3; + } + if ((c & 0xf8) == 0xf0) { + return 4; + } + /* Invalid sequence */ + return -1; +} + +int utf8_strlen(const char *str, int bytelen) +{ + int charlen = 0; + if (bytelen < 0) { + bytelen = strlen(str); + } + while (bytelen) { + int c; + int l = utf8_tounicode(str, &c); + charlen++; + str += l; + bytelen -= l; + } + return charlen; +} + +int utf8_index(const char *str, int index) +{ + const char *s = str; + while (index--) { + int c; + s += utf8_tounicode(s, &c); + } + return s - str; +} + +int utf8_charequal(const char *s1, const char *s2) +{ + int c1, c2; + + utf8_tounicode(s1, &c1); + utf8_tounicode(s2, &c2); + + return c1 == c2; +} + +int utf8_tounicode(const char *str, int *uc) +{ + unsigned const char *s = (unsigned const char *)str; + + if (s[0] < 0xc0) { + *uc = s[0]; + return 1; + } + if (s[0] < 0xe0) { + if ((s[1] & 0xc0) == 0x80) { + *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80); + return 2; + } + } + else if (s[0] < 0xf0) { + if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) { + *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80); + return 3; + } + } + + /* Invalid sequence, so just return the byte */ + *uc = *s; + return 1; +} + +#endif diff --git a/src/rt/linenoise/utf8.h b/src/rt/linenoise/utf8.h new file mode 100644 index 00000000000..9537939876a --- /dev/null +++ b/src/rt/linenoise/utf8.h @@ -0,0 +1,79 @@ +#ifndef UTF8_UTIL_H +#define UTF8_UTIL_H +/** + * UTF-8 utility functions + * + * (c) 2010 Steve Bennett + * + * See LICENCE for licence details. + */ + +#ifndef USE_UTF8 +#include + +/* No utf-8 support. 1 byte = 1 char */ +#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B)) +#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1) +#define utf8_index(C, I) (I) +#define utf8_charlen(C) 1 + +#else +/** + * Converts the given unicode codepoint (0 - 0xffff) to utf-8 + * and stores the result at 'p'. + * + * Returns the number of utf-8 characters (1-3). + */ +int utf8_fromunicode(char *p, unsigned short uc); + +/** + * Returns the length of the utf-8 sequence starting with 'c'. + * + * Returns 1-4, or -1 if this is not a valid start byte. + * + * Note that charlen=4 is not supported by the rest of the API. + */ +int utf8_charlen(int c); + +/** + * Returns the number of characters in the utf-8 + * string of the given byte length. + * + * Any bytes which are not part of an valid utf-8 + * sequence are treated as individual characters. + * + * The string *must* be null terminated. + * + * Does not support unicode code points > \uffff + */ +int utf8_strlen(const char *str, int bytelen); + +/** + * Returns the byte index of the given character in the utf-8 string. + * + * The string *must* be null terminated. + * + * This will return the byte length of a utf-8 string + * if given the char length. + */ +int utf8_index(const char *str, int charindex); + +/** + * Returns the unicode codepoint corresponding to the + * utf-8 sequence 'str'. + * + * Stores the result in *uc and returns the number of bytes + * consumed. + * + * If 'str' is null terminated, then an invalid utf-8 sequence + * at the end of the string will be returned as individual bytes. + * + * If it is not null terminated, the length *must* be checked first. + * + * Does not support unicode code points > \uffff + */ +int utf8_tounicode(const char *str, int *uc); + +#endif + +#endif diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index 1eedb8d5e28..8dac7d515b3 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -203,3 +203,10 @@ rust_uv_ip4_port rust_uv_ip6_port rust_uv_tcp_getpeername rust_uv_tcp_getpeername6 +linenoise +linenoiseSetCompletionCallback +linenoiseAddCompletion +linenoiseHistoryAdd +linenoiseHistorySetMaxLen +linenoiseHistorySave +linenoiseHistoryLoad diff --git a/src/rusti/rusti.rc b/src/rusti/rusti.rc new file mode 100644 index 00000000000..ac5edd4b48c --- /dev/null +++ b/src/rusti/rusti.rc @@ -0,0 +1,33 @@ +// rusti - REPL using the JIT backend + +#[link(name = "rusti", + vers = "0.4", + uuid = "7fb5bf52-7d45-4fee-8325-5ad3311149fc", + url = "https://github.com/mozilla/rust/tree/master/src/rusti")]; + +#[crate_type = "bin"]; + +#[no_core]; + +#[allow(vecs_implicitly_copyable, + non_implicitly_copyable_typarams)]; + +extern mod core(vers = "0.4"); +extern mod std(vers = "0.4"); +extern mod rustc(vers = "0.4"); +extern mod syntax(vers = "0.4"); + +use core::*; +use io::{ReaderUtil, WriterUtil}; +use rustc::back; +use rustc::driver::{driver, session}; +use rustc::front; +use rustc::lib::llvm::llvm; +use rustc::metadata::{creader, filesearch}; +use rustc::middle::{freevars, kind, lint, trans, ty, typeck}; +use rustc::middle; +use syntax::{ast, ast_util, codemap, diagnostic, fold, parse, print, visit}; +use syntax::ast_util::*; +use parse::token; +use print::{pp, pprust}; +use std::rl; diff --git a/src/rusti/rusti.rs b/src/rusti/rusti.rs new file mode 100644 index 00000000000..5f1cad3ae17 --- /dev/null +++ b/src/rusti/rusti.rs @@ -0,0 +1,325 @@ +/** + * A structure shared across REPL instances for storing history + * such as statements and view items. I wish the AST was sendable. + */ +struct Repl { + prompt: ~str, + binary: ~str, + running: bool, + view_items: ~str, + stmts: ~str +} + +/// A utility function that hands off a pretty printer to a callback. +fn with_pp(intr: @token::ident_interner, + cb: fn(pprust::ps, io::Writer)) -> ~str { + do io::with_str_writer |writer| { + let pp = pprust::rust_printer(writer, intr); + + cb(pp, writer); + pp::eof(pp.s); + } +} + +/** + * The AST (or the rest of rustc) are not sendable yet, + * so recorded things are printed to strings. A terrible hack that + * needs changes to rustc in order to be outed. This is unfortunately + * going to cause the REPL to regress in parser performance, + * because it has to parse the statements and view_items on each + * input. + */ +fn record(repl: Repl, blk: @ast::blk, intr: @token::ident_interner) -> Repl { + let view_items = if blk.node.view_items.len() > 0 { + let new_view_items = do with_pp(intr) |pp, writer| { + for blk.node.view_items.each |view_item| { + pprust::print_view_item(pp, *view_item); + writer.write_line(~""); + } + }; + + debug!("new view items %s", new_view_items); + + repl.view_items + "\n" + new_view_items + } else { repl.view_items }; + let stmts = if blk.node.stmts.len() > 0 { + let new_stmts = do with_pp(intr) |pp, writer| { + for blk.node.stmts.each |stmt| { + match stmt.node { + ast::stmt_decl(*) => { + pprust::print_stmt(pp, **stmt); + writer.write_line(~""); + } + ast::stmt_expr(expr, _) | ast::stmt_semi(expr, _) => { + match expr.node { + ast::expr_assign(*) | + ast::expr_assign_op(*) | + ast::expr_swap(*) => { + pprust::print_stmt(pp, **stmt); + writer.write_line(~""); + } + _ => {} + } + } + } + } + }; + + debug!("new stmts %s", new_stmts); + + repl.stmts + "\n" + new_stmts + } else { repl.stmts }; + + Repl{ + view_items: view_items, + stmts: stmts, + .. repl + } +} + +/// Run an input string in a Repl, returning the new Repl. +fn run(repl: Repl, input: ~str) -> Repl { + let options: @session::options = @{ + crate_type: session::unknown_crate, + binary: repl.binary, + addl_lib_search_paths: ~[os::getcwd()], + .. *session::basic_options() + }; + + debug!("building driver input"); + let head = include_str!("wrapper.rs"); + let foot = fmt!("%s\nfn main() {\n%s\n\nprint({\n%s\n})\n}", + repl.view_items, repl.stmts, input); + let wrapped = driver::str_input(head + foot); + + debug!("inputting %s", head + foot); + + debug!("building a driver session"); + let sess = driver::build_session(options, diagnostic::emit); + + debug!("building driver configuration"); + let cfg = driver::build_configuration(sess, + repl.binary, + wrapped); + + debug!("parsing"); + let mut crate = driver::parse_input(sess, cfg, wrapped); + let mut opt = None; + + for crate.node.module.items.each |item| { + match item.node { + ast::item_fn(_, _, _, blk) => { + if item.ident == sess.ident_of(~"main") { + opt = blk.node.expr; + } + } + _ => {} + } + } + + let blk = match opt.get().node { + ast::expr_call(_, exprs, _) => { + match exprs[0].node { + ast::expr_block(blk) => @blk, + _ => fail + } + } + _ => fail + }; + + debug!("configuration"); + crate = front::config::strip_unconfigured_items(crate); + + debug!("maybe building test harness"); + crate = front::test::modify_for_testing(sess, crate); + + debug!("expansion"); + crate = syntax::ext::expand::expand_crate(sess.parse_sess, + sess.opts.cfg, + crate); + + debug!("intrinsic injection"); + crate = front::intrinsic_inject::inject_intrinsic(sess, crate); + + debug!("core injection"); + crate = front::core_inject::maybe_inject_libcore_ref(sess, crate); + + debug!("building lint settings table"); + lint::build_settings_crate(sess, crate); + + debug!("ast indexing"); + let ast_map = syntax::ast_map::map_crate(sess.diagnostic(), *crate); + + debug!("external crate/lib resolution"); + creader::read_crates(sess.diagnostic(), *crate, sess.cstore, + sess.filesearch, + session::sess_os_to_meta_os(sess.targ_cfg.os), + sess.opts.static, sess.parse_sess.interner); + + debug!("language item collection"); + let lang_items = middle::lang_items::collect_language_items(crate, sess); + + debug!("resolution"); + let {def_map: def_map, + exp_map2: exp_map2, + trait_map: trait_map} = middle::resolve::resolve_crate(sess, + lang_items, + crate); + + debug!("freevar finding"); + let freevars = freevars::annotate_freevars(def_map, crate); + + debug!("region_resolution"); + let region_map = middle::region::resolve_crate(sess, def_map, crate); + + debug!("region paramaterization inference"); + let rp_set = middle::region::determine_rp_in_crate(sess, ast_map, + def_map, crate); + + debug!("typechecking"); + let ty_cx = ty::mk_ctxt(sess, def_map, ast_map, freevars, + region_map, rp_set, move lang_items, crate); + let (method_map, vtable_map) = typeck::check_crate(ty_cx, trait_map, + crate); + + debug!("const marking"); + middle::const_eval::process_crate(crate, def_map, ty_cx); + + debug!("const checking"); + middle::check_const::check_crate(sess, crate, ast_map, def_map, + method_map, ty_cx); + + debug!("privacy checking"); + middle::privacy::check_crate(ty_cx, &method_map, crate); + + debug!("loop checking"); + middle::check_loop::check_crate(ty_cx, crate); + + debug!("alt checking"); + middle::check_alt::check_crate(ty_cx, crate); + + debug!("liveness checking"); + let last_use_map = middle::liveness::check_crate(ty_cx, + method_map, crate); + + debug!("borrow checking"); + let (root_map, mutbl_map) = middle::borrowck::check_crate(ty_cx, + method_map, + last_use_map, + crate); + + debug!("kind checking"); + kind::check_crate(ty_cx, method_map, last_use_map, crate); + + debug!("lint checking"); + lint::check_crate(ty_cx, crate); + + let maps = {mutbl_map: mutbl_map, + root_map: root_map, + last_use_map: last_use_map, + method_map: method_map, + vtable_map: vtable_map}; + + debug!("translation"); + let (llmod, _) = trans::base::trans_crate(sess, crate, ty_cx, + ~path::from_str(""), + exp_map2, maps); + let pm = llvm::LLVMCreatePassManager(); + + debug!("executing jit"); + back::link::jit::exec(sess, pm, llmod, 0, false); + llvm::LLVMDisposePassManager(pm); + + debug!("recording input into repl history"); + record(repl, blk, sess.parse_sess.interner) +} + +/// Run a command, e.g. :clear, :exit, etc. +fn run_cmd(repl: &mut Repl, _in: io::Reader, _out: io::Writer, + cmd: ~str, _args: ~[~str]) { + match cmd { + ~"exit" => repl.running = false, + ~"clear" => { + repl.view_items = ~""; + repl.stmts = ~""; + + // FIXME: Win32 version of linenoise doesn't have the required function + //rl::clear(); + } + ~"help" => { + io::println(~":clear - clear the screen\n" + + ~":exit - exit from the repl\n" + + ~":help - show this message"); + } + _ => io::println(~"unknown cmd: " + cmd) + } +} + +fn main() { + let args = os::args(); + let in = io::stdin(); + let out = io::stdout(); + let mut repl = Repl { + prompt: ~"rusti> ", + binary: args[0], + running: true, + view_items: ~"", + stmts: ~"" + }; + + do rl::complete |line, suggest| { + if line.starts_with(":") { + suggest(~":clear"); + suggest(~":exit"); + suggest(~":help"); + } + } + + while repl.running { + let result = rl::read(repl.prompt); + + if result.is_none() { + break; + } + + let line = result.get(); + + if line.is_empty() { + io::println(~"()"); + + loop; + } + + rl::add_history(line); + + if line.starts_with(~":") { + let full = line.substr(1, line.len() - 1); + let split = full.split_char(' '); + let len = split.len(); + + if len > 0 { + let cmd = split[0]; + + if !cmd.is_empty() { + let args = if len > 1 { + do vec::view(split, 1, len - 1).map |arg| { + *arg + } + } else { ~[] }; + + run_cmd(&mut repl, in, out, cmd, args); + + loop; + } + } + } + + let result = do task::try |copy repl| { + run(copy repl, line) + }; + + if result.is_ok() { + repl = result.get(); + } + } +} diff --git a/src/rusti/wrapper.rs b/src/rusti/wrapper.rs new file mode 100644 index 00000000000..1e98aa81369 --- /dev/null +++ b/src/rusti/wrapper.rs @@ -0,0 +1,19 @@ +#[allow(ctypes)]; +#[allow(heap_memory)]; +#[allow(implicit_copies)]; +#[allow(managed_heap_memory)]; +#[allow(non_camel_case_types)]; +#[allow(non_implicitly_copyable_typarams)]; +#[allow(owned_heap_memory)]; +#[allow(path_statement)]; +#[allow(structural_records)]; +#[allow(unrecognized_lint)]; +#[allow(unused_imports)]; +#[allow(vecs_implicitly_copyable)]; +#[allow(while_true)]; + +extern mod std; + +fn print(result: T) { + io::println(fmt!("%?", result)); +}