diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..9e0d2d09 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,166 @@ +name: Build +on: push + +jobs: + build-internal: + timeout-minutes: 5 + name: Build + runs-on: ${{ matrix.os }} + strategy: + matrix: + name: [ + ubuntu-latest-gcc-6, + ubuntu-latest-gcc-7, + ubuntu-latest-gcc-8, + ubuntu-latest-gcc-9, + ubuntu-latest-gcc-10, + ubuntu-latest-clang-6.0, + ubuntu-latest-clang-7, + ubuntu-latest-clang-8, + ubuntu-latest-clang-9, + ubuntu-latest-clang-10, + macOS-10.14-xcode-10.3, + macOS-10.14-gcc-9, + ] + include: + - name: ubuntu-latest-gcc-6 + os: ubuntu-latest + compiler: gcc + version: "6" + + - name: ubuntu-latest-gcc-7 + os: ubuntu-latest + compiler: gcc + version: "7" + + - name: ubuntu-latest-gcc-8 + os: ubuntu-latest + compiler: gcc + version: "8" + + - name: ubuntu-latest-gcc-9 + os: ubuntu-latest + compiler: gcc + version: "9" + + - name: ubuntu-latest-gcc-10 + os: ubuntu-latest + compiler: gcc + version: "10" + + - name: ubuntu-latest-clang-6.0 + os: ubuntu-latest + compiler: clang + version: "6.0" + + - name: ubuntu-latest-clang-7 + os: ubuntu-latest + compiler: clang + version: "7" + + - name: ubuntu-latest-clang-8 + os: ubuntu-latest + compiler: clang + version: "8" + + - name: ubuntu-latest-clang-9 + os: ubuntu-latest + compiler: clang + version: "9" + + - name: ubuntu-latest-clang-10 + os: ubuntu-latest + compiler: clang + version: "10" + + - name: macOS-10.14-xcode-10.3 + os: macOS-10.14 + compiler: xcode + version: "10.3" + + - name: macOS-10.14-gcc-9 + os: macOS-10.14 + compiler: gcc + version: "9" + steps: + - uses: actions/checkout@v1 + - name: Install Compiler (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + if [ "${{ matrix.compiler }}" = "gcc" ]; then + sudo apt-get install -y g++-${{ matrix.version }} + echo "::set-env name=CC::gcc-${{ matrix.version }}" + echo "::set-env name=CXX::g++-${{ matrix.version }}" + else + sudo apt-get install -y clang-${{ matrix.version }} + echo "::set-env name=CC::clang-${{ matrix.version }}" + echo "::set-env name=CXX::clang++-${{ matrix.version }}" + fi + - name: Install and Test (Linux) + if: runner.os == 'Linux' + run: (sudo ./install-linux.sh && make tests) + - name: Install Compiler (macOS) + if: runner.os == 'macOS' + run: | + brew install cmake ninja + if [ "${{ matrix.compiler }}" = "gcc" ]; then + brew install gcc@${{ matrix.version }} + echo "::set-env name=CC::gcc-${{ matrix.version }}" + echo "::set-env name=CXX::g++-${{ matrix.version }}" + else + sudo xcode-select -switch /Applications/Xcode_${{ matrix.version }}.app + echo "::set-env name=CC::clang" + echo "::set-env name=CXX::clang++" + fi + - name: Install and Test (macOS) + if: runner.os == 'macOS' + run: (./install-mac.sh && make tests) + + build-external: + timeout-minutes: 5 + name: Build External + runs-on: ubuntu-latest + container: + image: ${{ matrix.container }} + options: --user root + strategy: + matrix: + name: [ + debian-stable-gcc-7, + debian-stable-gcc-8, + debian-stable-clang-7, + ] + include: + - name: debian-stable-gcc-7 + container: debian:stable + compiler: gcc + version: "7" + + - name: debian-stable-gcc-8 + container: debian:stable + compiler: gcc + version: "8" + + - name: debian-stable-clang-7 + container: debian:stable + compiler: clang + version: "7" + + steps: + - uses: actions/checkout@v1 + - name: Install Compiler + run: | + apt-get update + apt-get install -y sudo # sudo is not present on the image + if [ "${{ matrix.compiler }}" = "gcc" ]; then + apt-get install -y g++-${{ matrix.version }} + echo "::set-env name=CC::gcc-${{ matrix.version }}" + echo "::set-env name=CXX::g++-${{ matrix.version }}" + else + apt-get install -y clang-${{ matrix.version }} + echo "::set-env name=CC::clang-${{ matrix.version }}" + echo "::set-env name=CXX::clang++-${{ matrix.version }}" + fi + - name: Install and Test + run: (./install-linux.sh && make tests) \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 940ec193..00000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -dist: trusty -language: c -services: - - docker -compiler: - - gcc - - clang -before_script: - - sudo apt-get update -script: - - (./install-linux.sh && make tests) -after_failure: - - cat build/Testing/Temporary/LastTest.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bea04b..d4641b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## 2020-10-04 Version 0.2.9 + +### New features + +* Homebrew formula and readme instructions (Thanks, @SeekingMeaning) +* Homebrew badge (Thanks, @organom) +* `main()` can now be defined as part of `Namespace` of the main file: `ns { F main(...) ...}` +* Add experimental `only(val, predicate, mapper)` +* Add `realpath(Str)` +* Use `NGS_PATH` environment variable for `require()`. `NGS_DIR` is deprecated. + +### Fixes and improvements + +* Github actions instead of Travis (Thanks, @organom) +* `$(log ...)` now logs i/o redirections +* Remove unused `ValueWrapper` type +* Bootstrapping - `MY_NAMESPACE::main` works even if `main` is not defined in the main file, allowing `ngs -e 'require("my_module.ngs")` to run its own `main()` +* `filterk()`, `rejectk()`, `filterv()`, `rejectv()` - the predicate is now optional and defaults to `identity`. +* Got rid of `xxd` build time dependency +* make `sys/poll.h` dependency optional + + +### Breaking changes + +* Remove deprecated `n()` +* `switch` and `cond` are now consistent with if, accepting `{...}` code blocks for the LHS (`switch {a=1; a+a} {...}`, `cond { {a=b+c; a>0 } ... }`). + ## 2020-07-26 Version 0.2.8 ### New features diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ca242a0..9e0a2fd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,11 @@ cmake_minimum_required(VERSION 3.0) -include(FindPkgConfig) project(NGS) +include(CheckFunctionExists) +include(CheckIncludeFile) +include(FindPkgConfig) + + # -D_DARWIN_C_SOURCE - SIGWINCH and friends on MacOS # -D_XOPEN_SOURCE - strptime on Linux # -D_DEFAULT_SOURCE - MAP_ANONYMOUS on Linux @@ -16,14 +20,31 @@ pkg_check_modules(LIBFFI libffi REQUIRED) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${LIBFFI_INCLUDE_DIRS} /usr/local/include) link_directories(/usr/local/lib) -add_executable(ngs version.h ngs.c syntax.include syntax.auto.h pcre_constants.include errno.include obj.c vm.c compile.c debug.c ast.c malloc.c decompile.c) +add_executable(ngs + version.h + ngs.c + syntax.include syntax.auto.h + pcre_constants.include errno.include + obj.c vm.c compile.c debug.c ast.c malloc.c decompile.c + stdlib.ngs.h +) -include(CheckFunctionExists) check_function_exists(fmemopen FMEMOPEN) IF(NOT FMEMOPEN) target_sources(ngs PRIVATE fmemopen.c) ENDIF() +check_include_file(execinfo.h EXECINFO_H) +IF(EXECINFO_H) + add_definitions(-DHAVE_EXECINFO_H) +ENDIF() + +check_include_file(sys/poll.h POLL_H) +IF(POLL_H) + add_definitions(-DHAVE_POLL_H) +ENDIF() + + find_program(SED NAMES gsed sed) # gsed - MacOS, sed - all the rest find_file(PCRE_H pcre.h) add_custom_command( @@ -60,6 +81,13 @@ add_custom_command( DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/build-scripts/include_errno.c ) +add_custom_command( + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/stdlib.ngs.h + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build-scripts/bin2c.sh lib/stdlib.ngs >${CMAKE_CURRENT_BINARY_DIR}/stdlib.ngs.h + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/lib/stdlib.ngs +) + target_link_libraries(ngs m pthread gc ffi dl json-c pcre) @@ -78,4 +106,4 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE DESTINATION doc/ngs) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/doc/ DESTINATION man/man1 FILES_MATCHING PATTERN "*.1") enable_testing() -add_test(all bash -c "NGS_BOOTSTRAP=${CMAKE_CURRENT_SOURCE_DIR}/lib/bootstrap.ngs NGS_DIR=${CMAKE_CURRENT_SOURCE_DIR}/lib ${CMAKE_CURRENT_BINARY_DIR}/ngs ${CMAKE_CURRENT_SOURCE_DIR}/test.ngs") +add_test(all bash -c "${CMAKE_CURRENT_BINARY_DIR}/ngs ${CMAKE_CURRENT_SOURCE_DIR}/test.ngs") diff --git a/Dockerfile b/Dockerfile index 109cc27d..01dc0362 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,6 @@ WORKDIR /root/ COPY --from=0 /usr/local/lib/ngs /usr/local/lib/ngs COPY --from=0 /usr/local/bin/ngs /usr/local/bin/ngs COPY --from=0 /src/test.ngs /root/test.ngs -RUN ngs test.ngs +RUN env NGS_TESTS_BASE_DIR=/usr/local/lib/ngs ngs test.ngs CMD ["/bin/bash"] diff --git a/bin/ec2din.ngs b/bin/ec2din.ngs index 8dcead8d..5ff0e803 100755 --- a/bin/ec2din.ngs +++ b/bin/ec2din.ngs @@ -30,7 +30,7 @@ F main(filters:Arr, allreg:Bool=false, j:Bool=false, c:Str=null, out_lines:Int=5 local_filters = AllOf() filters = ['--filters'] +? collector filters.each(F(arg) { - kv = arg.split('=') + econd { arg == 'windows' collect("Name=platform,Values=windows") @@ -58,7 +58,7 @@ F main(filters:Arr, allreg:Bool=false, j:Bool=false, c:Str=null, out_lines:Int=5 arg in AWS::INSTANCE_STATES collect("Name=instance-state-name,Values=$arg") - kv.len() == 2 collect("Name=tag:${kv[0]},Values=${kv[1]}") + {kv = arg.split('='); kv.len() == 2} collect("Name=tag:${kv[0]},Values=${kv[1]}") } }) diff --git a/build-scripts/bin2c.sh b/build-scripts/bin2c.sh new file mode 100755 index 00000000..1020985c --- /dev/null +++ b/build-scripts/bin2c.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Contributed by Steven Penny, https://github.com/nu8 +# Cosmetical changes and minor fixes by Ilya Sher + +file_name=$1 +var_name=$(tr /. __ <' { + pfx.val / 'stdlib.ngs' + } else { + location_info.file + } + if m = f ~ pfx { { - 'file': m.after + 'file': m.after[1..null] 'line': location_info.first_line } } diff --git a/doc/make_methods.ngs b/doc/make_methods.ngs deleted file mode 100755 index 6155dcca..00000000 --- a/doc/make_methods.ngs +++ /dev/null @@ -1,584 +0,0 @@ -#!/usr/bin/env ngs -{ - - # TODO: Get rid of global state (tc = TextCollector) - - # Autoload - AWS - ArgvMatcher - DelimStr - Iter - KV - Lock - Res - ResDef - Stats - Table - Thread - test - - # Use anonymous function to exclude methods in this file - # The inner functions are local - ns { - - global Arr, Str, init, push, each - - mode = ARGV[0] - assert(mode == 'methods' or mode == 'types') - - type TextCollector - - F init(t:TextCollector, pfx='', pfx_inc='\t', heading_level=1) { - init(args()) - t.pfx_stack = [] - t.acc = [] - } - - F push(t:TextCollector, s:Str) t.acc.push(t.pfx + s) - - F indent(t:TextCollector) { - t.pfx_stack.push(t.pfx) - t.pfx += t.pfx_inc - } - - F indent(t:TextCollector, body:Fun) { - t.indent() - body() - t.outdent() - } - - F outdent(t:TextCollector) { - t.pfx = t.pfx_stack.pop() - } - - F heading_inc(t:TextCollector) t.heading_level+=1 - F heading_dec(t:TextCollector) t.heading_level-=1 - - F heading_inc(t:TextCollector, body:Fun) { - t.heading_inc() - body() - t.heading_dec() - } - - - F Arr(t:TextCollector) t.acc - F Str(t:TextCollector) t.acc.join('\n') - F each(t:TextCollector, *args) t.acc.each(*args) - - tc = TextCollector() - - # --- DocNode --- - - type DocNode - type TextDocNode(DocNode) - type TopDocNode(DocNode) - type TypesDocNode(DocNode) - type TypeDocNode(DocNode) - type TypeFieldsDocNode(DocNode) - type TypeFieldDocNode(TextDocNode) - type TypeMethodsDocNode(DocNode) - type TypeMethodDocNode(DocNode) - type TypeMethodImplDocNode(DocNode) - type TypeConstructorsDocNode(DocNode) - type ParentTypesDocNode(DocNode) - type ChildTypesDocNode(DocNode) - type ParentTypeDocNode(DocNode) - type MethodsDocNode(DocNode) - type MethodDocNode(DocNode) - type MethodImplDocNode(DocNode) - type DescDocNode(TextDocNode) - type ParamsDocNode(DocNode) - type ParamDocNode(DocNode) - type ParamDfltDocNode(TextDocNode) - type ParamSpecialDfltDocNode(TextDocNode) - type RetDocNode(TextDocNode) - type ExampleDocNode(TextDocNode) - type LocationDocNode(DocNode) - - F init(dn:DocNode, children:Arr=null) { - if not(children) children = [] - init(args()) - } - - F init(tdn:TextDocNode, text:Arr) init(args()) - F init(tdn:TextDocNode, text:Str) super(tdn, [text]) - - F push(dn:DocNode, child:DocNode) dn.children.push(child) - - F process_children(dn:DocNode, options:Hash, filt={true}) { - tc.indent({ dn.children.filter(filt).Html(options) }) - } - - F encode_id(s:Str) s.replace(/[^-_a-zA-Z0-9]/, F(char) '%' + encode_hex(char)) - - # --- Html --- - - # Method permalink points to the index page - F method_permalink(s:Str) '' - - # Note that id is not unique! There can be several implementations with same parameters types - F method_impl_id(name:Str, mi:Fun) (name + '-' + mi.params().type.name.join('-')).encode_id() - - # Method implementation permalink points to the index page - F method_impl_permalink(name:Str, mi:Fun) { - id = method_impl_id(name, mi) - '' + name.encode_html() + '' - } - - F type_permalink(t:Type) '' - F type_details_link(t:Type) '' + t.name.encode_html() + '' - - F method_ref_link(display_name:Str, link_data:Str) '' + display_name + '' - - commit = `git rev-parse HEAD`.lines()[0] - # https://github.com/ngs-lang/ngs/blob/00e547caf492e3ac42d2aca8ef32a28f302c7287/c/stdlib.ngs#L3197 - F make_location_link(dn:LocationDocNode) { - { - 'text': "${dn.file}:${dn.line}" - 'link': "https://github.com/ngs-lang/ngs/blob/$commit/lib/${dn.file}#L${dn.line}" - } - } - - - F Html(a:Arr, options:Hash) a.each(Html(X, options)) - - F Html(dn:TopDocNode, options:Hash={'pfx': '', 'do_pfx': true}) { - tc.push('') - tc.push('') - tc.push('') - tc.push('') - tc.push('') - tc.push('') - tc.push('') - dn.process_children(options) - tc.push('') - tc.push('') - tc - } - - F Html(dn:MethodsDocNode, options:Hash) { - tc.push("") - tc.push("Methods") - tc.heading_inc() do { - dn.process_children(options) - } - tc.push("") - } - - F Html(dn:TypeMethodsDocNode, options:Hash) { - not(dn.children) returns null - tc.push("") - tc.push("Methods") - tc.heading_inc() do { - dn.process_children(options) - } - tc.push("") - } - - F Html(dn:TypeConstructorsDocNode, options:Hash) { - not(dn.children) returns null - tc.push("") - tc.push("Constructors") - tc.heading_inc() do { - dn.process_children(options + {'method': dn.method}, TypeMethodImplDocNode) - } - tc.push("") - } - - F Html(dn:TypesDocNode, options:Hash) { - tc.push("") - tc.push("Types") - tc.heading_inc() do { - dn.process_children(options) - } - tc.push("") - } - - F Html(dn:MethodDocNode, options:Hash) { - not(dn.children.filter(MethodImplDocNode)) returns null - tc.push('
') - link = method_permalink(dn.name) - tc.push("${dn.name.encode_html()}$link") - tc.heading_inc() do { - dn.process_children(options + {'method': dn.name}, MethodImplDocNode) - } - tc.push('
') - } - - F Html(dn:TypeMethodDocNode, options:Hash) { - dn.process_children(options + {'method': dn.method}, TypeMethodImplDocNode) - } - - F Html(dn:TypeFieldsDocNode, options:Hash) { - not(dn.children) returns null - tc.push("Fields") - tc.push('
') - dn.process_children(options) - tc.push('
') # type-field-content - } - - F Html(dn:TypeFieldDocNode, options:Hash) { - tc.push("${dn.name.encode_html()}") - tc.push('
') - dn.text / encode_html % tc.push(X) - tc.push('
') # type-field-content - } - - F Html(dn:ParentTypesDocNode, options:Hash) { - not(dn.children) returns null - tc.push("Parent types") - tc.push("") - } - - F Html(dn:ChildTypesDocNode, options:Hash) { - not(dn.children) returns null - tc.push("Direct children types") - tc.push("") - } - - F Html(dn:TypeDocNode, options:Hash) { - tc.push('
') - tc.indent() do { - type_name = dn.type.name - if options.get('detailed_type_page') { - tc.push("${dn.type.name.encode_html()}") - } else { - tc.push("${type_details_link(dn.type)}") - } - tc.push('
') - tc.indent() do { - tc.heading_inc() do { - dn.children.the_one(DescDocNode) do F(dn) { - dn.text / encode_html % tc.push(X) - } - if options.get('detailed_type_page') { - dn.process_children(options, TypeFieldsDocNode) - dn.children.the_one(ParentTypesDocNode).Html(options) - dn.children.the_one(ChildTypesDocNode).Html(options) - dn.children.the_one(ExampleDocNode) do F(dn) { - tc.push("Example") - tc.push('
') - tc.push('
' + dn.text.map(encode_html).join('\n') + '
') - tc.push('
') # type-example-content - } - dn.process_children(options, TypeConstructorsDocNode) - dn.process_children(options, TypeMethodsDocNode) - } - } - } - tc.push('
') - } - tc.push('
') - } - - F Html(dn:TypeDocNode, options:Hash) { - guard options.get('leaf') - tc.indent() do { - tc.push("
  • ${type_details_link(dn.type)}") - tc.indent() do { - tc.heading_inc() do { - dn.children.the_one(DescDocNode) do F(dn) { - tc.push(" - ") - dn.text / encode_html % tc.push(X) - } - } - } - tc.push("
  • ") - } - } - - F HtmlParams(dn:DocNode) { - params_dn = dn.children.the_one(ParamsDocNode) - # echo(pdn) - params = params_dn.children.map() with F(param_dn) { - html = '' - if 'splat' in param_dn { - html += param_dn.splat - } - html += param_dn.name.encode_html() - html += ':' - html += type_details_link(param_dn.type) - param_dn.children.the_one(ParamDfltDocNode) do F(dflt) { - html += "=${dflt.text[0].encode_html()}" - } - param_dn.children.the_one(ParamSpecialDfltDocNode) do F(dflt) { - html += "=${dflt.text[0].encode_html()}" - } - html - } - params.join(', ') - } - - F Html(dn:MethodImplDocNode, options) { - tc.push('
    ') - link = method_impl_permalink(options.method, dn.mi) - - if options.get('detailed_method_page') { - tc.push("${options.method.encode_html()}(${HtmlParams(dn)})") - } else { - tc.push("$link(${HtmlParams(dn)})") - } - - tc.indent() do { - tc.push('
    ') - tc.heading_inc() do { - dn.children.the_one(DescDocNode) do F(dn) { - dn.text / encode_html % tc.push(X) - } - if options.get('detailed_method_page') { - dn.children.the_one(LocationDocNode) do F(dn) { - l = make_location_link(dn) - tc.push("
    Source: ${l.text.encode_html()}
    ") - } - dn.children.the_one(ParamsDocNode).children.each(F(param_dn:ParamDocNode) { - if param_dn.children.any(DescDocNode) { - tc.push("${param_dn.name.encode_html()}") - tc.push('
    ') - param_dn.children.the_one(DescDocNode).text / encode_html % tc.push(X) - tc.push('
    ') # method-param-content - } - }) - dn.children.the_one(RetDocNode) do F(dn) { - tc.push("Returns") - tc.push('
    ') - tc.push(dn.text.map(encode_html).join('\n')) - tc.push('
    ') # method-example-content - } - dn.children.the_one(ExampleDocNode) do F(dn) { - tc.push("Example") - tc.push('
    ') - tc.push('
    ' + dn.text.map(encode_html).join('\n') + '
    ') - tc.push('
    ') # method-example-content - } - } - tc.push('
    ') # method-impl-content - } - } - tc.push('
    ') # method-impl - } - - F Html(dn:TypeMethodImplDocNode, options) { - tc.push('
    ') - tc.push(method_impl_permalink(options.method, dn.mi) + "(${HtmlParams(dn)})") - dn.children.the_one(DescDocNode) do F(dn) { - tc.push('
    ') - dn.text / encode_html % tc.push(X) - tc.push('
    ') # type-method-impl - } - tc.push('
    ') # type-method-impl - } - - F is_public_name(s:Str) s ~ /^[^_]/ - F is_phased_out_type(s:Str) s ~ Pfx('Aws') - - F to_lines(s:Str) [s] - F to_lines(a:Arr) a - - F list_methods() - collector/{} - globals().filterk(is_public_name).sortk(lte).each(F(name, value) { - cond { - (value is Arr) and value.all(Fun) { - collect(name, value) - } - value is NormalType { - # NormalType has special constructor which we can't handle for now - # so skipping that one - collect(name, value.constructors[1..null]) - } - value is Type { - collect(name, value.constructors) - } - } - }) - - F process_param(param:Hash, doc:Hash) { - # echo("P $param") - dn = ParamDocNode() - dn.name = param.name - dn.type = param.get('type', Any) - if 'dflt' in param { - dn.push() do - ematch param.dflt { - Fun ParamSpecialDfltDocNode('(a function)') - Any ParamDfltDocNode(param.dflt.Str()) - } - } - if 'splat' in param { - dn.splat = param.splat - } - if param.name in doc { - dn.push(DescDocNode(doc[param.name])) - } - dn - } - - F process_method_impl_doc_attr(mi:Fun, dn:DocNode, doc_attr:Str, doc_node_type:Type) { - d = mi.attrs().doc or {} - if doc_attr in d { - dn.push(doc_node_type(d[doc_attr].to_lines())) - } - } - - F process_type_impl_doc_attr(t:Type, dn:DocNode, doc_attr:Str, doc_node_type:Type) { - a = t.attrs() - 'doc' not in a returns null - d = t.attrs().doc or {} - if doc_attr in d { - dn.push(doc_node_type(d[doc_attr].to_lines())) - } - } - - method_impls_by_id = {} - - pfx = Pfx("$NGS_DIR/") - F process_method_impl(name:Str, mi:Fun) { - method_impls_by_id.dflt(method_impl_id(name, mi), [name]).push(mi) - - dn = MethodImplDocNode([]) - dn.mi = mi - process_method_impl_doc_attr(mi, dn, '', DescDocNode) - dn.push(ParamsDocNode(mi.params().map(process_param(X, mi.attrs().doc or {})))) - process_method_impl_doc_attr(mi, dn, '%EX', ExampleDocNode) - process_method_impl_doc_attr(mi, dn, '%RET', RetDocNode) - - if mi is UserDefinedMethod { - # Handling UserDefinedMethod, not handling built-ins yet - location_info = mi.ip().resolve_instruction_pointer() - if m = location_info.file ~ pfx { - dn.push(LocationDocNode().set('file', m.after).set('line', location_info.first_line)) - } - } - dn - } - - F process_method(name, value) { - dn = MethodDocNode(value.map(process_method_impl(name, X))) - dn.name = name - dn - } - - eswitch mode { - - 'methods' { - - # Index page - page = TopDocNode([ MethodsDocNode(list_methods() / process_method) ]) - - # Detailed method implementations pages - method_impls_by_id.each(F(id, impls) { - assert('/' not in id) - assert('.' not in id) - tc = TextCollector() - local mdn = MethodsDocNode([process_method(impls[0], impls[1..null])]) - local page = TopDocNode([mdn]) - local html = Html(page, {'detailed_method_page': true}) - html.Str().write("method-impl-${id}.html") - }) - - } - - 'types' { - - # TODO: methods with this type - # TODO: constructors - # TODO: child types - - # Splat params will be Arr or Hash but they are not interesting - F method_impl_has_arg_of_type(mi:Fun, t:Type) mi.params().reject(X.get('splat')).type.has(t) - - # We'll use it to list methods that have given - # type as type of one of the parameters. - methods = list_methods() - - types = globals().filterv(Type).filterk(is_public_name).filterk(not + is_phased_out_type).sortk(lte) - - # Init with ( type -> [] ) pairs - types_direct_children = types.values().Hash({ [] }) - - types.values().each(F(t) { - t.parents.each(F(p) { - types_direct_children[p].push(t) - }) - }) - - - # TODO: straighten by using ParentTypeDocNode instead of 'full' / 'leaf' option. - F type_to_doc_node(value:Type, full:Bool) { - # echo("N $name") - # ret = TypeDocNode([ParentTypesDocNode(value.parents.map())]) - ret = TypeDocNode() - ret.type = value - # ret.full = full - process_type_impl_doc_attr(value, ret, '', DescDocNode) - type_attrs_dns = ((try value.attrs().doc) or {}).without('').reject(X ~ /^%/).map(F(k, v) { - TypeFieldDocNode(v.to_lines()).set('name', k) - }) - ret.push(TypeFieldsDocNode(type_attrs_dns)) - - if full { - process_type_impl_doc_attr(value, ret, '%EX', ExampleDocNode) - ret.push(ParentTypesDocNode(value.parents.map(type_to_doc_node(X, false)))) - ret.push(ChildTypesDocNode(types_direct_children[value].map(type_to_doc_node(X, false)))) - methods_of_type_dns = collector - methods.each() do F(name, impls) { - relevant_impls = impls.filter(method_impl_has_arg_of_type(X, value)).map() do F(mi) { - dn = TypeMethodImplDocNode([]) - dn.mi = mi - process_method_impl_doc_attr(mi, dn, '', DescDocNode) - dn.push(ParamsDocNode(mi.params().map(process_param(X, mi.attrs().doc or {})))) - dn - } - if relevant_impls { - dn = TypeMethodDocNode(relevant_impls) - dn.method = name - collect(dn) - } - } - constructors_dns = value.constructors.reject(NormalTypeConstructor).map() do F(c) { - dn = TypeMethodImplDocNode([]) - # dn.method = value.name - dn.mi = c - process_method_impl_doc_attr(c, dn, '', DescDocNode) - dn.push(ParamsDocNode(c.params().map(process_param(X, c.attrs().doc or {})))) - dn - } - ret.push(TypeMethodsDocNode(methods_of_type_dns)) - ret.push(TypeConstructorsDocNode(constructors_dns).set('method', value.name)) - } - ret - } - - types .= map(F(name, value) { - type_to_doc_node(value, true) - }) - - # Index page - tdn = TypesDocNode(types) - page = TopDocNode([tdn]) - - # Detailed types pages - types.each(F(t) { - assert('/' not in t.type.name) - assert('.' not in t.type.name) - tc = TextCollector() - local tdn = TypesDocNode([t]) - local page = TopDocNode([tdn]) - local html = Html(page, {'detailed_type_page': true}) - html.Str().write("type-${t.type.name}.html") - }) - } - - } - - tc = TextCollector() - echo(Html(page)) - - # echo(methods.children[-100]) - } -} - diff --git a/doc/ngs.1.md b/doc/ngs.1.md index 91986ba5..187d76b3 100644 --- a/doc/ngs.1.md +++ b/doc/ngs.1.md @@ -56,30 +56,18 @@ In case of an uncaught exception, the exit code is 240. In cases where additiona # ENVIRONMENT * `DEBUG` - when non-empty string, switches on `debug` method output (default is off). It's recommended to use `debug("my debug info")` in your scripts for turning debug output on and off easily, using the `DEBUG` environment variable. -* `NGS_BOOTSTRAP` points to the bootstrap NGS file. On NGS startup this file is always run. Defaults to first of: - * `$HOME/.bootstrap.ngs` - * `/etc/ngs/bootstrap.ngs` - * `/var/lib/ngs/bootstrap.ngs` - * `/usr/share/ngs/bootstrap.ngs` +* `NGS_BOOTSTRAP` points to the bootstrap NGS file. On NGS startup this file is always run. Defaults to built-in stdlib with bootsrapper. * `NGS_BOOTSTRAP_DEBUG` - if defined, show **bootstrap.ngs** debugging information. -* `NGS_DIR` (defaults to `/usr/share/ngs`) - location of **stdlib.ngs** file and the **autoload** directory. Files are automatically loaded from this directory when an undefined global variable is used. +* `NGS_PATH` (defaults to `$HOME/.ngs:/usr/local/etc/ngs:/usr/local/lib/ngs:/etc/ngs:/usr/lib/ngs`) - location of the **autoload** directory. Files are automatically loaded from this directory when an undefined global variable is used. * `NGS_EXIT_BACKTRACE` - print backtrace when `exit()` is called. * `NGS_WARN_BACKTRACE` - print backtrace when `warn()` is called. Prints only unique backtraces. * `NGS_ERROR_BACKTRACE` - print backtrace when `error()` is called. Prints only unique backtraces. # FILES -## bootstrap.ngs - -Typically located in `NGS_DIR`. Responsible for bootstrapping NGS. - -* Loads **stdlib.ngs** if needed. -* Runs the script specified in the command line or exectes *expression* according to swithces. -* Prints *expression* value if needed. - ## stdlib.ngs -Located in `NGS_DIR`. Standard library. Defines many methods and autoloading behaviour. +Packaged into `ngs` binary during build. Standard library. Defines many methods and autoloading behaviour. # SEE ALSO diff --git a/doc/ngslang.1.md b/doc/ngslang.1.md index 4153b931..edc62b7e 100644 --- a/doc/ngslang.1.md +++ b/doc/ngslang.1.md @@ -1704,11 +1704,7 @@ Handlers are called by NGS when a certain condition occurs. ## global_not_found_handler -`global_not_found_handler` is called on attempt to read from an undefined global variable. Sample usage from **stdlib.ngs** - - F global_not_found_handler(name:Str) { - require("${NGS_DIR}/autoload/${name}.ngs") - } +`global_not_found_handler` is called on attempt to read from an undefined global variable. Uses `NGS_PATH` when looking up the appropriate file. # Hooks diff --git a/lib/autoload/Lines.ngs b/lib/autoload/Lines.ngs deleted file mode 100644 index 32b3c08b..00000000 --- a/lib/autoload/Lines.ngs +++ /dev/null @@ -1,47 +0,0 @@ -doc Array of strings which comprise a text which is treated as a -doc unit for purposes of output (and maybe for other purposes later). -type Lines(ArrLike) - -doc Split s to strings using end-of-line separators. -doc UNIX and Windows line endings supported (Windows - not tested yet). -doc Warning: Max OS <= 9 line separation of CR (\r) is not supported -doc %EX - "xx\nyy".lines() # %[xx yy] -F init(lines:Lines, s:Str) { - _lines = if s { - s -= MaybeSfx('\n') - s.split('\n').map(X - MaybeSfx('\r')) - } else { - [] - } - lines.init(_lines) -} - -doc Echo all lines using echo(Str) -F echo(lines:Lines) lines.each(echo) - -doc Echo all lines to dst -F echo(dst, lines:Lines) { - lines.each(echo(dst, X)) - dst -} - -doc Write all lines to standard error using warn(Str) -F warn(lines:Lines) lines.each(warn) - -doc Write all lines to standard error using error(Str) -F error(lines:Lines) lines.each(error) - -# Later this should be a widget which can be displayed -# with ASCII art in console or as a box in a browser. -# WARNING: only works when one byte is one character. -doc %STATUS - experimental -F framed(lines:Lines) { - not(lines) returns lines - m = lines.map(len).max() - border = '+' + ('-' * (m+2)) + '+' - collector/Lines() { - collect(border) - lines.each(F(x) collect("| ${x.Str(m)} |")) - collect(border) - } -} diff --git a/lib/autoload/ItermTerminal.ngs b/lib/autoload/constructors/AnsiTerminal/ItermTerminal.ngs similarity index 51% rename from lib/autoload/ItermTerminal.ngs rename to lib/autoload/constructors/AnsiTerminal/ItermTerminal.ngs index 318e507e..5b002686 100644 --- a/lib/autoload/ItermTerminal.ngs +++ b/lib/autoload/constructors/AnsiTerminal/ItermTerminal.ngs @@ -1,8 +1,3 @@ -{ - doc %STATUS - experimental - type ItermTerminal(AnsiTerminal) -} - # iTerm2 detection is based on https://iterm2.com/utilities/it2check F AnsiTerminal(fd_in:Int, fd_out:Int) { guard 'NGS_NO_ITERM' not in ENV @@ -35,33 +30,3 @@ F AnsiTerminal(fd_in:Int, fd_out:Int) { parts = version_string.split(' ') ItermTerminal(fd_in, fd_out).set('term', parts[0]).set('ver', parts[1]) } - -F init(it:ItermTerminal, *args) { - super(it, *args) - it._status_set_badge_format_done = false - -} - -F write_iterm_csi(it:ItermTerminal) it.write("\e]1337;") -F write_iterm_csi(it:ItermTerminal, s:Str) it.write_iterm_csi().write(s) -F write_iterm_cmd(it:ItermTerminal, s:Str) it.write_iterm_csi(s).write_st() - -# https://www.iterm2.com/documentation-badges.html -F set_badge_format(it:ItermTerminal, fmt:Str) it.write_iterm_cmd("SetBadgeFormat=${fmt.encode_base64()}") - -F set_user_var(it:ItermTerminal, k:Str, v:Str) it.write_iterm_cmd("SetUserVar=${k}=${v.encode_base64()}") - -# TODO: Issue #205 - make status() configurable -F status(it:ItermTerminal, s:Str) { - if not(it._status_set_badge_format_done) { - it.set_badge_format("\\(user.status)") - it._status_set_badge_format_done = true - exit_hook["iterm_clear_status_badge:${it.fd_in}:${it.fd_out}"] = { - it.set_badge_format("") - } - } - it.set_user_var('status', s) -} - -# TODO: https://iterm2.com/documentation-escape-codes.html - diff --git a/lib/autoload/constructors/Terminal/AnsiTerminal.ngs b/lib/autoload/constructors/Terminal/AnsiTerminal.ngs new file mode 100644 index 00000000..98f0e788 --- /dev/null +++ b/lib/autoload/constructors/Terminal/AnsiTerminal.ngs @@ -0,0 +1,7 @@ +F Terminal(fd_in:Int, fd_out:Int) { + guard isatty(fd_in) and isatty(fd_out) + debug("term", "Terminal() choose AnsiTerminal()") + k = "${fd_in}:${fd_out}" + k in STDLIB_TERMINALS returns STDLIB_TERMINALS[k] + STDLIB_TERMINALS[k] = AnsiTerminal(fd_in, fd_out) +} diff --git a/lib/autoload/AWS.ngs b/lib/autoload/globals/AWS.ngs similarity index 100% rename from lib/autoload/AWS.ngs rename to lib/autoload/globals/AWS.ngs diff --git a/lib/autoload/AWS2.ngs b/lib/autoload/globals/AWS2.ngs similarity index 100% rename from lib/autoload/AWS2.ngs rename to lib/autoload/globals/AWS2.ngs diff --git a/lib/autoload/AnsiTerminal.ngs b/lib/autoload/globals/AnsiTerminal.ngs similarity index 88% rename from lib/autoload/AnsiTerminal.ngs rename to lib/autoload/globals/AnsiTerminal.ngs index 1c1a180e..ed4bf7c1 100644 --- a/lib/autoload/AnsiTerminal.ngs +++ b/lib/autoload/globals/AnsiTerminal.ngs @@ -45,8 +45,4 @@ F status(at:AnsiTerminal, s:Str) { null } -{ - # Load possible subtypes, which define AnsiTerminal with appropriate guards - # so that AnsiTerminal returns the most appropriate subtype of AnsiTerminal - ItermTerminal -} +require(Dir("autoload/constructors/AnsiTerminal")) diff --git a/lib/autoload/ArgvMatcher.ngs b/lib/autoload/globals/ArgvMatcher.ngs similarity index 100% rename from lib/autoload/ArgvMatcher.ngs rename to lib/autoload/globals/ArgvMatcher.ngs diff --git a/lib/autoload/BlockingList.ngs b/lib/autoload/globals/BlockingList.ngs similarity index 100% rename from lib/autoload/BlockingList.ngs rename to lib/autoload/globals/BlockingList.ngs diff --git a/lib/autoload/Cond.ngs b/lib/autoload/globals/Cond.ngs similarity index 100% rename from lib/autoload/Cond.ngs rename to lib/autoload/globals/Cond.ngs diff --git a/lib/autoload/DelimStr.ngs b/lib/autoload/globals/DelimStr.ngs similarity index 100% rename from lib/autoload/DelimStr.ngs rename to lib/autoload/globals/DelimStr.ngs diff --git a/lib/autoload/Doc.ngs b/lib/autoload/globals/Doc.ngs similarity index 100% rename from lib/autoload/Doc.ngs rename to lib/autoload/globals/Doc.ngs diff --git a/lib/autoload/Enum.ngs b/lib/autoload/globals/Enum.ngs similarity index 100% rename from lib/autoload/Enum.ngs rename to lib/autoload/globals/Enum.ngs diff --git a/lib/autoload/Executor.ngs b/lib/autoload/globals/Executor.ngs similarity index 100% rename from lib/autoload/Executor.ngs rename to lib/autoload/globals/Executor.ngs diff --git a/lib/autoload/ExpBackIter.ngs b/lib/autoload/globals/ExpBackIter.ngs similarity index 100% rename from lib/autoload/ExpBackIter.ngs rename to lib/autoload/globals/ExpBackIter.ngs diff --git a/lib/autoload/FFI.ngs b/lib/autoload/globals/FFI.ngs similarity index 100% rename from lib/autoload/FFI.ngs rename to lib/autoload/globals/FFI.ngs diff --git a/lib/autoload/IP.ngs b/lib/autoload/globals/IP.ngs similarity index 100% rename from lib/autoload/IP.ngs rename to lib/autoload/globals/IP.ngs diff --git a/lib/autoload/Iter.ngs b/lib/autoload/globals/Iter.ngs similarity index 100% rename from lib/autoload/Iter.ngs rename to lib/autoload/globals/Iter.ngs diff --git a/lib/autoload/globals/ItermTerminal.ngs b/lib/autoload/globals/ItermTerminal.ngs new file mode 100644 index 00000000..3157ca35 --- /dev/null +++ b/lib/autoload/globals/ItermTerminal.ngs @@ -0,0 +1,35 @@ +# iTerm2 detection is based on https://iterm2.com/utilities/it2check + +doc %STATUS - experimental +type ItermTerminal(AnsiTerminal) + + +F init(it:ItermTerminal, *args) { + super(it, *args) + it._status_set_badge_format_done = false + +} + +F write_iterm_csi(it:ItermTerminal) it.write("\e]1337;") +F write_iterm_csi(it:ItermTerminal, s:Str) it.write_iterm_csi().write(s) +F write_iterm_cmd(it:ItermTerminal, s:Str) it.write_iterm_csi(s).write_st() + +# https://www.iterm2.com/documentation-badges.html +F set_badge_format(it:ItermTerminal, fmt:Str) it.write_iterm_cmd("SetBadgeFormat=${fmt.encode_base64()}") + +F set_user_var(it:ItermTerminal, k:Str, v:Str) it.write_iterm_cmd("SetUserVar=${k}=${v.encode_base64()}") + +# TODO: Issue #205 - make status() configurable +F status(it:ItermTerminal, s:Str) { + if not(it._status_set_badge_format_done) { + it.set_badge_format("\\(user.status)") + it._status_set_badge_format_done = true + exit_hook["iterm_clear_status_badge:${it.fd_in}:${it.fd_out}"] = { + it.set_badge_format("") + } + } + it.set_user_var('status', s) +} + +# TODO: https://iterm2.com/documentation-escape-codes.html + diff --git a/lib/autoload/KV.ngs b/lib/autoload/globals/KV.ngs similarity index 100% rename from lib/autoload/KV.ngs rename to lib/autoload/globals/KV.ngs diff --git a/lib/autoload/List.ngs b/lib/autoload/globals/List.ngs similarity index 100% rename from lib/autoload/List.ngs rename to lib/autoload/globals/List.ngs diff --git a/lib/autoload/MaybeFile.ngs b/lib/autoload/globals/MaybeFile.ngs similarity index 100% rename from lib/autoload/MaybeFile.ngs rename to lib/autoload/globals/MaybeFile.ngs diff --git a/lib/autoload/MethodParam.ngs b/lib/autoload/globals/MethodParam.ngs similarity index 100% rename from lib/autoload/MethodParam.ngs rename to lib/autoload/globals/MethodParam.ngs diff --git a/lib/autoload/MethodParams.ngs b/lib/autoload/globals/MethodParams.ngs similarity index 100% rename from lib/autoload/MethodParams.ngs rename to lib/autoload/globals/MethodParams.ngs diff --git a/lib/autoload/NoTerminal.ngs b/lib/autoload/globals/NoTerminal.ngs similarity index 100% rename from lib/autoload/NoTerminal.ngs rename to lib/autoload/globals/NoTerminal.ngs diff --git a/lib/autoload/ProcessFail.ngs b/lib/autoload/globals/ProcessFail.ngs similarity index 100% rename from lib/autoload/ProcessFail.ngs rename to lib/autoload/globals/ProcessFail.ngs diff --git a/lib/autoload/Res.ngs b/lib/autoload/globals/Res.ngs similarity index 100% rename from lib/autoload/Res.ngs rename to lib/autoload/globals/Res.ngs diff --git a/lib/autoload/Screen.ngs b/lib/autoload/globals/Screen.ngs similarity index 100% rename from lib/autoload/Screen.ngs rename to lib/autoload/globals/Screen.ngs diff --git a/lib/autoload/Set.ngs b/lib/autoload/globals/Set.ngs similarity index 100% rename from lib/autoload/Set.ngs rename to lib/autoload/globals/Set.ngs diff --git a/lib/autoload/Table.ngs b/lib/autoload/globals/Table.ngs similarity index 100% rename from lib/autoload/Table.ngs rename to lib/autoload/globals/Table.ngs diff --git a/lib/autoload/Table2.ngs b/lib/autoload/globals/Table2.ngs similarity index 100% rename from lib/autoload/Table2.ngs rename to lib/autoload/globals/Table2.ngs diff --git a/lib/autoload/Terminal.ngs b/lib/autoload/globals/Terminal.ngs similarity index 78% rename from lib/autoload/Terminal.ngs rename to lib/autoload/globals/Terminal.ngs index 43a41d66..2f472f7b 100644 --- a/lib/autoload/Terminal.ngs +++ b/lib/autoload/globals/Terminal.ngs @@ -17,14 +17,6 @@ F Terminal(fd_in:Int, fd_out:Int) { STDLIB_TERMINALS[k] = NoTerminal(fd_in, fd_out) } -F Terminal(fd_in:Int, fd_out:Int) { - guard isatty(fd_in) and isatty(fd_out) - debug("term", "Terminal() choose AnsiTerminal()") - k = "${fd_in}:${fd_out}" - k in STDLIB_TERMINALS returns STDLIB_TERMINALS[k] - STDLIB_TERMINALS[k] = AnsiTerminal(fd_in, fd_out) -} - F Terminal() Terminal(0,1) F init(t:Terminal, fd_in:Int, fd_out:Int) { @@ -53,3 +45,5 @@ F status(t:Terminal, s:Str) t.echo("[STATUS ${Time()}] $s") F Bool(t:Terminal) true F ExitCode(t:Terminal) t.Bool().ExitCode() + +require(Dir("autoload/constructors/Terminal")) diff --git a/lib/autoload/TestsResults.ngs b/lib/autoload/globals/TestsResults.ngs similarity index 100% rename from lib/autoload/TestsResults.ngs rename to lib/autoload/globals/TestsResults.ngs diff --git a/lib/autoload/ThreadedExecutor.ngs b/lib/autoload/globals/ThreadedExecutor.ngs similarity index 100% rename from lib/autoload/ThreadedExecutor.ngs rename to lib/autoload/globals/ThreadedExecutor.ngs diff --git a/lib/autoload/TmuxTerminal.ngs b/lib/autoload/globals/TmuxTerminal.ngs similarity index 100% rename from lib/autoload/TmuxTerminal.ngs rename to lib/autoload/globals/TmuxTerminal.ngs diff --git a/lib/autoload/bootstrap_invoke_main.ngs b/lib/autoload/globals/bootstrap_invoke_main.ngs similarity index 51% rename from lib/autoload/bootstrap_invoke_main.ngs rename to lib/autoload/globals/bootstrap_invoke_main.ngs index 292251b8..2766cb4a 100644 --- a/lib/autoload/bootstrap_invoke_main.ngs +++ b/lib/autoload/globals/bootstrap_invoke_main.ngs @@ -5,7 +5,7 @@ type BootstrapFailedMatchMain(Exception) doc Internal method. Please do not use. doc Tries to run c if it matches argv. F bootstrap_try_main(c:UserDefinedMethod, argv:Arr, b:Block) { - bootstrap_debug("Trying main() candidate $c") + debug('bootstrap', "Trying main() candidate $c") try { m = null try { @@ -14,17 +14,17 @@ F bootstrap_try_main(c:UserDefinedMethod, argv:Arr, b:Block) { throw BootstrapFailedMatchMain("Command line arguments are invalid. Failed to match main() method.").set('cause', e) } if not(m) { - bootstrap_debug("Trying main() candidate ${c} - no match before calling") + debug('bootstrap', "Trying main() candidate ${c} - no match before calling") return null } # echo("Matched: $m") v = c(*m.args, **m.kwargs) - bootstrap_debug("Trying main() candidate ${c} - matched") + debug('bootstrap', "Trying main() candidate ${c} - matched") b.return(v) } catch(mnf:MethodNotFound) { # That's fine mnf.callable !== c throws mnf - bootstrap_debug("Trying main() candidate ${c} - no match after calling") + debug('bootstrap', "Trying main() candidate ${c} - no match after calling") } } @@ -32,13 +32,24 @@ doc Internal method. Please do not use. doc Find and invoke main() method that matches the passed command line arguments. doc Called by bootstrapper in cases where main() is defined. doc default - The result of evaluation of the code that was run by bootstrapper. Returned when main is not a MultiMethod or the defined main() was not in the main file. -F bootstrap_invoke_main(bootstrap_main_file:Str, default) block b { - main is not MultiMethod returns default - candidate_mains = main.Arr().filter(F(m) m.ip().resolve_instruction_pointer().file == bootstrap_main_file).reverse() - not(candidate_mains) returns default - bootstrap_debug("Candidate main() functions: $candidate_mains") +F bootstrap_invoke_main(main) block b { + debug('bootstrap', "In bootstrap_invoke_main()") - candidate_mains.each(bootstrap_try_main(X, ARGV, b)) - # TODO: print usage examples here and exit with error code - throw BootstrapNoMatchingMain("Command line arguments are invalid.") + ematch main { + MultiMethod { + candidate_mains = main.Arr().reverse() + debug('bootstrap', "Candidate main() functions: $candidate_mains") + + candidate_mains.each(bootstrap_try_main(X, ARGV, b)) + # TODO: print usage examples here and exit with error code + throw BootstrapNoMatchingMain("Command line arguments are invalid.") + } + Fun { + bootstrap_try_main(main, ARGV, b) + throw BootstrapNoMatchingMain("Command line arguments are invalid.") + } + Any { + throw MainFail("'main' must be callable").set('value', main) + } + } } diff --git a/lib/autoload/escape_bash.ngs b/lib/autoload/globals/escape_bash.ngs similarity index 100% rename from lib/autoload/escape_bash.ngs rename to lib/autoload/globals/escape_bash.ngs diff --git a/lib/autoload/mysql.ngs b/lib/autoload/globals/mysql.ngs similarity index 100% rename from lib/autoload/mysql.ngs rename to lib/autoload/globals/mysql.ngs diff --git a/lib/autoload/test.ngs b/lib/autoload/globals/test.ngs similarity index 100% rename from lib/autoload/test.ngs rename to lib/autoload/globals/test.ngs diff --git a/lib/bootstrap.ngs b/lib/bootstrap.ngs deleted file mode 100644 index a78c82f0..00000000 --- a/lib/bootstrap.ngs +++ /dev/null @@ -1,331 +0,0 @@ -section "Basic Methods for Exception type" { - doc Initialize an Exception. - doc %RET - Exception with .backtrace and .message - doc message - Goes into .message - F init(e:Exception, message:Str) { - e.message = message - } - - doc Initialize an Exception. - doc %RET - Exception with .backtrace and .cause - F init(e:Exception, cause:Exception) { - e.cause = cause - } - - doc Initialize an Exception. - doc %RET - Exception with .backtrace, .message, and .cause - F init(e:Exception, message:Str, cause:Exception) { - e.message = message - e.cause = cause - } -} - -section "Basic Exception subtypes" { - type NotImplemented; NotImplemented.inherit(Exception) - type ReadFail; ReadFail.inherit(Exception) - type RequireFail; RequireFail.inherit(Exception) - type MainFail; MainFail.inherit(Exception) - - doc Represents error when converting program execution result to exit code - type ExitCodeFail; ExitCodeFail.inherit(Exception) -} - -section "Basic I/O" { - doc Write a string followed by newline character to a file descriptor. - doc Method with same name and parameters is later loaded from stdlib. - doc It is more correct and has better error handling. - F echo(fd:Int, s:Str) { - bytes = c_write(fd, "${s}\n") - if bytes == -1 { - throw Exception("Failed to write to file descriptor ${fd}.") - } - if bytes != len(s) { - throw Exception("Failed to write to file descriptor ${fd}. Partial success.") - } - null - } - - doc Print given string and a newline to stdout. - doc %RET - null - doc %EX - echo("blah") # Output: blah - F echo(s:Str) echo(1, s) -} - -doc Information about exiting, used by bootstrap and as argument to exit_hook. -doc exit_code - the code to exit with -doc exceptions - exceptions that occurred during exit procedure -type Exit - -# === bootstrap ================================== - -section "Basic ExitCode pseudo-constructor methods" { - doc %RET - always 0 - F ExitCode(x) 0 - - doc %RET - 0 for true, 1 for false - F ExitCode(b:Bool) if b 0 1 - - doc %RET - n - F ExitCode(n:Int) n - - doc %RET - Always 240 - F ExitCode(e:Exception) 240 -} - -section "MultiMethod - constructor and basic methods" { - doc Create MultiMethod with no methods - F MultiMethod() super([]) - - doc Number of methods in MultiMethod - F len(mm:MultiMethod) mm.Arr().len() - - doc Whether MultiMethod has any methods - F Bool(mm:MultiMethod) mm.Arr().Bool() -} - -main = MultiMethod() - -# *** Define bootstrap_debug function *** -bootstrap_debug = - if 'NGS_BOOTSTRAP_DEBUG' in ENV - F(s) echo(2, "+ [BOOSTRAP DEBUG] $s") - else - F(s) {} - -bootstrap_ngs_dir_candidates = [ - '/usr/local/etc/ngs' - '/usr/local/lib/ngs' - '/etc/ngs' - '/usr/lib/ngs' -] - -doc Internal method. Please do not use. -doc Find which of bootstrap_ngs_dir_candidates directories contains stdlib.ngs . That directory would be the "NGS_DIR". -doc %RET - Str or null -doc %STATUS - internal -F bootstrap_find_ngs_dir() { - 'NGS_DIR' in ENV returns ENV['NGS_DIR'] - l = bootstrap_ngs_dir_candidates.len() - for(i;l) { - c_access("${bootstrap_ngs_dir_candidates[i]}/stdlib.ngs", ACCESS['F_OK']) == 0 returns bootstrap_ngs_dir_candidates[i] - } - null -} - -# *** read - abstraction above low level c io primitives *** -# TODO: Better exceptions -doc Fetches the whole file -doc fname - File name to read -doc %RET - Whole file as a string -F read(fname:Str) { - try { - fd = c_open(fname, 'r') - fd <= 0 throws Exception("read(): failed to open file ${fname}") - bootstrap_debug("read(): opened file ${fname} as fd ${fd}") - file_len = c_lseek(fd, 0, "end") - file_len < 0 throws Exception("read(): failed to get the size of file ${fname}") - c_lseek(fd, 0, "set") - bootstrap_debug("read(): file size of ${fname} is ${file_len}") - data = c_read(fd, file_len) - c_close(fd) - data[1] - } catch(e) { - throw ReadFail(e) - } -} - - - -F bootstrap_print_compilation_warnings(program_bytecode, fname) { - - warnings = program_bytecode.attrs()['warnings'] - l = warnings.len() - for(i;l) { - w = warnings[i] - wl = w['location'] - echo(2, "${fname}:${wl['first_line']}:${wl['first_column']} warning: ${w['message']}") - } -} - -doc Runs the given file -doc %RET - Typically whatever the last expression in the file evaluates to -F require(fname:Str) { - try { - program_text = read(fname) - program_bytecode = compile(program_text, fname) - bootstrap_debug("require(): bytecode for ${fname} has length of ${program_bytecode.len()} bytes") - bootstrap_print_compilation_warnings(program_bytecode, fname) - program_func = load(program_bytecode, "require()d file: $fname") - ret = program_func() - bootstrap_debug("require(): done requiring ${fname}") - ret - } catch(e) { - throw RequireFail(e) - } -} - -doc Internal method. Please do not use. -doc Main entry point. Executed on NGS boot. -doc %STATUS - internal -F bootstrap() { - global ARGV0 - global ORIG_ARGV = copy(ARGV) - - do_load_stdlib = true - code_to_run = null - - bootstrap_debug('bootstrap.ngs begin') - bootstrap_debug("BOOTSTRAP_FILE=$BOOTSTRAP_FILE") - - # *** Prepare ARGV *** - # first_item = shift(array, default_value_if_array_is_empty) - ARGV0 = ARGV.shift(null) - - # *** Parse switches *** - sw = null - if ARGV { - a = ARGV[0] - if (a == '--version') { - echo(VERSION) - return 0 - } - if (a == '-e' or a == '-E' or a == '-p' or a == '-pj' or a == '-pi' or a == '-pl' or a == '-pil' or a == '-pjl' or a == '-pc' or a == '-pt') { - sw = ARGV.shift() - # Newline at the end is for avoiding # commenting out the closing } - # Newline at the beginning is for symmetry - default_code = 'F default_argv_code() { fetch() }()' - code_to_run = "{\n${ARGV.shift(default_code)}\n}" - # code_to_run = ARGV.shift(default_code) - bootstrap_debug("Got $sw switch, using command line provided code") - if sw == '-E' { - bootstrap_debug('Will skip loading stdlib because of -E flag') - do_load_stdlib = false - } - fname = "" - } - } - - if 'NGS_SKIP_STDLIB' in ENV { - bootstrap_debug('Will skip loading stdlib because of set NGS_SKIP_STDLIB environment variable') - do_load_stdlib = false - } - - global NGS_DIR = bootstrap_find_ngs_dir() - - NGS_DIR is Null throws Exception("Could not find NGS_DIR") - - if do_load_stdlib { - bootstrap_debug('Will load stdlib') - stdlib_file = "${NGS_DIR}/stdlib.ngs" - bootstrap_debug("Stdlib is at $stdlib_file") - require(stdlib_file) - } - - # Note: not() is not defined yet - - if code_to_run is Null { - bootstrap_debug('No -e or -E switch was passed, will load the program from a file') - fname = ARGV.shift(null) - if fname is Null { - fname = ENV.get('NGS_CLI', "${NGS_DIR}/cli.ngs") - } else { - ARGV0 = fname - } - code_to_run = read(fname) - } - - program_bytecode = compile(code_to_run, fname) - bootstrap_debug("bootstrap(): bytecode for ${fname} has length of ${program_bytecode.len()} bytes") - bootstrap_print_compilation_warnings(program_bytecode, fname) - program_func = load(program_bytecode, "bootstrap()ped file: $fname") - - result = program_func() - - section "Handle main()" { - if main.len() > 0 { - bootstrap_debug("main() defined, using bootstrap_invoke_main()") - if global_not_found_handler.len() == 0 { - throw MainFail("Autoloading (usually defined in stdlib) is not present but the main() invoking mechanism requires it") - } - result = bootstrap_invoke_main(fname, result) - } - } - - if sw == '-E' { - } else { - - transformation = { - '-pl': identity - '-pil': inspect - '-pjl': encode_json - } - if sw in transformation { - for r in result { - transformation[sw](r).echo() - } - return 0 - } - - transformation = { - '-p': identity - '-pi': inspect - '-pj': encode_json - '-pc': code - '-pt': { Table2::Table(A) } # Lazy loading of Table2 - } - if sw in transformation { - echo(transformation[sw](result)) - return 0 - } - } - result.ExitCode() -} - -doc Uses built-in dump() to show an exception. Only used if stdlib is not loaded (stdlib defines better method). -F print_exception(e:Exception) { - dump(exception) -} - -doc Prints exceptions. Only used if stdlib is not loaded (stdlib changes exit_hook to a Hook object). -F exit_hook(exit:Exit) { - for(i; exit.exceptions.len()) { - echo(2, "[exit_hook] --- Uncaught exception ---") - print_exception(exit.exceptions[i]) - } -} - -doc Internal method. Please do not use. -doc Runs bootstrap() and handles exceptions. -doc %STATUS - internal -F bootstrap_exception_catch_wrapper() { - exit = Exit() - exit.exit_code = 0 - exit.exceptions = [] - try { - exit.exit_code = bootstrap() - } catch(e:Exception) { - exit.exceptions.push(e) - exit.exit_code = 240 - try { - exit.exit_code = e.ExitCode() - } catch(e:Exception) { - exit.exceptions.push(ExitCodeFail("Failed to convert exception to exit code", e)) - exit.exit_code = exit.exit_code + 1 - } - } - - try { - exit_hook(exit) - } catch(e:Exception) { - exit.exceptions.push(e) - exit.exit_code = exit.exit_code + 2 - for(i; exit.exceptions.len()) { - echo(2, "[bootstrap_exception_catch_wrapper] --- Uncaught exception ---") - dump(exit.exceptions[i]) - } - } - - c_exit(exit.exit_code) -} - -bootstrap_exception_catch_wrapper() diff --git a/lib/experiments/UI.ngs b/lib/experiments/UI.ngs new file mode 100644 index 00000000..b29bd099 --- /dev/null +++ b/lib/experiments/UI.ngs @@ -0,0 +1,26 @@ +ns { + + # Run with: ngs -e 'require("experiments/UI.ngs")' + + type Dimension + type CharactersDimension(Dimension) + type StretchDimension(Dimension) + type PixelsDimension(Dimension) + + type Screen + type TerminalScreen(Screen) + type WebScreen(Screen) + type Widget + type ContainerWidget(Widget) + + F init(d:Dimension, val) cd.val = val + + + # Later - multu-screen sessions + F Screen() TerminalScreen() # for now terminal only, later - web + + F main() { + s = Screen() + echo(s) + } +} diff --git a/lib/stdlib.ngs b/lib/stdlib.ngs index 959f2f6f..bb94f45f 100644 --- a/lib/stdlib.ngs +++ b/lib/stdlib.ngs @@ -1,3 +1,67 @@ +# WARNING: This file is packaged into the ngs binary at build time +# it is shipped for reference only. + +section "Basic Methods for Exception type" { + doc Initialize an Exception. + doc %RET - Exception with .backtrace and .message + doc message - Goes into .message + F init(e:Exception, message:Str) { + e.message = message + } + + doc Initialize an Exception. + doc %RET - Exception with .backtrace and .cause + F init(e:Exception, cause:Exception) { + e.cause = cause + } + + doc Initialize an Exception. + doc %RET - Exception with .backtrace, .message, and .cause + F init(e:Exception, message:Str, cause:Exception) { + e.message = message + e.cause = cause + } +} + +section "Basic Exception subtypes" { + type NotImplemented; NotImplemented.inherit(Exception) + type ReadFail; ReadFail.inherit(Exception) + type RequireFail; RequireFail.inherit(Exception) + type MainFail; MainFail.inherit(Exception) + + doc Represents error when converting program execution result to exit code + type ExitCodeFail; ExitCodeFail.inherit(Exception) +} + +section "Basic I/O" { + doc Print given string and a newline to stdout. + doc %RET - null + doc %EX - echo("blah") # Output: blah + F echo(s:Str) echo(1, s) +} + + + +section "Exit type and basic ExitCode pseudo-constructor methods" { + + doc Information about exiting, used by bootstrap and as argument to exit_hook. + doc exit_code - the code to exit with + doc exceptions - exceptions that occurred during exit procedure + type Exit + + doc %RET - always 0 + F ExitCode(x) 0 + + doc %RET - 0 for true, 1 for false + F ExitCode(b:Bool) if b 0 1 + + doc %RET - n + F ExitCode(n:Int) n + + doc %RET - Always 240 + F ExitCode(e:Exception) 240 +} + section "Block type and methods" { doc The type of the NAME variable used in syntax: block NAME BODY doc %AUTO - block NAME BODY @@ -146,6 +210,15 @@ section "Attributes convenience methods" { section "MultiMethod" { + doc Create MultiMethod with no methods + F MultiMethod() super([]) + + doc Number of methods in MultiMethod + F len(mm:MultiMethod) mm.Arr().len() + + doc Whether MultiMethod has any methods + F Bool(mm:MultiMethod) mm.Arr().Bool() + block _ { global each # Preventing execution of F Arr(Eachable1) defined below. @@ -160,6 +233,8 @@ section "MultiMethod" { } } + F filtero(mm:MultiMethod, pred) mm.Arr().filter(pred).MultiMethod() + F Str(mm:MultiMethod) "" } @@ -361,6 +436,10 @@ F only(predicate, mapper:Fun) { TEST ["abc", 1, "def", 2].map(only(Int, X*2)) == ["abc", 2, "def", 4] +doc %STATUS - experimental +doc Same as only(predicate, mapper)(val) +F only(val, predicate, mapper:Fun) only(predicate, mapper)(val) + doc Filter e using predicate. doc e - Eachable1. WARNING: instances of same type as e must have empty constructor and push() method. doc predicate - Decision function to be called with each item as first argument. @@ -372,10 +451,10 @@ F filter(e:Eachable1, predicate=identity) { pred = Pred(predicate) t = typeof(e) ret = t() - e.each() do F(elt) { + e.each(F(elt) { if pred(elt) ret.push(elt) - } + }) ret } @@ -461,12 +540,12 @@ doc %RET - The only element that satisfies the predicate. F the_one(e:Eachable1, predicate={true}) { p = Pred(predicate) ret = EmptyBox() - e.each() do F(elt) { + e.each(F(elt) { if p(elt) { ret throws ElementNotFound("the_one() had more than one match").set('container', e).set('predicate', predicate) ret = FullBox(elt) } - } + }) not(ret) throws ElementNotFound("the_one() had no matches").set('container', e).set('predicate', predicate) ret.get() } @@ -939,9 +1018,7 @@ doc %EX - my_hashlike.filter(F(k, v) k == 'a') doc %RET - value of the same type as hl F filter(hl:HashLike, predicate) { t = typeof(hl) - ret = t() - ret.attrs().HashLike = hl.attrs().HashLike.filter(predicate) - ret + t(hl.attrs().HashLike.filter(predicate)) } TEST HashLike({"a": 1, "b": 2}).filter(F(k, v) v==1) == HashLike({"a": 1}) @@ -961,6 +1038,93 @@ F Hash(hl:HashLike) hl.attrs().HashLike doc Convert HashLike into JSON compatible data structure - object F JsonData(hl:HashLike) hl.map(F(k, v) [JsonData(k), JsonData(v)]).Hash() + + +section "Set" { + + doc Represents a set of items + type Set + Set.inherit(Eachable1) + + doc Initialize a set + F init(s:Set) { + s.val = {} + } + + doc Convert array to set + F init(s:Set, e:Eachable1) { + super(s) + e.each({ s.val[A] = true }) + } + + doc Add an element to the set + doc %RET - s + F push(s:Set, v) { + s.val[v] = true + s + } + + doc Get number of items in the set + doc %RET - Int + F len(s:Set) s.val.len() + + doc Call cb for each value in the set + doc %RET - s + F each(s:Set, cb:Fun) { + s.val.keys().each(cb) + s + } + + doc Check if the value is in the set + doc %RET - Bool + F in(x, s:Set) x in s.val + + doc Check whether the set is not empty + F Bool(s:Set) Bool(s.val) + + doc Convert the set to a human-readable representation + F Str(s:Set) "<${s.typeof().name} ${s.val.keys().join(', ')}>" + + doc Set difference + doc %RET - Set + F -(a:Set, b:Set) a.filter(X not in b) + + TEST Set([1,2]) - Set([2]) == Set([1]) + + doc Set union + doc %RET - Set + F +(a:Set, b:Set) Set().set('val', a.val + b.val) + + TEST Set([1,2]) + Set([3]) == Set([1,2,3]) + + doc Check whether all values in a are also in b + F '<='(a:Set, b:Set) { + len(a) <= len(b) and a.all(X in b) + } + + TEST Set([1,2]) + Set([3]) <= Set([1,2,3]) + TEST Set([1,2]) <= Set([1,2,3]) + TEST not(Set([1,2,4]) <= Set([1,2,3])) + + F copy(s:Set) { + ret = Set() + s.each(ret.push(X)) + ret + } + + doc Inspect Set + doc %RET - Lines + F inspect(a:Set) Lines(["Set of size ${a.len()}"] + a.map({" ${SafeStr(A)}"}).digest(16, ' (...)')) + + doc Defines collector { ... collect(...) ... } behaviour for Set. + F collector(s:Set, body:Fun) { + body(F(elt) s.push(elt)) + s + } + +} + + section "Named Instances" { doc "Named instances" are typically called enums or enumerated values in other languages. doc %EX - type Color(NamedInstances) @@ -1040,35 +1204,35 @@ section "Named Instances" { TEST global init; type Color(NamedInstances); F init(c:Color, numval:Int) c.numval = numval; Color.NamedInstances = ns { RED = Color(4); GREEN = Color(2); BLUE = Color(1) ; }; Color.NamedInstances is Namespace and 2.decode(Color) == Color::GREEN and "RED".decode(Color) == Color::RED } -# === Hook ======================================= +section "Hook" { + doc Hook is a simple pub-sub + type Hook + Hook.inherit(HashLike) -doc Hook is a simple pub-sub -type Hook -Hook.inherit(HashLike) + doc Hook constructor. + doc %RET - Hook + F init(hook:Hook) { + super(hook) + hook.attrs().idx = 0 + } -doc Hook constructor. -doc %RET - Hook -F init(hook:Hook) { - super(hook) - hook.attrs().idx = 0 -} + doc Add unnamed handler. + doc The hook is automatically named "pushed-N" where N is sequential integer. + doc %RET - New hook name + F push(hook:Hook, handler:UserDefinedMethod) { + name = "pushed-${hook.attrs().idx}" + hook.attrs().idx += 1 + hook[name] = handler + name + } -doc Add unnamed handler. -doc The hook is automatically named "pushed-N" where N is sequential integer. -doc %RET - New hook name -F push(hook:Hook, handler:UserDefinedMethod) { - name = "pushed-${hook.attrs().idx}" - hook.attrs().idx += 1 - hook[name] = handler - name + # TODO: consider optional? try/catch isolation + doc Runs all handlers passing all args. + doc args - Arguments to pass to handlers + doc %RET - Arr. Results from all the handlers + F call(hook:Hook, *args) hook.attrs().HashLike.mapv(X(*args)) } -# TODO: consider optional? try/catch isolation -doc Runs all handlers passing all args. -doc args - Arguments to pass to handlers -doc %RET - Arr. Results from all the handlers -F call(hook:Hook, *args) hook.attrs().HashLike.mapv(X(*args)) - # === Real ======================================= # TODO: something more efficient @@ -1138,7 +1302,7 @@ section "Type" { doc %AUTO - type MyType2([MyParent1, MyParent2, ...]) F Type(t:Str, doc, ns, parents:Arr) { ret = Type(t, doc, ns) - parents % ret.inherit(X) + parents.each(ret.inherit(X)) ret } @@ -1308,34 +1472,20 @@ type CError CError.inherit(Error) doc CError constructor. In addition to storing message field, adds errno and errno_name fields. -F init(e:CError, message:Str) { - errno = c_errno() +F init(e:CError, errno:Int, message:Str) { super(e, message) e.errno = errno e.errno_name = c_strerror(e.errno) } doc CError constructor. In addition to storing message field, adds errno and errno_name fields. -F init(e:CError, errno:Int, message:Str) { - super(e, message) - e.errno = errno - e.errno_name = c_strerror(e.errno) -} +F init(e:CError, message:Str) super(e, c_errno(), message) + doc Exception representing a failure to kill() a process type KillFail KillFail.inherit(CError) -section "ValueWrapper type and methods" { - type ValueWrapper - - F init(vw:ValueWrapper) throw Error("ValueWrapper subtype ${vw.typeof().name} must have init() argument") - - F init(vw:ValueWrapper, val) vw.val = val - - F Str(vw:ValueWrapper) "<${vw.typeof().name} ${vw.val}>" -} - # === Lock ======================================= doc Synchronization lock. @@ -1418,63 +1568,6 @@ F Str(cpm:c_pthread_mutex_t) "" doc Convert c_pthread_mutexattr_t to Str for displaying to humans. F Str(cpma:c_pthread_mutexattr_t) "" -# === auto-load ================================== - -AUTOLOAD = ns { - global global_not_found_handler - _lock = ReentrantLock() - - # Sorted by: file, global variable name - # Not exposing _global_name_to_autoload_file as use cases are not clear yet. - # Also, it's probably better to expose an API and not the data. - _global_name_to_autoload_file = %{ - InMemoryCache Cache - OnDiskCache Cache - - IPAddr IP - IPNet IP - - ArrIter Iter - ConstIter Iter - HashIter Iter - NoNext Iter - RangeIter Iter - - ResDef Res - } - - invocation = 0 - - doc Called when reading undefined global. - doc Implements autoloading. - doc Searches in $NGS_DIR/autoload/GLOBAL_NAME.ngs - doc WARNING: May have security implications when looking up a name from untrusted source. - doc %EX - test("My web server runs") do { .... } # $NGS_DIR/autoload/test.ngs is automatically loaded. - F global_not_found_handler(name:Str) { - _lock.acquire() do { - i = invocation - invocation += 1 - F local_debug(s:Str) { - debug("autoload", "global_not_found_handler() [invocation $i] ${s}") - } - local_debug("name=${name}") - idx = ll_resolve_global_variable(name) - ll_is_global_variable_defined(idx) returns - if name in _global_name_to_autoload_file { - local_debug("loading ${NGS_DIR}/autoload/${_global_name_to_autoload_file[name]}.ngs") - require("${NGS_DIR}/autoload/${_global_name_to_autoload_file[name]}.ngs") - return - } - local_debug("loading ${NGS_DIR}/autoload/${name}.ngs") - val = require("${NGS_DIR}/autoload/${name}.ngs") - if not(ll_is_global_variable_defined(idx)) { - local_debug("global variable ${name} still not set; setting") - ll_set_global_variable(idx, val) - } - } - } -} - section "Range" { doc A range type Range @@ -1569,8 +1662,7 @@ section "Range" { doc Length of a NumRange. Only defined for Int start and end. doc Equals to how many times cb will be invoked in each(r, cb). F len(r:NumRange) { - guard r.start is Int - guard r.end is Int + guard [r.start, r.end].all(Int) r.end - r.start - 1 + r.include_start.Int() + r.include_end.Int() } @@ -1748,14 +1840,12 @@ doc %EX - ) F finally(body:Fun, cleanup:Fun) { ret = Result(body) cleanup() - if ret is Failure { - throw ret.val - } + ret is Failure throws ret.val ret.get() } TEST a=0; {1/1}.finally({a=10}) == 1 and a == 10 -TEST a=0; (try finally() with {1/0} with {a=10} catch(e:DivisionByZero) "OK") == "OK" and a == 10 +TEST a=0; (try finally({1/0}, {a=10}) catch(e:DivisionByZero) "OK") == "OK" and a == 10 TEST F f() block b { finally({ b.return(7) }, {8}) }; f() == 7 # --- Hash --- @@ -1777,19 +1867,6 @@ doc %EX - h.get("a") # 1 doc %EX - h.get("b") # null F get(h:Hash, k) get(h, k, null) -doc Convert hash values to integers where possible -doc %RET - New Hash -doc %STATUS - deprecated -doc %EX - %{k 7 kk "a"}["k"] is Str # true -doc %EX - %{k 7 kk "a"}.n() # {'k': 7, 'kk': 'a'} -doc %EX - %{k 7 kk "a"}.n()["k"] is Int # true -F n(h:Hash) { - warn("Using deprecated n(Hash)") - h.mapv({Int(A) tor A}) -} - -TEST %{k 7 kk "a"}.n() == {'k': 7, 'kk': 'a'} - # --- Arr --- # TODO: generalize to Eachable1 ? @@ -1966,11 +2043,7 @@ TEST [1,2,3].map(X*4) == [4,8,12] F subset(smaller:Arr, larger:Arr) { len(smaller) > len(larger) returns false - h = if larger.len() > 10 { - larger.Set() - } else { - larger - } + h = larger.only({A.len() > 10}, Set) smaller.all(X in h) } @@ -2020,17 +2093,6 @@ doc %EX - echo("Array items: " + ArrLike().push(10).push(20).push(30).map_idx_va doc %EX - # Outputs: Array items: [0]=10, [1]=20, [2]=30 F map_idx_val(e:Eachable1, mapper:Fun) collector each_idx_val(e, collect + mapper) -doc Map an Arr to an Arr (array) of values using mapper -doc mapper is called as mapper(INDEX, ITEM) -doc %RET - New Arr -doc %EX - echo("Array items: " + [10,20,30].map_idx_val(F(idx, val) "[$idx]=$val").join(", ")) -doc %EX - # Outputs: Array items: [0]=10, [1]=20, [2]=30 -F map_idx_val(arr:Arr, mapper:Fun) { - l = arr.len() - collector - for(i;l) collect(mapper(i, arr[i])) -} - TEST r=[]; ["a", "b"].each_idx_val(F(idx, val) r.push([idx, val])); r == [[0, "a"], [1, "b"]] @@ -2084,11 +2146,7 @@ doc Filter out all values in a that are also in b doc %RET - Arr doc %EX - [1,2,3] - [5,6,1] # [2,3] F -(a:Arr, b:Arr) { - h = if b.len() > 10 { - b.Set() - } else { - b - } + h = b.only({A.len() > 10}, Set) a.filter(X not in h) } @@ -2555,7 +2613,7 @@ F split(e:Eachable1, delim) { TEST [1, "a", 2, 3, "a", 4].split("a") == [[1], [2, 3], [4]] doc Call cb with array of maximum length of n, repeatedely for all items of e. -doc TODO: better doc +doc TODO: better doc, rename to each() to be consistent with each(Arr, Int, Fun)? doc %STATUS - experimental F each_chunk(e:Eachable1, n:Int, cb:Fun) { cur = [] @@ -2655,10 +2713,8 @@ doc cb - Function to call with successive keys and values doc %RET - h doc %EX - {"a": 1, "b": 2}.each(F(k, v) echo("$k=$v")) # Outputs: "a=1" and on the next line "b=2" F each(h:Hash, cb:Fun) { - entry = ll_hash_head(h) - while entry != null { + for(entry = ll_hash_head(h); entry != null; entry = entry.ll_hash_entry_next()) { cb(entry.ll_hash_entry_key(), entry.ll_hash_entry_val()) - entry = entry.ll_hash_entry_next() } h } @@ -2667,27 +2723,18 @@ doc Iterate a Hash. doc h - Hash to iterate doc cb - Function to call with successive keys doc %RET - h -F eachk(h:Hash, cb:Fun) { - entry = ll_hash_head(h) - while entry != null { - cb(entry.ll_hash_entry_key()) - entry = entry.ll_hash_entry_next() - } - h -} +F eachk(h:Hash, cb:Fun) h.each({cb(A)}) + +TEST ret = []; {"a": 1, "b": 2}.eachk(ret.push(X)); ret == %[a b] doc Iterate a Hash. doc h - Hash to iterate doc cb - Function to call with successive values doc %RET - h -F eachv(h:Hash, cb:Fun) { - entry = ll_hash_head(h) - while entry != null { - cb(entry.ll_hash_entry_val()) - entry = entry.ll_hash_entry_next() - } - h -} +F eachv(h:Hash, cb:Fun) h.each({cb(B)}) + +TEST ret = []; {"a": 1, "b": 2}.eachv(ret.push(X)); ret == %[1 2] + doc Iterate a Hash. doc h - Hash to iterate @@ -2697,12 +2744,8 @@ doc %RET - h doc %EX - {"a": 1, "b": 2}.each_idx_key_val(F(idx, k, v) echo("[$idx] $k=$v")) doc %EX - # Outputs: "[0] a=1" and on the next line "[1] b=2" F each_idx_key_val(h:Hash, cb:Fun) { - entry = ll_hash_head(h) - i = 0 - while entry != null { - cb(i, entry.ll_hash_entry_key(), entry.ll_hash_entry_val()) - entry = entry.ll_hash_entry_next() - i += 1 + for({idx=0; entry=ll_hash_head(h)}; entry != null; {idx+=1; entry = entry.ll_hash_entry_next()}) { + cb(idx, entry.ll_hash_entry_key(), entry.ll_hash_entry_val()) } h } @@ -2723,18 +2766,7 @@ doc h - Hash with source keys and values doc mapper - Function to be called with sequential zero-based index, keys and values from h doc %RET - Arr doc %EX - {'a': 1, 'b': 2}.map_idx_key_val(F(i, k, v) "${i}-${k}-$v") # ['0-a-1', '1-b-2'] -F map_idx_key_val(h:Hash, mapper:Fun) { - idx = 0 - collector { - entry = ll_hash_head(h) - while entry != null { - collect(mapper(idx, entry.ll_hash_entry_key(), entry.ll_hash_entry_val())) - entry = entry.ll_hash_entry_next() - idx += 1 - } - } -} - +F map_idx_key_val(h:Hash, mapper:Fun) collector h.each_idx_key_val(collect + mapper) doc Map Hash keys. Build new Hash with same values as in h but keys mapped by mapper. doc h - Source hash @@ -2800,28 +2832,31 @@ doc %EX - {"a": 1, "b": 2, "ccc": 3}.filterk(/^.$/) # {a=1, b=2} doc %EX - {"aa": 1, "ab": 2, "ba": 3}.filterk(/^a/) # {aa=1, ab=2} doc %EX - {"aa": 1, "ab": 2, 10: 3}.filterk(Int) # {10=3} doc %EX - {10: "a", 20: "b", 30: "c"}.filterk(X > 10) # {20=b, 30=c} -F filterk(h:Eachable2, predicate) filter(h, Pred(predicate) + {A}) +F filterk(h:Eachable2, predicate=identity) filter(h, Pred(predicate) + {A}) TEST {"a1": 1, "a2": 2, "b1": 10}.filterk(/^b/) == {"b1": 10} +TEST {0: "zero", 1: "one"}.filterk() == {1: "one"} doc Filter hash by keys, removing matched. See filterk(). doc %EX - {"a1": 1, "a2": 2, "b1": 10}.rejectk(/^b/) # {"a1": 1, "a2": 2} -F rejectk(h:Eachable2, predicate) h.filterk(not + Pred(predicate)) +F rejectk(h:Eachable2, predicate=identity) h.filterk(not + Pred(predicate)) TEST {"a1": 1, "a2": 2, "b1": 10}.rejectk(/^b/) == {"a1": 1, "a2": 2} +TEST {0: "zero", 1: "one"}.rejectk() == {0: "zero"} doc Filter hash by values doc %EX - {"a1": 1, "a2": 2, "b1": 10}.filterv(X>5) # {"b1": 10} -F filterv(h:Eachable2, predicate) filter(h, Pred(predicate) + {B}) +F filterv(h:Eachable2, predicate=identity) filter(h, Pred(predicate) + {B}) TEST {"a1": 1, "a2": 2, "b1": 10}.filterv(X>5) == {"b1": 10} - +TEST {"yes": true, "no": false}.filterv() == {"yes": true} doc Filter hash by values -F rejectv(h:Eachable2, predicate) h.filterv(not + Pred(predicate)) +F rejectv(h:Eachable2, predicate=identity) h.filterv(not + Pred(predicate)) TEST {"a1": 1, "a2": 2, "b1": 10}.rejectv(X>5) == {"a1": 1, "a2": 2} +TEST {"yes": true, "no": false}.rejectv() == {"no": false} doc Count number of key-value pairs in Hash that satisfy the predicate. doc e - Something to check, typically a Hash @@ -2860,11 +2895,14 @@ doc Create a Hash from Arr of Arr[2] doc arr - Array of Arrays. Each one of the sub-arrays must have exactly two elements. doc %RET - Hash doc %EX - Hash([['a', 1], ['c', 3]]) # {'a': 1, 'c': 3} -F Hash(arr:Arr) +F Hash(arr:Arr) { + guard arr.all(Arr) + guard arr.all({A.len() == 2}) collector/{} arr.each(F(pair) { collect(*pair) }) +} TEST Hash([['a', 1], ['c', 3]]) == {'a': 1, 'c': 3} @@ -3121,6 +3159,8 @@ doc %RET - Box. fb if predicate succeeds, EmptyBox if not. doc %EX - Box(10).filter(X>5) # doc %EX - Box(10).filter(X>20) # F filter(fb:FullBox, predicate=identity) { + # Can not use "...super(fb, predicate) or EmptyBox()" because filter(Eachable1, ...) can't create FullBox + # ("FullBox must be initialized with exactly one value") on order to push() into it p = Pred(predicate) p(fb.val) returns fb EmptyBox() @@ -3128,11 +3168,7 @@ F filter(fb:FullBox, predicate=identity) { TEST FullBox(null).filter() == EmptyBox() -doc Do nothing -doc %RET - eb -doc %EX - EmptyBox().filter(X>20) # -F filter(eb:EmptyBox, predicate=identity) eb - +# Done by filter(Eachable1) TEST EmptyBox().filter() == EmptyBox() doc Get FullBox value @@ -3310,7 +3346,7 @@ F get(s:Success) s.val doc Throws ResultFail doc %EX - { 1 / 0 }.Result().get() # ResultFail exception F get(f:Failure) { - throw ResultFail("Can not get value of Failure").set('cause', f.val) + throw ResultFail("Can not get value of Failure", f.val) } doc Gets wrapped value @@ -4200,7 +4236,7 @@ section "Thread" block _ { throw ThreadJoinFail("Failed to c_pthread_join").set('code', join_result[0]) } if join_result[1][0] { - throw OtherThreadFail("Joined thread threw exception").set('cause', join_result[1][1]).set('thread', t) + throw OtherThreadFail("Joined thread threw exception", join_result[1][1]).set('thread', t) } join_result[1][1] } @@ -4490,15 +4526,7 @@ section "Path" { doc %EX - Path(".") # doc %EX - Path(".", true) # doc %EX - ``find tmp/v8``.map(Path(X, true)).map(typeof).name.Stats() # - F Path(s:Str, subtype=false) { - ret = Path() - ret.path = s - if subtype { - ret.specific() - } else { - ret - } - } + F Path(s:Str, subtype=false) Path().set('path', s).only({subtype}, specific) doc Path constructor doc s - path @@ -4982,9 +5010,7 @@ F basename(p:Path) p.path.basename() doc Get basename of the file. F basename(s:Str) { - ret = s.split('/')[-1] - not(ret) throws BasenameArgumentFail("'${s}' is not a valid input to basename()").set('s', s) - ret + s.split('/')[-1] or throw BasenameArgumentFail("'${s}' is not a valid input to basename()").set('s', s) } TEST basename("/abc") == "abc" @@ -5041,11 +5067,13 @@ F close(f:File) { ret = c_close(f.fd) ret < 0 throws FileIOFail("Failed to close the file $f").set('file', f) f.fd = null + f } # Note: EINTR should not be handled doc Close a file. Uses CLOSE(2). doc Throws FileIOFail if an underlying error occurs. +doc %RET - fd F close(fd:Int) { ret = c_close(fd) ret < 0 throws FileIOFail("Failed to close file descriptor $fd").set('fd', fd) @@ -5235,10 +5263,7 @@ doc %RET - Bool doc %EX - if $(test -f myfile) ... # if runs Bool() on any non-Bool condition expression F Bool(pp:ProcessesPipeline) pp.wait().processes.all({A.exit_code == 0}) -F finished(p:Process) { - p.exit_code is Int or - p.exit_signal is Int -} +F finished(p:Process) [p.exit_code, p.exit_signal].any(Int) doc Wait for a process. Waits for reading and writing threads to finish. doc Waits for the process to finish. Checks whether to throw ProcessFail using finished_ok(). @@ -5284,6 +5309,7 @@ F wait(p:Process) { } doc Wait for all processes to finish. +doc %RET - pp F wait(pp:ProcessesPipeline) { pp.processes.each(wait) last_process = pp.processes[-1] @@ -5408,7 +5434,10 @@ F '$()'(p:Process) { debug("process", "Parsed command: ${argv}") if p.command.options.get('log') == true { - log("Running command: ${argv.map(escape_bash).join(' ')}") + F redir_str(r:CommandRedir) { + (r.fd or '').Str() + r.marker + escape_bash(r.datum) + } + log("Running command: ${argv.map(escape_bash).join(' ')}${' ' +? p.command.redirects.map(redir_str).join(' ')}") } try { @@ -6359,6 +6388,56 @@ F code(n:Null) "null" doc Convert Null into JSON compatible data structure - null F JsonData(n:Null) n +section "Lines" { + doc Array of strings which comprise a text which is treated as a + doc unit for purposes of output (and maybe for other purposes later). + type Lines(ArrLike) + + doc Split s to strings using end-of-line separators. + doc UNIX and Windows line endings supported (Windows - not tested yet). + doc Warning: Max OS <= 9 line separation of CR (\r) is not supported + doc %EX - "xx\nyy".lines() # %[xx yy] + F init(lines:Lines, s:Str) { + _lines = if s { + s -= MaybeSfx('\n') + s.split('\n').map(X - MaybeSfx('\r')) + } else { + [] + } + lines.init(_lines) + } + + doc Echo all lines using echo(Str) + F echo(lines:Lines) lines.each(echo) + + doc Echo all lines to dst + F echo(dst, lines:Lines) { + lines.each(echo(dst, X)) + dst + } + + doc Write all lines to standard error using warn(Str) + F warn(lines:Lines) lines.each(warn) + + doc Write all lines to standard error using error(Str) + F error(lines:Lines) lines.each(error) + + # Later this should be a widget which can be displayed + # with ASCII art in console or as a box in a browser. + # WARNING: only works when one byte is one character. + doc %STATUS - experimental + F framed(lines:Lines) { + not(lines) returns lines + m = lines.map(len).max() + border = '+' + ('-' * (m+2)) + '+' + collector/Lines() { + collect(border) + lines.each(F(x) collect("| ${x.Str(m)} |")) + collect(border) + } + } +} + # === exit_hook - Print uncaught exception ======= doc Convert an array to a possibly shorter version, for displaying to human. @@ -7034,6 +7113,17 @@ F write(f:File, s:Str) { write(if f.fd is Null then f.path else f.fd, s) } +# TODO: better exception +F realpath(path:Str) { + c_realpath(path) or throw CError("Faied to realpath()").set(path=path) +} + +doc %RET - Same type as p but using absolute path +F realpath(p:Path) { + t = typeof(p) + t(realpath(p.path)) +} + section "JSON" { doc Convert data structure into JSON compatible data using JsonData and call built-in encode_json(). doc Implement JsonData() for your own types to customize converting to JSON. @@ -7092,3 +7182,289 @@ section "Doc" { Doc::Node(name, children.reject(NoData).map(only(FullBox, get)), **attributes) } } + + +section "Loading code" { + + # TODO: XDG + NGS_PATH = %[ + /usr/local/etc/ngs + /usr/local/lib/ngs + /etc/ngs + /usr/lib/ngs + ] + + if _h = ENV.get('HOME') { + NGS_PATH.unshift(_h / '.ngs') + } + + if _d = ENV.get('NGS_DIR') { + warn("NGS_DIR environment variable is deprecated. Use NGS_PATH instead.") + NGS_PATH = [_d] + } + + if _p = ENV.get('NGS_PATH') { + debug('autoload', "Using environment variable NGS_PATH: ${_p}") + NGS_PATH = _p.split(':') + } + + + F bootstrap_print_compilation_warnings(program_bytecode, fname) { + program_bytecode.attrs().warnings.each(F(w) { + wl = w.location + warn("${fname}:${wl.first_line}:${wl.first_column} warning: ${w.message}") + }) + } + + doc Runs the given file + doc %RET - Typically whatever the last expression in the file evaluates to + F require(fname:Str) { + candidates = NGS_PATH.map(X / fname) + debug('require', "Candidate files: ${candidates.join(',')}") + f = candidates.first(File(X), null) + not(f) throws RequireFail("Failed to require() $fname. It was not found in path").set(fname=fname, path=NGS_PATH, candidates=candidates) + require(f) + } + + F require(fname:Str) { + guard fname ~ Pfx('/') + try { + program_text = read(fname) + program_bytecode = compile(program_text, fname) + debug('require', "require(): bytecode for ${fname} has length of ${program_bytecode.len()} bytes") + bootstrap_print_compilation_warnings(program_bytecode, fname) + program_func = load(program_bytecode, "require()d file: $fname") + ret = program_func() + debug('require', "require(): done requiring ${fname}") + ret + } catch(e) { + guard e is not RequireFail + guard e is not NormalExit # Used by CLI + throw RequireFail(e) + } + } + + F require(p:Path) require(p.path) + + doc %STATUS - experimental + F require(d:Dir) { + debug('require', "Requiring directory ${d}") + for path_dir in NGS_PATH.reverse() { + if _d = Dir(path_dir / d.path) { + dir(_d).map(require) + } + } + } + + AUTOLOAD = ns { + global global_not_found_handler + + _lock = ReentrantLock() + + # Sorted by: file, global variable name + # Not exposing _global_name_to_autoload_file as use cases are not clear yet. + # Also, it's probably better to expose an API and not the data. + _global_name_to_autoload_file = %{ + InMemoryCache Cache + OnDiskCache Cache + + IPAddr IP + IPNet IP + + ArrIter Iter + ConstIter Iter + HashIter Iter + NoNext Iter + RangeIter Iter + + ResDef Res + } + + invocation = 0 + + doc Called when reading undefined global. + doc Implements autoloading. + doc Searches for autoload/globals/GLOBAL_NAME.ngs in NGS_PATH + doc WARNING: May have security implications when looking up a name from untrusted source. + doc %EX - test("My web server runs") do { .... } # autoload/globals/test.ngs is automatically loaded. + F global_not_found_handler(name:Str) { + _lock.acquire() do { + + F _debug(s:Str) debug("autoload", "global_not_found_handler() [invocation $i] ${s}") + + i = invocation + invocation += 1 + _debug("name=${name}") + idx = ll_resolve_global_variable(name) + ll_is_global_variable_defined(idx) returns + if name in _global_name_to_autoload_file { + _debug("loading autoload/globals/${_global_name_to_autoload_file[name]}.ngs") + require("autoload/globals/${_global_name_to_autoload_file[name]}.ngs") + return + } + _debug("loading autoload/globals/${name}.ngs") + + val = require("autoload/globals/${name}.ngs") + if not(ll_is_global_variable_defined(idx)) { + _debug("global variable ${name} still not set; setting") + ll_set_global_variable(idx, val) + } + + } + } + } + +} + + +section "Bootstrap" { + main = MultiMethod() + + doc Internal method. Please do not use. + doc Main entry point. Executed on NGS boot. + doc %STATUS - internal + F bootstrap() { + global ARGV0 + global ORIG_ARGV = copy(ARGV) + + if 'NGS_BOOTSTRAP_DEBUG' in ENV { + warn("Using deprecated NGS_BOOTSTRAP_DEBUG environment variable. It has no effect. Use DEBUG=bootstrap instead.") + } + + code_to_run = null + + debug('bootstrap', 'start') + + ARGV0 = ARGV.shift(null) + + # *** Parse switches *** + sw = null + if ARGV { + a = ARGV[0] + if a == '--version' { + echo(VERSION) + return 0 + } + + if a in %[-e -p -pj -pi -pl -pil -pjl -pc -pt] { + sw = ARGV.shift() + # Newline at the end is for avoiding # commenting out the closing } + # Newline at the beginning is for symmetry + default_code = 'F default_argv_code() { fetch() }()' + code_to_run = "{\n${ARGV.shift(default_code)}\n}" + debug('bootstrap', "Got $sw switch, using command line provided code") + fname = "" + } + } + + if not(code_to_run) { + debug('bootstrap', 'No -e switch was passed, will load the program from a file') + fname = ARGV.shift(null) + if not(fname) { + return require("cli.ngs") + } + ARGV0 = fname + code_to_run = read(fname) + } + + program_bytecode = compile(code_to_run, fname) + debug('bootstrap', "bootstrap(): bytecode for ${fname} has length of ${program_bytecode.len()} bytes") + bootstrap_print_compilation_warnings(program_bytecode, fname) + program_func = load(program_bytecode, "bootstrap()ped file: $fname") + + result = program_func() + + section "Handle main()" { + + F in_main_file(m) m.ip().resolve_instruction_pointer().file == fname + + result = econd { + result is Namespace { + if 'main' in result { + bootstrap_invoke_main(result['main']) + } else { + result + } + } + main is MultiMethod { + m = main.filtero(in_main_file) + if len(main) != len(m) { + warn("main method define in non-main file") + } + if m { + bootstrap_invoke_main(m) + } else { + result + } + } + main is Fun { + bootstrap_invoke_main(main) + } + true { + throw MainFail("'main' must be a Namespace, MultiMethod or a Fun, not ${main.typeof().name}").set('value', main) + } + } + } + + transformation = { + '-pl': identity + '-pil': inspect + '-pjl': encode_json + } + if sw in transformation { + for r in result { + transformation[sw](r).echo() + } + return 0 + } + + transformation = { + '-p': identity + '-pi': inspect + '-pj': encode_json + '-pc': code + '-pt': { Table2::Table(A) } # Lazy loading of Table2 + } + if sw in transformation { + echo(transformation[sw](result)) + return 0 + } + + result.ExitCode() + } + + doc Internal method. Please do not use. + doc Runs bootstrap() and handles exceptions. + doc %STATUS - internal + F bootstrap_exception_catch_wrapper() { + exit = Exit().set(exit_code=0, exceptions=[]) + try { + exit.exit_code = bootstrap() + } catch(e:Exception) { + exit.exceptions.push(e) + exit.exit_code = 240 + try { + exit.exit_code = e.ExitCode() + } catch(e:Exception) { + exit.exceptions.push(ExitCodeFail("Failed to convert exception to exit code", e)) + exit.exit_code = exit.exit_code + 1 + } + } + + try { + exit_hook(exit) + } catch(e:Exception) { + exit.exceptions.push(e) + exit.exit_code = exit.exit_code + 2 + exit.exceptions.each(F(e) { + error("[bootstrap_exception_catch_wrapper] --- Uncaught exception ---") + dump(e) + }) + } + + c_exit(exit.exit_code) + } + + bootstrap_exception_catch_wrapper() + +} diff --git a/ngs.c b/ngs.c index 6af83f02..6cefda95 100644 --- a/ngs.c +++ b/ngs.c @@ -13,6 +13,7 @@ #include "compile.h" #include "decompile.h" #include "vm.h" +#include "stdlib.ngs.h" char *sprintf_position(yycontext *yy, int pos) { int linecol[2]; @@ -23,41 +24,13 @@ char *sprintf_position(yycontext *yy, int pos) { } char *find_bootstrap_file() { - static char *places[] = { - "/usr/local/etc/ngs/bootstrap.ngs", - "/usr/local/lib/ngs/bootstrap.ngs", - "/etc/ngs/bootstrap.ngs", - "/usr/lib/ngs/bootstrap.ngs", - NULL - }; char *fname; - char *home_dir; - int i; - int len; - char fmt[] = "%s/.bootstrap.ngs"; fname = getenv("NGS_BOOTSTRAP"); if(fname) { return fname; } - home_dir = getenv("HOME"); - if(home_dir) { - len = snprintf(NULL, 0, fmt, home_dir) + 1; - fname = (char *)NGS_MALLOC_ATOMIC(len); - snprintf(fname, len, fmt, home_dir); - // printf("HOME fname: %s\n", fname); - if(access(fname, F_OK) != -1) { - return fname; - } - } - - for(i=0; places[i]; i++) { - if(access(places[i], F_OK) != -1) { - return places[i]; - } - } - return NULL; } @@ -145,7 +118,7 @@ int main(int argc, char **argv) if(0) { yymatchDot(NULL); yyAccept(NULL, 0); } NGS_GC_INIT(); - VALUE main_thread_local = make_hash(4); + VALUE main_thread_local = make_namespace(4); pcre_malloc = GC_malloc; pcre_free = GC_free; @@ -160,17 +133,19 @@ int main(int argc, char **argv) yyctx.fail_rule = "(unknown)"; yyctx.lines = 0; yyctx.lines_postions[0] = 0; + yyctx.input_file = NULL; bootstrap_file_name = find_bootstrap_file(); if(!bootstrap_file_name) { - fprintf(stderr, "Could not find bootstrap file\n"); - exit(246); - } - if(!strcmp(bootstrap_file_name, "-")) { - source_file_name = ngs_strdup(""); - yyctx.input_file = stdin; + source_file_name = ngs_strdup(""); + yyctx.input_file = fmemopen(lib_stdlib_ngs, lib_stdlib_ngs_len, "r"); } else { - source_file_name = bootstrap_file_name; - yyctx.input_file = fopen(bootstrap_file_name, "r"); + if(!strcmp(bootstrap_file_name, "-")) { + source_file_name = ngs_strdup(""); + yyctx.input_file = stdin; + } else { + source_file_name = ngs_strdup(bootstrap_file_name); + yyctx.input_file = fopen(bootstrap_file_name, "r"); + } } if(!yyctx.input_file) { fprintf(stderr, "Error while opening bootstrap file '%s': %d - %s\n", bootstrap_file_name, errno, strerror(errno)); @@ -191,7 +166,7 @@ int main(int argc, char **argv) // TODO: use native_... methods to load and run the code COMPILATION_RESULT *r = compile(tree, source_file_name); vm_init(&vm, argc, argv); - set_global(&vm, "BOOTSTRAP_FILE", make_string(bootstrap_file_name)); + set_global(&vm, "BOOTSTRAP_FILE", make_string(source_file_name)); ctx_init(&ctx); ip = vm_load_bytecode(&vm, r->bytecode); closure = make_closure_obj(ip, 0, 0, 0, 0, 0, NULL, NULL); diff --git a/obj.c b/obj.c index a2fd0fa4..9a6f45c9 100644 --- a/obj.c +++ b/obj.c @@ -1,5 +1,7 @@ #include +#ifdef HAVE_EXECINFO_H #include +#endif #include #include @@ -36,7 +38,13 @@ static void _dump(FILE *f, VALUE v, int level) { if(IS_NATIVE_METHOD(v)) { symbols_buffer[0] = OBJ_DATA_PTR(v); +#ifdef HAVE_EXECINFO_H symbols = backtrace_symbols(symbols_buffer, 1); +#else + symbols = NGS_MALLOC(sizeof(char *)); + symbols[0] = ngs_strdup("(name unavailable due to missing execinfo.h at build time)"); + +#endif fprintf(f, "%*s* native method %s at %p req_params=%d\n", level << 1, "", symbols[0], OBJ_DATA_PTR(v), NATIVE_METHOD_OBJ_N_REQ_PAR(v)); for(i=0; inumber = SWITCH_ number = - <[-+]?"0x"[0-9a-fA-F]+> ! [a-zA-Z] { + <[-+]?"0x"[0-9a-fA-F]+> ! ([a-zA-Z] | "." [0-9a-fA-F]) { MAKE_NODE(ret, INT_NODE); ret->number = strtol(yytext, NULL, 16); $$ = ret; } | - <[-+]?[0-9]+[.][0-9]+> ! [a-zA-Z] { + <[-+]?[0-9]+[.][0-9]+> ! ([a-zA-Z] | "." [0-9]) { MAKE_NODE(ret, REAL_NODE); ret->data = NGS_MALLOC(sizeof(NGS_REAL)); *(NGS_REAL *)ret->data = strtod(yytext, NULL); $$ = ret; } | - <[-+]?[0-9]+> ! [a-zA-Z] { + <[-+]?[0-9]+> ! ([a-zA-Z] | "." [0-9]) { MAKE_NODE(ret, INT_NODE); ret->number = atol(yytext); $$ = ret; diff --git a/test.ngs b/test.ngs index 8a78f615..443476fb 100755 --- a/test.ngs +++ b/test.ngs @@ -27,7 +27,8 @@ F perform_tests_in_file(file:Str) { }) } -files = %[bootstrap.ngs stdlib.ngs lang-tests.ngs].map(NGS_DIR / X) + ``find "${NGS_DIR}/autoload" -name '*.ngs'`` +base_dir = ENV.get('NGS_TESTS_BASE_DIR', `line: dirname ${realpath(ARGV0)}` / 'lib') +files = %[lang-tests.ngs stdlib.ngs].map(base_dir / X) + ``find "${base_dir}/autoload" -name '*.ngs'`` files.each(perform_tests_in_file) diff --git a/version.h b/version.h index 772890c6..58384ed6 100644 --- a/version.h +++ b/version.h @@ -1,6 +1,6 @@ #ifndef VERSION_H #define VERSION_H -#define NGS_VERSION "0.2.8" +#define NGS_VERSION "0.2.9" #endif // VERSION_H diff --git a/vim/syntax/ngs.vim b/vim/syntax/ngs.vim index 6c069823..57446d8e 100644 --- a/vim/syntax/ngs.vim +++ b/vim/syntax/ngs.vim @@ -40,7 +40,6 @@ syn keyword ngsType Range RangeIter ReadFail ReadingPipeBetweenChildren Real Ree syn keyword ngsType Seq Set Sfx SocketFile SplatMethodParam StackDepthFail Stat StatFail Stats Str SubSeq Success SwitchFail SwitchParseFail Symlink syn keyword ngsType Table TestFail TestMessage TestsResults Thread ThreadFail Threads Time TimeFail TmpFile TtyCheckFail Type syn keyword ngsType UndefinedLocalVar UserDefinedMethod -syn keyword ngsType ValueWrapper syn keyword ngsType WriteFail WritingPipeBetweenChildren syn keyword ngsType c_ffi_cif c_ffi_type c_pthread_attr_t c_pthread_cond_t c_pthread_mutex_t c_pthread_mutexattr_t c_pthread_t c_tm " Generated types - end @@ -54,11 +53,11 @@ syn keyword ngsFunction Pred syn keyword ngsFunction SafeStr StrForTable StrParams Strs syn keyword ngsFunction TODO syn keyword ngsFunction abs access acquire all any args arr_splat assert assert_array assert_base assert_bool assert_eq assert_exit_code assert_has assert_hash assert_hash_keys assert_hash_keys_values assert_in assert_match assert_min_len assert_output_has assert_path_exists assert_resolvable assert_string attrs -syn keyword ngsFunction band basename bootstrap bootstrap_debug bootstrap_exception_catch_wrapper bootstrap_find_ngs_dir bootstrap_invoke_main bootstrap_print_compilation_warnings bootstrap_try_main bor bxor -syn keyword ngsFunction c_access c_chdir c_close c_closedir c_dlopen c_errno c_execve c_exit c_ffi_call c_ffi_prep_cif c_fork c_fstat c_getpid c_getppid c_gettimeofday c_gmtime c_isatty c_kill c_localtime c_lseek c_lstat c_mktime c_open c_opendir c_pcre_compile c_pcre_exec c_pipe c_poll c_pthread_attr_init c_pthread_cond_broadcast c_pthread_cond_destroy c_pthread_cond_init c_pthread_cond_signal c_pthread_cond_wait c_pthread_create c_pthread_join c_pthread_mutex_init c_pthread_mutex_lock c_pthread_mutex_unlock c_pthread_mutexattr_init c_pthread_mutexattr_settype c_pthread_self c_read c_readdir c_stat c_strcasecmp c_strcmp c_strerror c_strftime c_strptime c_time c_waitpid c_write cached calculate_num_cols_to_show call ceil cell_display_width chdir child_fd chr close close_reading_end close_writing_end code column columns compile config converge copy count create created +syn keyword ngsFunction band basename bootstrap bootstrap_exception_catch_wrapper bootstrap_invoke_main bootstrap_print_compilation_warnings bootstrap_try_main bor bxor +syn keyword ngsFunction c_access c_chdir c_close c_closedir c_dlopen c_errno c_execve c_exit c_ffi_call c_ffi_prep_cif c_fork c_fstat c_getpid c_getppid c_gettimeofday c_gmtime c_isatty c_kill c_localtime c_lseek c_lstat c_mktime c_open c_opendir c_pcre_compile c_pcre_exec c_pipe c_poll c_pthread_attr_init c_pthread_cond_broadcast c_pthread_cond_destroy c_pthread_cond_init c_pthread_cond_signal c_pthread_cond_wait c_pthread_create c_pthread_join c_pthread_mutex_init c_pthread_mutex_lock c_pthread_mutex_unlock c_pthread_mutexattr_init c_pthread_mutexattr_settype c_pthread_self c_read c_readdir c_realpath c_stat c_strcasecmp c_strcmp c_strerror c_strftime c_strptime c_time c_waitpid c_write cached calculate_num_cols_to_show call ceil cell_display_width chdir child_fd chr close close_reading_end close_writing_end code column columns compile config converge copy count create created syn keyword ngsFunction debug decode decode_hex decode_json decode_uri_component del delete dflt die digest dir drop dump syn keyword ngsFunction each each_chunk each_group_test each_idx_key_val each_idx_val eachk eachv echo encode encode_hex encode_html encode_html_attr encode_json encode_uri_component ends_with ensure ensure_array error exception_specific_message exit expect -syn keyword ngsFunction fetch filter filterk filterv finally find find_if_needed find_in_path finished finished_ok first flatten floor framed +syn keyword ngsFunction fetch filter filterk filtero filterv finally find find_if_needed find_in_path finished finished_ok first flatten floor framed syn keyword ngsFunction get glob global_not_found_handler globals gmtime group syn keyword ngsFunction has has_index has_no hash hash_splat syn keyword ngsFunction id identity ids in index indexes inherit init inspect intersperse ip is is_blocked_group is_subtype isatty @@ -66,10 +65,10 @@ syn keyword ngsFunction join syn keyword ngsFunction keys kill syn keyword ngsFunction latest len limit lines ll_hash_entry_key ll_hash_entry_next ll_hash_entry_val ll_hash_head ll_hash_tail ll_is_global_variable_defined ll_resolve_global_variable ll_set_global_variable ll_thread_local load localtime log lstat lte syn keyword ngsFunction main map map_base_idx map_idx_key_val map_idx_val mapk mapkv mapo mapv max maybe_print_stacktrace merge_sorted method_not_found_handler min myip -syn keyword ngsFunction n next none nop normalize_presence_list not +syn keyword ngsFunction next none nop normalize_presence_list not syn keyword ngsFunction only open opt_prop ord syn keyword ngsFunction params parent_fd partial partial_tail partition peek pfilter pid pmap pop pos ppid print_exception progress ptimes push push_all -syn keyword ngsFunction rand rand_uniq read reduce register_column reject rejectk rejectv release replace report req_prop require resolve_instruction_pointer retry reverse round run +syn keyword ngsFunction rand rand_uniq read realpath reduce register_column reject rejectk rejectv release replace report req_prop require resolve_instruction_pointer retry reverse round run syn keyword ngsFunction set shift sort sortk sortv specific split srand starts_with stat status stdlib_aws_straighten_tags store strftime subset sum syn keyword ngsFunction take tap test the_one throw_if_no_next time times trunc typeof syn keyword ngsFunction uniq unshift update users_ids diff --git a/vm.c b/vm.c index 77170892..45ffd21b 100644 --- a/vm.c +++ b/vm.c @@ -4,10 +4,17 @@ #include #include #include -#include #include #include +#ifdef HAVE_POLL_H +#include +#endif + +// REALPATH(3) +#include +#include + // GETTIMEOFDAY(2) #include @@ -688,6 +695,18 @@ METHOD_RESULT native_c_open_str_str METHOD_PARAMS { return METHOD_OK; } +METHOD_RESULT native_c_realpath_str METHOD_PARAMS { + const char *path = obj_to_cstring(argv[0]); + char resolved_path[PATH_MAX + 1]; + resolved_path[0] = '\0'; + if(realpath(path, resolved_path)) { + *result = make_string(resolved_path); + } else { + *result = MAKE_NULL; + } + METHOD_RETURN(*result); +} + // READ(2) // TODO: error handling support METHOD_RESULT native_c_read_int_int METHOD_PARAMS { @@ -1642,6 +1661,7 @@ METHOD_RESULT native_c_access METHOD_PARAMS { } \ }; +#ifdef HAVE_POLL_H // WIP METHOD_RESULT native_c_poll METHOD_PARAMS { VALUE ret, revents; @@ -1663,6 +1683,7 @@ METHOD_RESULT native_c_poll METHOD_PARAMS { ARRAY_ITEMS(ret)[1] = revents; METHOD_RETURN(ret); } +#endif METHOD_RESULT native_id_pthread METHOD_PARAMS { unsigned char *p; @@ -2257,6 +2278,9 @@ void vm_init(VM *vm, int argc, char **argv) { set_global(vm, "OS", OS); + VALUE FEATURES = make_hash(4); + set_global(vm, "FEATURES", FEATURES); + MK_BUILTIN_TYPE_DOC(Null, T_NULL, "Null type. Has only one instance, null"); vm->type_by_value_tag[V_NULL >> TAG_BITS] = &vm->Null; @@ -3020,6 +3044,9 @@ void vm_init(VM *vm, int argc, char **argv) { _doc(vm, "", "Open a file. Uses OPEN(2)."); _doc(vm, "flags", "r - O_RDONLY; w - O_WRONLY | O_CREAT | O_TRUNC; a - O_WRONLY | O_CREAT | O_APPEND"); _doc(vm, "%RET", "Int - file descriptor or -1"); + register_global_func(vm, 0, "c_realpath", &native_c_realpath_str, 1, "path", vm->Str); + _doc(vm, "", "Real path. Uses REALPATH(3)."); + _doc(vm, "%RET", "Str or null"); register_global_func(vm, 0, "c_close", &native_c_close_int, 1, "fd", vm->Int); _doc(vm, "", "Close a file. Uses CLOSE(2)."); _doc(vm, "%RET", "Int - zero on success or -1"); @@ -3030,7 +3057,12 @@ void vm_init(VM *vm, int argc, char **argv) { register_global_func(vm, 0, "c_write", &native_c_write_int_str, 2, "fd", vm->Int, "s", vm->Str); _doc(vm, "", "Write to a file. Uses WRITE(2)."); _doc(vm, "%RET", "Int - number of bytes written or -1"); +#ifdef HAVE_POLL_H register_global_func(vm, 0, "c_poll", &native_c_poll, 2, "fds_evs", vm->Arr, "timeout", vm->Int); + set_hash_key(FEATURES, make_string("poll"), MAKE_TRUE); +#else + set_hash_key(FEATURES, make_string("poll"), MAKE_FALSE); +#endif // TODO DOC register_global_func(vm, 1, "c_lseek", &native_c_lseek_int_int_str,3,"fd", vm->Int, "offset", vm->Int, "whence", vm->Str); _doc(vm, "", "Call LSEEK(2)."); @@ -3551,8 +3583,10 @@ void vm_init(VM *vm, int argc, char **argv) { #undef A set_global(vm, "ACCESS", access); +#ifdef HAVE_POLL_H // --- man poll(2) --- E(POLLIN); E(POLLPRI); E(POLLOUT); E(POLLERR); E(POLLHUP); E(POLLNVAL); +#endif // --- man 2 stat --- E(S_IFMT); E(S_IFSOCK); E(S_IFLNK); E(S_IFREG); E(S_IFBLK); E(S_IFDIR); E(S_IFCHR); E(S_IFIFO);